mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 11:10:54 +00:00
Refactor free-sort plugin to use pointer events (#2893)
This commit is contained in:
parent
cb87df4c10
commit
2f37c57d22
5
.changeset/dull-dragons-wonder.md
Normal file
5
.changeset/dull-dragons-wonder.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@jspsych/plugin-free-sort": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix event handling on non-touch devices
|
@ -1,5 +1,7 @@
|
|||||||
import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
|
import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
|
||||||
|
|
||||||
|
import { inside_ellipse, make_arr, random_coordinate, shuffle } from "./utils";
|
||||||
|
|
||||||
const info = <const>{
|
const info = <const>{
|
||||||
name: "free-sort",
|
name: "free-sort",
|
||||||
parameters: {
|
parameters: {
|
||||||
@ -240,17 +242,15 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
for (const x of make_arr(0, trial.sort_area_width - trial.stim_width, num_rows)) {
|
for (const x of make_arr(0, trial.sort_area_width - trial.stim_width, num_rows)) {
|
||||||
for (const y of make_arr(0, trial.sort_area_height - trial.stim_height, num_rows)) {
|
for (const y of make_arr(0, trial.sort_area_height - trial.stim_height, num_rows)) {
|
||||||
if (x > (trial.sort_area_width - trial.stim_width) * 0.5) {
|
if (x > (trial.sort_area_width - trial.stim_width) * 0.5) {
|
||||||
//r_coords.push({ x:x, y:y } )
|
|
||||||
r_coords.push({
|
r_coords.push({
|
||||||
x: x + trial.sort_area_width * (0.5 * trial.column_spread_factor),
|
x: x + trial.sort_area_width * (0.5 * trial.column_spread_factor),
|
||||||
y: y,
|
y,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
l_coords.push({
|
l_coords.push({
|
||||||
x: x - trial.sort_area_width * (0.5 * trial.column_spread_factor),
|
x: x - trial.sort_area_width * (0.5 * trial.column_spread_factor),
|
||||||
y: y,
|
y,
|
||||||
});
|
});
|
||||||
//l_coords.push({ x:x, y:y } )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,7 +267,6 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
stimuli = shuffle(stimuli);
|
stimuli = shuffle(stimuli);
|
||||||
}
|
}
|
||||||
|
|
||||||
let inside = [];
|
|
||||||
for (let i = 0; i < stimuli.length; i++) {
|
for (let i = 0; i < stimuli.length; i++) {
|
||||||
var coords;
|
var coords;
|
||||||
if (trial.stim_starts_inside) {
|
if (trial.stim_starts_inside) {
|
||||||
@ -312,21 +311,19 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
x: coords.x,
|
x: coords.x,
|
||||||
y: coords.y,
|
y: coords.y,
|
||||||
});
|
});
|
||||||
if (trial.stim_starts_inside) {
|
|
||||||
inside.push(true);
|
|
||||||
} else {
|
|
||||||
inside.push(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const inside = stimuli.map(() => trial.stim_starts_inside);
|
||||||
|
|
||||||
// moves within a trial
|
// moves within a trial
|
||||||
let moves = [];
|
const moves = [];
|
||||||
|
|
||||||
// are objects currently inside
|
// are objects currently inside
|
||||||
let cur_in = false;
|
let cur_in = false;
|
||||||
|
|
||||||
// draggable items
|
// draggable items
|
||||||
const draggables = display_element.querySelectorAll(".jspsych-free-sort-draggable");
|
const draggables = Array.from(
|
||||||
|
display_element.querySelectorAll<HTMLImageElement>(".jspsych-free-sort-draggable")
|
||||||
|
);
|
||||||
|
|
||||||
// button (will show when all items are inside) and border (will change color)
|
// button (will show when all items are inside) and border (will change color)
|
||||||
const border: HTMLElement = display_element.querySelector("#jspsych-free-sort-border");
|
const border: HTMLElement = display_element.querySelector("#jspsych-free-sort-border");
|
||||||
@ -345,48 +342,13 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
trial.counter_text_finished;
|
trial.counter_text_finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_event_name = "mousedown";
|
for (const draggable of draggables) {
|
||||||
let move_event_name = "mousemove";
|
draggable.addEventListener("pointerdown", function ({ clientX: pageX, clientY: pageY }) {
|
||||||
let end_event_name = "mouseup";
|
let x = pageX - this.offsetLeft;
|
||||||
if (typeof document.ontouchend !== "undefined") {
|
let y = pageY - this.offsetTop - window.scrollY;
|
||||||
// for touch devices
|
this.style.transform = "scale(" + trial.scale_factor + "," + trial.scale_factor + ")";
|
||||||
start_event_name = "touchstart";
|
|
||||||
move_event_name = "touchmove";
|
|
||||||
end_event_name = "touchend";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < draggables.length; i++) {
|
|
||||||
draggables[i].addEventListener(start_event_name, (event: MouseEvent | TouchEvent) => {
|
|
||||||
let pageX: number;
|
|
||||||
let pageY: number;
|
|
||||||
if (event instanceof MouseEvent) {
|
|
||||||
pageX = event.pageX;
|
|
||||||
pageY = event.pageY;
|
|
||||||
}
|
|
||||||
//if (typeof document.ontouchend !== "undefined") {
|
|
||||||
if (event instanceof TouchEvent) {
|
|
||||||
// for touch devices
|
|
||||||
event.preventDefault();
|
|
||||||
const touchObject = event.changedTouches[0];
|
|
||||||
pageX = touchObject.pageX;
|
|
||||||
pageY = touchObject.pageY;
|
|
||||||
}
|
|
||||||
|
|
||||||
let elem = event.currentTarget as HTMLImageElement;
|
|
||||||
let x = pageX - elem.offsetLeft;
|
|
||||||
let y = pageY - elem.offsetTop - window.scrollY;
|
|
||||||
elem.style.transform = "scale(" + trial.scale_factor + "," + trial.scale_factor + ")";
|
|
||||||
|
|
||||||
let move_event = (e) => {
|
|
||||||
let clientX = e.clientX;
|
|
||||||
let clientY = e.clientY;
|
|
||||||
if (typeof document.ontouchend !== "undefined") {
|
|
||||||
// for touch devices
|
|
||||||
const touchObject = e.changedTouches[0];
|
|
||||||
clientX = touchObject.clientX;
|
|
||||||
clientY = touchObject.clientY;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const on_pointer_move = ({ clientX, clientY }: PointerEvent) => {
|
||||||
cur_in = inside_ellipse(
|
cur_in = inside_ellipse(
|
||||||
clientX - x,
|
clientX - x,
|
||||||
clientY - y,
|
clientY - y,
|
||||||
@ -396,12 +358,12 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
trial.sort_area_height * 0.5,
|
trial.sort_area_height * 0.5,
|
||||||
trial.sort_area_shape == "square"
|
trial.sort_area_shape == "square"
|
||||||
);
|
);
|
||||||
elem.style.top =
|
this.style.top =
|
||||||
Math.min(
|
Math.min(
|
||||||
trial.sort_area_height - trial.stim_height * 0.5,
|
trial.sort_area_height - trial.stim_height * 0.5,
|
||||||
Math.max(-trial.stim_height * 0.5, clientY - y)
|
Math.max(-trial.stim_height * 0.5, clientY - y)
|
||||||
) + "px";
|
) + "px";
|
||||||
elem.style.left =
|
this.style.left =
|
||||||
Math.min(
|
Math.min(
|
||||||
trial.sort_area_width * 1.5 - trial.stim_width,
|
trial.sort_area_width * 1.5 - trial.stim_width,
|
||||||
Math.max(-trial.sort_area_width * 0.5, clientX - x)
|
Math.max(-trial.sort_area_width * 0.5, clientX - x)
|
||||||
@ -419,7 +381,7 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// replace in overall array, grab index from item id
|
// replace in overall array, grab index from item id
|
||||||
var elem_number = parseInt(elem.id.split("jspsych-free-sort-draggable-")[1], 10);
|
var elem_number = parseInt(this.id.split("jspsych-free-sort-draggable-")[1], 10);
|
||||||
inside.splice(elem_number, 1, cur_in);
|
inside.splice(elem_number, 1, cur_in);
|
||||||
|
|
||||||
// modify text and background if all items are inside
|
// modify text and background if all items are inside
|
||||||
@ -437,11 +399,11 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
get_counter_text(inside.length - inside.filter(Boolean).length);
|
get_counter_text(inside.length - inside.filter(Boolean).length);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener(move_event_name, move_event);
|
document.addEventListener("pointermove", on_pointer_move);
|
||||||
|
|
||||||
var end_event = (e) => {
|
const on_pointer_up = (e) => {
|
||||||
document.removeEventListener(move_event_name, move_event);
|
document.removeEventListener("pointermove", on_pointer_move);
|
||||||
elem.style.transform = "scale(1, 1)";
|
this.style.transform = "scale(1, 1)";
|
||||||
if (trial.change_border_background_color) {
|
if (trial.change_border_background_color) {
|
||||||
if (inside.every(Boolean)) {
|
if (inside.every(Boolean)) {
|
||||||
border.style.background = trial.border_color_in;
|
border.style.background = trial.border_color_in;
|
||||||
@ -452,13 +414,13 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
moves.push({
|
moves.push({
|
||||||
src: elem.dataset.src,
|
src: this.dataset.src,
|
||||||
x: elem.offsetLeft,
|
x: this.offsetLeft,
|
||||||
y: elem.offsetTop,
|
y: this.offsetTop,
|
||||||
});
|
});
|
||||||
document.removeEventListener(end_event_name, end_event);
|
document.removeEventListener("pointerup", on_pointer_up);
|
||||||
};
|
};
|
||||||
document.addEventListener(end_event_name, end_event);
|
document.addEventListener("pointerup", on_pointer_up);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,56 +469,6 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
|
|||||||
}
|
}
|
||||||
return text_out;
|
return text_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper functions
|
|
||||||
function shuffle(array) {
|
|
||||||
// define three variables
|
|
||||||
let cur_idx = array.length,
|
|
||||||
tmp_val,
|
|
||||||
rand_idx;
|
|
||||||
|
|
||||||
// While there remain elements to shuffle...
|
|
||||||
while (0 !== cur_idx) {
|
|
||||||
// Pick a remaining element...
|
|
||||||
rand_idx = Math.floor(Math.random() * cur_idx);
|
|
||||||
cur_idx -= 1;
|
|
||||||
|
|
||||||
// And swap it with the current element.
|
|
||||||
tmp_val = array[cur_idx];
|
|
||||||
array[cur_idx] = array[rand_idx];
|
|
||||||
array[rand_idx] = tmp_val;
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
function make_arr(startValue, stopValue, cardinality) {
|
|
||||||
const step = (stopValue - startValue) / (cardinality - 1);
|
|
||||||
let arr = [];
|
|
||||||
for (let i = 0; i < cardinality; i++) {
|
|
||||||
arr.push(startValue + step * i);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function inside_ellipse(x, y, x0, y0, rx, ry, square = false) {
|
|
||||||
const results = [];
|
|
||||||
if (square) {
|
|
||||||
return Math.abs(x - x0) <= rx && Math.abs(y - y0) <= ry;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
(x - x0) * (x - x0) * (ry * ry) + (y - y0) * (y - y0) * (rx * rx) <= rx * rx * (ry * ry)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function random_coordinate(max_width, max_height) {
|
|
||||||
const rnd_x = Math.floor(Math.random() * (max_width - 1));
|
|
||||||
const rnd_y = Math.floor(Math.random() * (max_height - 1));
|
|
||||||
return {
|
|
||||||
x: rnd_x,
|
|
||||||
y: rnd_y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
46
packages/plugin-free-sort/src/utils.ts
Normal file
46
packages/plugin-free-sort/src/utils.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export function shuffle(array) {
|
||||||
|
// define three variables
|
||||||
|
let cur_idx = array.length,
|
||||||
|
tmp_val,
|
||||||
|
rand_idx;
|
||||||
|
|
||||||
|
// While there remain elements to shuffle...
|
||||||
|
while (0 !== cur_idx) {
|
||||||
|
// Pick a remaining element...
|
||||||
|
rand_idx = Math.floor(Math.random() * cur_idx);
|
||||||
|
cur_idx -= 1;
|
||||||
|
|
||||||
|
// And swap it with the current element.
|
||||||
|
tmp_val = array[cur_idx];
|
||||||
|
array[cur_idx] = array[rand_idx];
|
||||||
|
array[rand_idx] = tmp_val;
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function make_arr(startValue, stopValue, cardinality) {
|
||||||
|
const step = (stopValue - startValue) / (cardinality - 1);
|
||||||
|
let arr = [];
|
||||||
|
for (let i = 0; i < cardinality; i++) {
|
||||||
|
arr.push(startValue + step * i);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inside_ellipse(x, y, x0, y0, rx, ry, square = false) {
|
||||||
|
const results = [];
|
||||||
|
if (square) {
|
||||||
|
return Math.abs(x - x0) <= rx && Math.abs(y - y0) <= ry;
|
||||||
|
} else {
|
||||||
|
return (x - x0) * (x - x0) * (ry * ry) + (y - y0) * (y - y0) * (rx * rx) <= rx * rx * (ry * ry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function random_coordinate(max_width, max_height) {
|
||||||
|
const rnd_x = Math.floor(Math.random() * (max_width - 1));
|
||||||
|
const rnd_y = Math.floor(Math.random() * (max_height - 1));
|
||||||
|
return {
|
||||||
|
x: rnd_x,
|
||||||
|
y: rnd_y,
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user