UNPKG

psytask

Version:

JavaScript Framework for Psychology task

234 lines (186 loc) 5.52 kB
# Psytask JavaScript Framework for Psychology task. Compatible with the jspsych plugin. Compare to jsPsych, Psytask has: - Easier and more flexible development experiment - Higher time precision, try [online test](https://bluebones-team.github.io/psytask) on your browser. - Smaller bundle size, Faster loading speed ## Install If you're an old hand at Web development, we recommend installing via NPM: ```bash npm i psytask ``` Otherwise, via CDN: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!-- load jspsych css if needed, it should be above psytask css --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspsych@8.2.2/css/jspsych.css" /> <!-- load psytask css --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/psytask/dist/main.css" /> </head> <body> <!-- main script --> <script type="module"> // load psytask js import { createApp } from 'https://cdn.jsdelivr.net/npm/psytask/dist/index.min.js'; const app = await creaeApp(); //... </script> <!-- load jspsych plugin if needed, it should be below psytask js and add `defer` property --> <script defer src="https://cdn.jsdelivr.net/npm/@jspsych/plugin-html-keyboard-response@2.1.0/dist/index.browser.min.js" ></script> </body> </html> ``` ## Usage :::NOTE Considering that most psychology researchers do not have a background in Web development, the following examples will be used based on a cdn installation. ::: All psychological tasks are combinations of a series of scenes, writing a psychology task requires only 2 steps: 1. create scene 2. show scene ### Create Scene ```js import { createApp, h } from '<your-cdn-url>'; // create app const app = await createApp(); // create bulit-in scenes const fixation = app.fixation({ duration: 1000 }); const blank = app.blank(); const guide = app.text('Welcome to our task', { close_on: 'click' }); // create jsPsych scene const jsPsychScene = app.jsPsych({ type: jsPsychHtmlKeyboardResponse, // please import jspsych.css via CDN yourself stimulus: 'Hello world', choices: ['f', 'j'], }); // create custom scene const scene = app.scene( // 1st. argument: setup function function (self) { // 1. create custom element to show custom stimulus const el = h('p'); // 2. mount it to scene root element self.root.appendChild(el); // 3. return update function, which will be run on each time it is shown return (props) => { // 4. apply custom show paramenters el.textContent = `current show params is: ${JSON.stringify(props)}`; }; }, // 2nd. argument: scene options { duration: 200, close_on: 'keydown', on_frame(time) { console.log(`last frame time is: ${time}`); }, }, ); ``` ### Show Scene Based on the above example: ```js // show with paramenters const data = await scene.show('custom show params'); // show with new scene options const data = await scene.config({ duration: Math.random() * 1000 }).show(); ``` Scene will log the first frame time in each show: ```js const { start_time } = await scene.show(); ``` Usually, we need to show a series of scenes: ```js import { RandomSampling, StairCase } from '<your-cdn-url>'; // show a fixed sequence for (const char of ['A', 'B', 'C']) { await scene.show(char); } // show a random sequence for (const char of new RandomSampling({ candidates: ['A', 'B', 'C'], sampleSize: 10, replace: true, })) { await scene.show(char); } // show a staircase const staircase = new StairCase({ start: 10, step: 1, up: 3, down: 1, reversal: 6, min: 1, max: 12, }); for (const value of staircase) { await scene.show(value); staircase.response(Math.random() > 0.5); // mock user response } ``` ### Data Collection We use `DataCollector` to save data as a file: ```js import { DataCollector } from '<your-cdn-url>'; // create data collector const dc = new DataCollector('demo.csv'); // show scenes for (const char of ['A', 'B', 'C']) { const data = await scene.show(char); // add a row await dc.add({ stimulus: char, // first frame time start_time: data.start_time, }); } // save as file await dc.save(); ``` Usually, scene will just collect the first frame time into the `start_time` data field. If we want to log subject response, we can add data field to scene: ```js const scene = app.scene(function (self) { const el = h('p'); self.root.appendChild(el); // 1. add event listener self.useEventListener(el, 'keydown', (event) => { self.close(); // press any key to close // 2. set data field self.data['key'] = event.key; self.data['rt'] = event.timeStamp - self.data.start_time; }); return (props) => { el.textContent = `current show params is: ${JSON.stringify(props)}`; }; }); // the data has 3 fields now: start_time, key, rt const data = await scene.show(); ``` ### Clean up After a scene is shown, we need to clean up the resources manually. This is done to free up invalid memory and avoid memory leaks: ```js // we create the app and a scene before, so we need to clean up them app[Symbol.dispose](); scene[Symbol.dispose](); ``` But if we develop it with TypeScript 5.2+ and create them via `using` keyword, they will be cleaned up automatically: ```ts using app = await createApp(); using scene = app.text('custom text'); ```