diff --git a/examples/jspsych-virtual-chinrest.html b/examples/jspsych-virtual-chinrest.html
index 6c13c9a1..46fe05df 100644
--- a/examples/jspsych-virtual-chinrest.html
+++ b/examples/jspsych-virtual-chinrest.html
@@ -5,11 +5,11 @@
-
-
+
-
+
@@ -25,7 +25,7 @@
// note: pixels_per_unit will be ignored
let no_resize = {
type: 'virtual-chinrest',
- blindspot_reps: 3,
+ blindspot_reps: 1,
resize_units: "none",
pixels_per_unit: 50
};
@@ -45,7 +45,7 @@
// don't report viewing distance to subject
let deg_resize = {
type: 'virtual-chinrest',
- blindspot_reps: 3,
+ blindspot_reps: 1,
resize_units: "deg",
pixels_per_unit: 50,
// viewing_distance_report: 'none'
diff --git a/plugins/jspsych-virtual-chinrest.js b/plugins/jspsych-virtual-chinrest.js
index ed568379..3b0159ce 100644
--- a/plugins/jspsych-virtual-chinrest.js
+++ b/plugins/jspsych-virtual-chinrest.js
@@ -32,20 +32,18 @@ jsPsych.plugins["virtual-chinrest"] = (function () {
// },
adjustment_prompt: {
type: jsPsych.plugins.parameterType.STRING,
- default:
- " Let’s find out how big your monitor is! " +
- "
Please use any credit card that you have available. " +
- "It can also be a grocery store membership card, " +
- "your drivers license or anything else of the same format. " +
- "Place your card flat onto the screen, and adjust the image below to match its size.
" +
- "If you do not have access to a real card " +
- "you can use a ruler to measure the image width to 3.37 inches or 85.6 mm. ",
+ default: `
+
+
Click and drag the lower right corner of the image until it is the same size as a credit card held up to the screen.
+
You can use any card that is the same size as a credit card, like a membership card or driver's license.
+
If you do not have access to a real card you can use a ruler to measure the image width to 3.37 inches or 85.6 mm.
+
`,
description:
- " Any content here will be displayed above the card stimulus.",
+ "Any content here will be displayed above the card stimulus.",
},
adjustment_button_prompt: {
type: jsPsych.plugins.parameterType.STRING,
- default: "Click here when the card has the right size!",
+ default: "Click here when the image is the correct size",
description:
" Content of the button displayed below the card stimulus.",
},
@@ -54,13 +52,13 @@ jsPsych.plugins["virtual-chinrest"] = (function () {
default: "img/card.png",
},
item_height_mm: {
- type: jsPsych.plugins.parameterType.INT,
+ type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: "Item height",
default: 53.98,
description: "The height of the item to be measured.",
},
item_width_mm: {
- type: jsPsych.plugins.parameterType.INT,
+ type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: "Item width",
default: 85.6,
description: "The width of the item to be measured.",
@@ -81,30 +79,25 @@ jsPsych.plugins["virtual-chinrest"] = (function () {
},
blindspot_prompt: {
type: jsPsych.plugins.parameterType.STRING,
- default:
- "Now, let’s quickly test how far away you are sitting. " +
- "You might know that vision tests at a doctor’s practice often involve chinrests. " +
- "The doctor basically asks you to sit away from a screen in a specific distance. " +
- "We do this here with a “virtual chinrest”.
" +
- "Instructions " +
- '' +
- "
Put your finger on space bar on the keyboard. " +
- "Close your right eye. (Tips: it might be easier to cover your right eye by hand!) " +
- "Using your left eye, focus on the black square. " +
- 'Click the button below to start the animation of the red ball. The red ball ' +
- "will disappear as it moves from right to left. Press the “Space” key as soon as the ball disappears from your eye sight. " +
- " Keep your right eye closed and hit the “Space” key fast! ",
+ default: `
+ Now we will quickly measure how far away you are sitting.
+
+
+ Put your left hand on the space bar .
+ Cover your right eye with your right hand.
+ Using your left eye, focus on the black square. Keep your focus on the black square.
+ The red ball will disappear as it moves from right to left. Press the space bar as soon as the ball disappears.
+
+
+ Press the space bar when you are ready to begin.
+ `
},
blindspot_start_prompt: {
type: jsPsych.plugins.parameterType.STRING,
default: "Start",
description: "Content of the start button for the blindspot tasks.",
},
- blindspot_done_prompt: {
- type: jsPsych.plugins.parameterType.STRING,
- default: "Done",
- description: "Content of the done button for the blindspot tasks.",
- },
+
blindspot_measurements_prompt: {
type: jsPsych.plugins.parameterType.STRING,
default: "Remaining measurements: ",
@@ -112,341 +105,309 @@ jsPsych.plugins["virtual-chinrest"] = (function () {
},
viewing_distance_report: {
type: jsPsych.plugins.parameterType.STRING,
- default: "Estimated viewing distance (cm):",
+ default: "Based on your responses, you are sitting about from the screen
Does that seem about right?
",
description:
'If "none" is given, viewing distance will not be reported to the participant',
},
+ redo_measurement_button_label: {
+ type: jsPsych.plugins.parameterType.STRING,
+ default: 'No, that is not close. Try again.'
+ },
+ blindspot_done_prompt: {
+ type: jsPsych.plugins.parameterType.STRING,
+ default: "Yes",
+ description: "Text for final prompt",
+ },
},
};
- // Get screen size
- var w = window.innerWidth;
- var h = window.innerHeight;
-
- const screen_size_px = [];
- screen_size_px.push(w);
- screen_size_px.push("x");
- screen_size_px.push(h);
-
- let trial_data = {}; // declare trial data as empty so we have access to it across functions
-
- let config_data = {
- ball_pos: [],
- slider_clck: false,
- };
-
plugin.trial = function (display_element, trial) {
- try {
- if (
- !(trial.blindspot_reps > 0) &&
- (trial.resize_units == "deg" || trial.resize_units == "degrees")
- ) {
- throw Error(
- "Blindspot repetitions set to 0, so resizing to degrees of visual angle is not possible!"
- );
- } else {
- const start_time = performance.now();
-
- trial_data = {
- item_width_mm: trial.item_width_mm,
- item_height_mm: trial.item_height_mm, //card dimension: 85.60 × 53.98 mm (3.370 × 2.125 in)
- };
-
- let button_str = '';
- if (!(trial.blindspot_reps > 0)) {
- button_str = '';
- }
-
- let report_str = "";
- if (!(trial.viewing_distance_report == "none")) {
- report_str =
- '' +
- '' +
- trial.viewing_distance_report +
- " " +
- "
";
- }
-
- let pagesize_content =
- '' + trial.adjustment_prompt;
-
- // variables to determine div size
- let aspect_ratio = 1;
-
- // if (trial.mouse_adjustment) {
- aspect_ratio = trial.item_width_mm / trial.item_height_mm;
- const start_div_height =
- aspect_ratio < 1
- ? trial.item_init_size
- : Math.round(trial.item_init_size / aspect_ratio);
- const start_div_width =
- aspect_ratio < 1
- ? Math.round(trial.item_init_size * aspect_ratio)
- : trial.item_init_size;
- const adjust_size = Math.round(start_div_width * 0.1);
- pagesize_content +=
- "
" +
- button_str +
- trial.adjustment_button_prompt +
- "
" +
- '
" +
- "
";
- // } else {
- // pagesize_content +=
- // '' +
- // '
' +
- // button_str +
- // trial.adjustment_button_prompt +
- // "
" +
- // '
' +
- // "
" +
- // "";
- // }
-
- const blindspot_content =
- '' +
- trial.blindspot_prompt +
- '
' +
- '
' +
- trial.blindspot_start_prompt +
- " " +
- '
' +
- trial.blindspot_done_prompt +
- " " +
- `
${trial.blindspot_measurements_prompt} ${trial.blindspot_reps}
` +
- report_str +
- " ";
-
- display_element.innerHTML =
- '' +
- pagesize_content +
- blindspot_content +
- "
";
-
- // Event listeners for mouse-based resize
- // if (trial.mouse_adjustment) {
- let dragging = false;
- let origin_x, origin_y;
- let cx, cy;
-
- const mouseupevent = function (e) {
- dragging = false;
- };
- display_element.addEventListener("mouseup", mouseupevent);
-
- const mousedownevent = function (e) {
- e.preventDefault();
- dragging = true;
- origin_x = e.pageX;
- origin_y = e.pageY;
- cx = parseInt(scale_div.style.width);
- cy = parseInt(scale_div.style.height);
- };
- display_element
- .querySelector("#jspsych-resize-handle")
- .addEventListener("mousedown", mousedownevent);
-
- const scale_div = display_element.querySelector("#item");
-
- function resizeevent(e) {
- if (dragging) {
- let dx = e.pageX - origin_x;
- let dy = e.pageY - origin_y;
-
- if (Math.abs(dx) >= Math.abs(dy)) {
- scale_div.style.width =
- Math.round(Math.max(20, cx + dx * 2)) + "px";
- scale_div.style.height =
- Math.round(Math.max(20, cx + dx * 2) / aspect_ratio) + "px";
- } else {
- scale_div.style.height =
- Math.round(Math.max(20, cy + dy * 2)) + "px";
- scale_div.style.width =
- Math.round(aspect_ratio * Math.max(20, cy + dy * 2)) + "px";
- }
- }
- }
-
- display_element.addEventListener("mousemove", resizeevent);
- // }
-
- //Event listeners for buttons
- if (trial.blindspot_reps > 0) {
- display_element
- .querySelector("#blind_spot")
- .addEventListener("click", function () {
- configureBlindSpot();
- });
- display_element
- .querySelector("#start_ball")
- .addEventListener("click", function () {
- animateBall();
- });
- } else {
- // run the two relevant functions to get item_width_mm and px2mm
- distanceSetup.px2mm(get_item_width());
- }
-
- display_element
- .querySelector("#proceed")
- .addEventListener("click", function () {
- // finish trial
- trial_data.rt = performance.now() - start_time;
- display_element.innerHTML = "";
-
- trial_data.item_width_deg =
- (2 *
- Math.atan(
- trial_data["item_width_mm"] / 2 / trial_data["view_dist_mm"]
- ) *
- 180) /
- Math.PI;
- trial_data.px2deg =
- trial_data["item_width_px"] / trial_data.item_width_deg; // size of item in pixels divided by size of item in degrees of visual angle
-
- let px2unit_scr = 0;
- switch (trial.resize_units) {
- case "cm":
- case "centimeters":
- px2unit_scr = trial_data["px2mm"] * 10; // pixels per centimeter
- break;
- case "inch":
- case "inches":
- px2unit_scr = trial_data["px2mm"] * 25.4; // pixels per inch
- break;
- case "deg":
- case "degrees":
- px2unit_scr = trial_data["px2deg"]; // pixels per degree of visual angle
- break;
- }
- if (px2unit_scr > 0) {
- // scale the window
- scale_factor = px2unit_scr / trial.pixels_per_unit;
- document.getElementById("jspsych-content").style.transform =
- "scale(" + scale_factor + ")";
- // pixels have been scaled, so pixels per degree, pixels per mm and pixels per item_width needs to be updated
- trial_data.px2deg = trial_data.px2deg / scale_factor;
- trial_data.px2mm = trial_data.px2mm / scale_factor;
- trial_data.item_width_px =
- trial_data.item_width_px / scale_factor;
- trial_data.scale_factor = scale_factor;
- }
-
- if (trial.blindspot_reps > 0) {
- trial_data.win_width_deg = window.innerWidth / trial_data.px2deg;
- trial_data.win_height_deg =
- window.innerHeight / trial_data.px2deg;
- } else {
- // delete degree related properties
- delete trial_data.px2deg;
- delete trial_data.item_width_deg;
- }
- jsPsych.finishTrial(trial_data);
- jsPsych.pluginAPI.cancelAllKeyboardResponses();
- });
- }
- } catch (e) {
- console.error(e);
+ /* check parameter compatibility */
+ if (!(trial.blindspot_reps > 0) && (trial.resize_units == "deg" || trial.resize_units == "degrees")) {
+ console.error("Blindspot repetitions set to 0, so resizing to degrees of visual angle is not possible!");
}
- };
- var distanceSetup = (function () {
- //The Module Pattern: http://benalman.com/news/2010/11/immediately-invoked-function-expression/
- var i = 0;
+ /* some additional parameter configuration */
+ var w = window.innerWidth;
+ var h = window.innerHeight;
- return {
- round: function (value, decimals) {
- return Number(Math.round(value + "e" + decimals) + "e-" + decimals);
- },
- px2mm: function (item_width_px) {
- const px2mm = item_width_px / trial_data["item_width_mm"];
- trial_data["px2mm"] = distanceSetup.round(px2mm, 2);
- return px2mm;
- },
+ const screen_size_px = [w, "x", h];
+
+ let trial_data = {
+ item_width_mm: trial.item_width_mm,
+ item_height_mm: trial.item_height_mm, //card dimension: 85.60 × 53.98 mm (3.370 × 2.125 in)
};
- })((window.distanceSetup = window.distanceSetup || {}));
- function get_item_width() {
- const item_width_px = parseFloat(
- getComputedStyle(document.querySelector("#item"), null).width.replace(
- "px",
- ""
- )
- );
+ let config_data = {
+ ball_pos: [],
+ slider_clck: false,
+ };
- trial_data["item_width_px"] = distanceSetup.round(item_width_px, 2);
- return item_width_px;
- }
+ let aspect_ratio = 1;
- function configureBlindSpot() {
- drawBall();
- document.querySelector("#page-size").remove();
- document.getElementById("blind-spot").style.visibility = "visible";
- document.addEventListener("keydown", recordPosition);
- }
+ aspect_ratio = trial.item_width_mm / trial.item_height_mm;
+ const start_div_height =
+ aspect_ratio < 1
+ ? trial.item_init_size
+ : Math.round(trial.item_init_size / aspect_ratio);
+ const start_div_width =
+ aspect_ratio < 1
+ ? Math.round(trial.item_init_size * aspect_ratio)
+ : trial.item_init_size;
+ const adjust_size = Math.round(start_div_width * 0.1);
- //Ball Animation
+ /* create content for first screen, resizing card */
+ let pagesize_content = `
+
+
+ ${trial.adjustment_prompt}
+
+ ${trial.adjustment_button_prompt}
+
+
+ `
- function drawBall(pos = 180) {
- // pos: define where the fixation square should be.
- var mySVG = SVG("svgDiv");
- const item_width_px = get_item_width();
- const rectX = distanceSetup.px2mm(item_width_px) * pos;
- const ballX = rectX * 0.6; // define where the ball is
- var ball = mySVG.circle(30).move(ballX, 50).fill("#f00");
- window.ball = ball;
- var square = mySVG.rect(30, 30).move(Math.min(rectX - 50, 950), 50); //square position
- config_data["square_pos"] = distanceSetup.round(square.cx(), 2);
- config_data["rectX"] = rectX;
- config_data["ballX"] = ballX;
- }
+ /* create content for second screen, blind spot */
+ let blindspot_content = `
+
+ ${trial.blindspot_prompt}
+
+
+
+ ${trial.blindspot_done_prompt} +
+
+ ${trial.blindspot_measurements_prompt}
+
${trial.blindspot_reps}
+ ${trial.viewing_distance_report !== "none" ?
+ `
+
+ ${trial.viewing_distance_report}
+
+
` : ''
+ }
+
`
- function animateBall() {
- ball
- .animate(7000)
- .during(function (pos) {
- moveX = -pos * config_data["ballX"];
- window.moveX = moveX;
- moveY = 0;
- ball.attr({ transform: "translate(" + moveX + "," + moveY + ")" }); //jqueryToVanilla: el.getAttribute('');
+ /* create content for final report screen */
+ let report_content = `
+
+
+ ${trial.viewing_distance_report}
+
+
${trial.redo_measurement_button_label}
+
${trial.blindspot_done_prompt}
+
+ `
+
+
+ /* show first screen */
+ display_element.innerHTML = `
+
+ ${pagesize_content}
+
+ `
+
+ const start_time = performance.now();
+
+ // Event listeners for mouse-based resize
+ let dragging = false;
+ let origin_x, origin_y;
+ let cx, cy;
+ const scale_div = display_element.querySelector("#item");
+
+ function mouseupevent() {
+ dragging = false;
+ };
+ document.addEventListener("mouseup", mouseupevent);
+
+ function mousedownevent(e) {
+ e.preventDefault();
+ dragging = true;
+ origin_x = e.pageX;
+ origin_y = e.pageY;
+ cx = parseInt(scale_div.style.width);
+ cy = parseInt(scale_div.style.height);
+ };
+ display_element.querySelector("#jspsych-resize-handle").addEventListener("mousedown", mousedownevent);
+
+ function resizeevent(e) {
+ if (dragging) {
+ let dx = e.pageX - origin_x;
+ let dy = e.pageY - origin_y;
+
+ if (Math.abs(dx) >= Math.abs(dy)) {
+ scale_div.style.width =
+ Math.round(Math.max(20, cx + dx * 2)) + "px";
+ scale_div.style.height =
+ Math.round(Math.max(20, cx + dx * 2) / aspect_ratio) + "px";
+ } else {
+ scale_div.style.height =
+ Math.round(Math.max(20, cy + dy * 2)) + "px";
+ scale_div.style.width =
+ Math.round(aspect_ratio * Math.max(20, cy + dy * 2)) + "px";
+ }
+ }
+ }
+ display_element.addEventListener("mousemove", resizeevent);
+
+ display_element.querySelector("#end_resize_phase").addEventListener("click", finishResizePhase);
+
+ function finishResizePhase(){
+ // check what to do next
+ if (trial.blindspot_reps > 0) {
+ get_item_width(); // modifies trial data
+ configureBlindSpot();
+ } else {
+ distanceSetup.px2mm(get_item_width());
+ endTrial();
+ }
+ }
+
+ function configureBlindSpot() {
+ document.querySelector("#content").innerHTML = blindspot_content;
+ drawBall();
+ jsPsych.pluginAPI.getKeyboardResponse({
+ callback_function: startBall,
+ valid_responses: ['space'],
+ rt_method: 'performance',
+ allow_held_keys: false,
+ persist: false
})
- .loop(true, false)
- .after(function () {
- animateBall();
+ }
+
+ var ball_position_listener = null;
+ function startBall(){
+ ball_position_listener = jsPsych.pluginAPI.getKeyboardResponse({
+ callback_function: recordPosition,
+ valid_responses: ['space'],
+ rt_method: 'performance',
+ allow_held_keys: false,
+ persist: true
});
+ animateBall();
+ }
- //disable the button after clicked once.
- var start_ball_handle = document.querySelector("#start_ball");
- start_ball_handle.setAttribute("disabled", true);
- document.querySelector("#start_ball").style.display = "none";
- }
+ function finishBlindSpotPhase(){
+ ball.stop();
- function recordPosition(event, angle = 13.5) {
- // angle: define horizontal blind spot entry point position in degrees.
- if (event.keyCode == "32") {
- //Press "Space"
+ jsPsych.pluginAPI.cancelAllKeyboardResponses();
+ showReport();
+ }
+
+ function showReport(){
+ // Display data
+ display_element.querySelector("#content").innerHTML = report_content;
+ display_element.querySelector('#distance-estimate').innerHTML = `${Math.round(trial_data["view_dist_mm"] / 10)} cm`
+
+ display_element.querySelector("#redo_blindspot").addEventListener('click', configureBlindSpot)
+ display_element.querySelector("#proceed").addEventListener('click', endTrial);
+ }
+
+ function endTrial() {
+ // finish trial
+ trial_data.rt = performance.now() - start_time;
+ display_element.innerHTML = "";
+
+ trial_data.item_width_deg =
+ (2 *
+ Math.atan(
+ trial_data["item_width_mm"] / 2 / trial_data["view_dist_mm"]
+ ) *
+ 180) /
+ Math.PI;
+ trial_data.px2deg =
+ trial_data["item_width_px"] / trial_data.item_width_deg; // size of item in pixels divided by size of item in degrees of visual angle
+
+ let px2unit_scr = 0;
+ switch (trial.resize_units) {
+ case "cm":
+ case "centimeters":
+ px2unit_scr = trial_data["px2mm"] * 10; // pixels per centimeter
+ break;
+ case "inch":
+ case "inches":
+ px2unit_scr = trial_data["px2mm"] * 25.4; // pixels per inch
+ break;
+ case "deg":
+ case "degrees":
+ px2unit_scr = trial_data["px2deg"]; // pixels per degree of visual angle
+ break;
+ }
+ if (px2unit_scr > 0) {
+ // scale the window
+ scale_factor = px2unit_scr / trial.pixels_per_unit;
+ document.getElementById("jspsych-content").style.transform =
+ "scale(" + scale_factor + ")";
+ // pixels have been scaled, so pixels per degree, pixels per mm and pixels per item_width needs to be updated
+ trial_data.px2deg = trial_data.px2deg / scale_factor;
+ trial_data.px2mm = trial_data.px2mm / scale_factor;
+ trial_data.item_width_px =
+ trial_data.item_width_px / scale_factor;
+ trial_data.scale_factor = scale_factor;
+ }
+
+ if (trial.blindspot_reps > 0) {
+ trial_data.win_width_deg = window.innerWidth / trial_data.px2deg;
+ trial_data.win_height_deg =
+ window.innerHeight / trial_data.px2deg;
+ } else {
+ // delete degree related properties
+ delete trial_data.px2deg;
+ delete trial_data.item_width_deg;
+ }
+ // NEED TO REMOVE EVENT LISTENERS
+
+ jsPsych.finishTrial(trial_data);
+ jsPsych.pluginAPI.cancelAllKeyboardResponses();
+ }
+
+ function get_item_width() {
+ const item_width_px = parseFloat(
+ getComputedStyle(document.querySelector("#item"), null).width.replace(
+ "px",
+ ""
+ )
+ );
+
+ trial_data["item_width_px"] = distanceSetup.round(item_width_px, 2);
+ return item_width_px;
+ }
+
+ function drawBall(pos = 180) {
+ // pos: define where the fixation square should be.
+ var mySVG = SVG("svgDiv");
+ const item_width_px = trial_data["item_width_px"];
+ const rectX = distanceSetup.px2mm(item_width_px) * pos;
+ const ballX = rectX * 0.6; // define where the ball is
+ var ball = mySVG.circle(30).move(ballX, 50).fill("#f00");
+ window.ball = ball;
+ var square = mySVG.rect(30, 30).move(Math.min(rectX - 50, 950), 50); //square position
+ config_data["square_pos"] = distanceSetup.round(square.cx(), 2);
+ config_data["rectX"] = rectX;
+ config_data["ballX"] = ballX;
+ }
+
+ function animateBall() {
+ ball
+ .animate(7000)
+ .during(function (pos) {
+ moveX = -pos * config_data["ballX"];
+ window.moveX = moveX;
+ moveY = 0;
+ ball.attr({ transform: "translate(" + moveX + "," + moveY + ")" }); //jqueryToVanilla: el.getAttribute('');
+ })
+ .loop(true, false)
+ .after(function () {
+ animateBall();
+ });
+ }
+
+ function recordPosition(info) {
+ // angle: define horizontal blind spot entry point position in degrees.
+ const angle = 13.5;
+
config_data["ball_pos"].push(distanceSetup.round(ball.cx() + moveX, 2));
var sum = config_data["ball_pos"].reduce((a, b) => a + b, 0);
var ballPosLen = config_data["ball_pos"].length;
@@ -462,30 +423,28 @@ jsPsych.plugins["virtual-chinrest"] = (function () {
counter = counter - 1;
document.querySelector("#click").textContent = Math.max(counter, 0);
if (counter <= 0) {
- ball.stop();
-
- // Disable space key
- document.addEventListener("keydown", function (e) {
- if (e.key == 32) {
- return false;
- }
- });
-
- // Display data
- info = document.querySelector("#info");
- document.querySelector("#info").style.visibility = "visible";
- var info_h = document.createElement("div");
- info_h.innerHTML = trial_data["view_dist_mm"] / 10;
- info.appendChild(info_h);
- document.querySelector("#proceed").style.display = "inline";
-
+ finishBlindSpotPhase();
return;
+ } else {
+ ball.stop();
+ animateBall();
}
-
- ball.stop();
- animateBall();
+
}
- }
+
+ var distanceSetup = {
+ round: function (value, decimals) {
+ return Number(Math.round(value + "e" + decimals) + "e-" + decimals);
+ },
+ px2mm: function (item_width_px) {
+ const px2mm = item_width_px / trial_data["item_width_mm"];
+ trial_data["px2mm"] = distanceSetup.round(px2mm, 2);
+ return px2mm;
+ }
+ }
+
+
+ };
//helper function for radians
// Converts from degrees to radians.