Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

World smallest JavaScript Behavioral Experimental Framework. Compatible with the jsPsych plugin.

License

NotificationsYou must be signed in to change notification settings

bluebonesx/psytask

Repository files navigation

NPM VersionNPM DownloadsjsDelivr hits (npm)

JavaScript Framework for Psychology tasks.Make development like making PPTs.

Compared to others, it:

  • Easier and more flexible
  • Higher time precision. Seebenchmark
  • Smaller bundle size, Faster loading speed. Seebenchmark
  • Type-Safe

Integration with:

API Docs |Benchmark |Tests |Play it now ! 🥳

Install

Note

The project is in early development. Please pin the version when using it.

via NPM:

npm create psytask# optional: use templatenpm install psytask# only packagenpm install @psytask/component vanjs-core vanjs-ext# optional: use components

via CDN:

<!-- add required packages  --><scripttype="importmap">{"imports":{"psytask":"https://cdn.jsdelivr.net/npm/psytask@1/dist/index.min.js","@psytask/core":"https://cdn.jsdelivr.net/npm/@psytask/core@1/dist/index.min.js","@psytask/components":"https://cdn.jsdelivr.net/npm/@psytask/components@1/dist/index.min.js","vanjs-core":"https://cdn.jsdelivr.net/npm/vanjs-core@1.6","vanjs-ext":"https://cdn.jsdelivr.net/npm/vanjs-ext@0.6"}}</script><!-- load packages --><scripttype="module">import{createApp}from'psytask';usingapp=awaitcreaeApp();</script>

Warning

PsyTask uses the modern JavaScriptusing keyword for automatic resource cleanup.

For CDN usage in old browsers that don't support theusing keyword, you will seeUncaught SyntaxError: Unexpected identifier 'app'. You need to change the code:

// Instead of: using app = await createApp();constapp=awaitcreateApp();// ... your code ...app.emit('dispose');// Manually clean up when done

Or, you can use the bundlers (like Vite, Bun, etc.) to transpile it.

Usage

The psychology tasks are just like PPTs; they both have a series of scenes.So writing a task only requires 2 steps: creating and showing scenes.

Create Scene

All you need isComponent:

import{Grating,adapter}from'@psytask/components';usingsimpleText=app.scene(// componentGrating,// scene options{    adapter,// VanJS supportdefaultProps:{type:Math.sin,size:100,sf:0.02},// show paramsduration:1e3,// show 1000 msclose_on:'key: ',// close on space key},);

Show Scene

Override default props or options:

constdata=awaitscene.show({text:'Press F or J'});// new propsconstdata=awaitscene.config({duration:1e3}).show();// new options

Block:

import{RandomSampling,StairCase}from'psytask';// fixed sequencefor(consttextof['A','B','C']){awaitscene.show({ text});}// random sequencefor(consttextofRandomSampling({candidates:['A','B','C'],sample:10,replace:true,})){awaitscene.show({ text});}// staircaseconststaircase=StairCase({start:10,step:1,up:3,down:1,reversals:6,min:1,max:12,trial:20,});for(constvalueofstaircase){constdata=awaitscene.show({text:value});constcorrect=data.response_key==='f';staircase.response(correct);// set response}

Data Collection

usingdc=app.collector('data.csv');for(consttextof['A','B','C']){constdata=awaitscene.show({ text});// `frame_times` will be recorded automaticallyconststart_time=/**@type {number} */(data.frame_times[0]);// add a rowdc.add({    text,response:data.response_key,rt:data.response_time-start_time,correct:data.response_key==='f',});}dc.final();// file contentdc.download();// download file

Learn More

Component

It a functionthat inputsProps and outputs an object includesNode andData Getter:

  • Props means the show parameters that control the display of the scene.
  • Node is a string or element, or array, that is mounted to the scene root element.
  • Data Getter is used to get generated data.
constComponent=(props)=>{constctx=getCurrentScene();return{node:'',data:()=>({})};};constComponent=(props)=>'text node';constComponent=(props)=>document.createElement('div');constComponent=(props)=>['text node',document.createElement('div')];

Caution

You shouldn't modify props, whatever, as it may change the default props.See one-way data flow inRedux andVue.

A practical example:

import{on,getCurrentScene}from'psytask';import{ImageStim,adapter}from'@psytask/components';importvanfrom'vanjs-core';const{ div}=van.tags;constComponent=/**@param {{ text: string }} props */(props)=>{/**@type {{ response_key: string; response_time: number }} */letdata;constctx=getCurrentScene();// add DOM event listenerconstcleanup=on(ctx.root,'keydown',(e)=>{if(e.key!=='f'||e.key!=='j')return;data={response_key:e.key,response_time:e.timeStamp};ctx.close();// close on 'f' or 'j'});ctx// reset data on show.on('show',()=>{data={response_key:'',response_time:0};})// remove DOM event listener on dispose.on('dispose',cleanup);// Return the element and data getterreturn{node:div(// use other ComponentImageStim({image:newImageData(1)}),),data:()=>data,};};

Tip

UseJSDoc Comment to get type hint in JavaScript.

Setup

When you callapp.scene(Component, { adapter, defaultProps }), it will useadapter.render to callComponent withdefaultProps once, thenNode will be mounted tothis.root.

Note

The component will be called only once; the following DOM update will be triggered by theProps update. Seereactivity.

Show

When you callawait scene.show(patchProps), it will execute the following process:

  • Update props: merge patch props with default props to update current props, which will triggerreactivity update.
  • Listeners added bythis.on('show') will be called.
  • Display and focusthis.root, it will be displayed on the screenin the next frame.
  • Create a timer bythis.options.timer and wait for it to stop.
  • Listeners added bythis.on('frame') will be called when the timer is running.
  • Hidethis.root when the timer is stopped, it will be hidden on the screenin the next frame.
  • Listeners added bythis.on('close') will be called.
  • Merge the timer records and the data fromData Getter.
graph LRa[update props] --> l1[on show] --> b[display & focus DOM] --> d[wait timer] --> l2[on frame] --> d --> e[hide root] --> l3[on close] --> f[merge data]
Loading

Reactivity

Stay tuned...

Better to see:VanJS tutorial,Vue reactivity

The bundle size of PsyTask is 1/12 of labjs, 1/50 of jspsych, and 1/260 of psychojs.

xychart    title "Bundle Size (KB)"    x-axis [psytask, labjs, jspsych, psychojs]    y-axis 0 --> 2600    bar [10.67, 122.45, 502.06, 2598.33]
Loading

Integration

npm i @psytask/jspsych @jspsych/plugin-clozenpm i -d jspsych# optional: for type hint

Or using CDN:

<!-- load jspsych css--><linkrel="stylesheet"href="https://cdn.jsdelivr.net/npm/jspsych@8.2.2/css/jspsych.css"/><!-- add packages --><scripttype="importmap">{"imports":{      ..."@psytask/jspsych":"https://cdn.jsdelivr.net/npm/@psytask/jspsych@1/dist/index.min.js","@jspsych/plugin-cloze":"https://cdn.jsdelivr.net/npm/@jspsych/plugin-cloze@2.2.0/+esm"}}</script>

Important

For CDNer, you should add the+esm after the jspsych plugin CDN URL, because jspsych plugins do not release ESM versions. Or you can useesm.sh.

import{jsPsychStim}from'@psytask/jspsych';importClozefrom'@jspsych/plugin-cloze';usingjspsych=app.scene(jsPsychStim,{defaultProps:{type:Cloze,text:'aba%%aba',check_answers:true,},});constdata=awaitjspsych.show();
<!-- add jatos script --><scriptsrc="jatos.js"></script>
// wait for jatos loadingawaitnewPromise((r)=>jatos.onLoad(r));usingdc=app.collector().on('add',(row)=>{// send data to JATOS serverjatos.appendResultData(row);});

About

World smallest JavaScript Behavioral Experimental Framework. Compatible with the jsPsych plugin.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2026 Movatter.jp