console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>点探测任务(手机触摸版)</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; -webkit-user-select:none; user-select:none; }
html,body { height:100%; }
body { font-family:"Microsoft YaHei",sans-serif; background:#f0f0f0; }
#screen { width:100%; height:100%; display:flex; flex-direction:column;
align-items:center; justify-content:center; font-size:18px; position:relative; }
.card { width:140px; height:200px; display:flex; align-items:center; justify-content:center;
font-size:22px; font-weight:bold; color:#fff; border-radius:10px; }
.fix { font-size:60px; font-weight:bold; color:#333; }
.probe{ font-size:70px; font-weight:bold; color:#e74c3c; }
.ins { text-align:left; line-height:1.8; padding:20px; max-width:480px; }
.ins h1 { text-align:center; margin-bottom:16px; }
.row { display:flex; justify-content:center; gap:40px; align-items:center; width:100%; }
/* 反应按钮区 */
#btns { position:fixed; left:0; right:0; bottom:0; height:22%; display:flex; z-index:10; }
.btn { flex:1; display:flex; align-items:center; justify-content:center;
font-size:28px; font-weight:bold; color:#fff; }
.btnL { background:#3498db; border-right:3px solid #fff; }
.btnR { background:#e67e22; }
.btn:active{ filter:brightness(0.8); }
/* 指导语内的开始按钮 */
.start-btn { display:block; margin:24px auto 0; width:80%; padding:16px;
background:#2ecc71; color:#fff; font-size:22px; font-weight:bold;
border:none; border-radius:10px; }
</style>
</head>
<body>
<div id="screen"></div>
<!-- 反应按钮:固定底部,点探测时显示 -->
<div id="btns" style="display:none;">
<div class="btn btnL" id="btnL">● 左</div>
<div class="btn btnR" id="btnR">右 ●</div>
</div>
<script>
var COLORS={fat:'#e67e22',thin:'#3498db',neutral:'#95a5a6'};
var LABELS={fat:'胖体型',thin:'瘦体型',neutral:'中性图'};
var FIX_MS=500, STIM_MS=500, ITI_MS=400;
var PRACTICE_N=10, FORMAL_N=120;
var scr=document.getElementById('screen');
var btns=document.getElementById('btns');
var btnL=document.getElementById('btnL'), btnR=document.getElementById('btnR');
var records=[];
var respCb=null;
function sleep(ms){ return new Promise(function(r){ setTimeout(r,ms); }); }
function showHTML(h){ scr.innerHTML=h; }
function htmlFix(){ return '<div class="fix">+</div>'; }
function htmlStim(tr){
return '<div class="row">'+
'<div class="card" style="background:'+COLORS[tr.left]+'">'+LABELS[tr.left]+'</div>'+
'<div class="card" style="background:'+COLORS[tr.right]+'">'+LABELS[tr.right]+'</div></div>';
}
function htmlProbe(left){
return '<div class="row" style="justify-content:'+(left?'flex-start':'flex-end')+'">'+
'<div class="probe">●</div></div>';
}
// 触摸反应
function pressSide(){
return new Promise(function(resolve){
respCb=function(side){ respCb=null; resolve(side); };
btns.style.display='flex';
});
}
function hideBtns(){ btns.style.display='none'; }
btnL.addEventListener('touchstart',function(e){ e.preventDefault(); if(respCb){ respCb('left'); } },{passive:false});
btnR.addEventListener('touchstart',function(e){ e.preventDefault(); if(respCb){ respCb('right'); } },{passive:false});
// 开始按钮(指导语里动态生成,绑定事件)
function waitStart(){
return new Promise(function(resolve){
var b=document.querySelector('.start-btn');
b.addEventListener('touchstart',function(e){ e.preventDefault(); resolve(); },{passive:false});
});
}
// 生成试次
function genTrials(n){
var t=[], conds=['fat_neutral','thin_neutral','neutral_neutral','filler'];
var per=Math.floor(n/conds.length);
for(var c=0;c<conds.length;c++){
var cd=conds[c];
var bt=cd==='fat_neutral'?'fat':cd==='thin_neutral'?'thin':'neutral';
for(var i=0;i<per;i++){
var bol=Math.random()<0.5, pol=Math.random()<0.5, lt, rt;
if(cd==='neutral_neutral'||cd==='filler'){ lt='neutral'; rt='neutral'; }
else if(bol){ lt=bt; rt='neutral'; }
else { lt='neutral'; rt=bt; }
t.push({cond:cd,body_type:bt,probe_on_left:pol,left:lt,right:rt});
}
}
for(var j=t.length-1;j>0;j--){ var k=Math.floor(Math.random()*(j+1)); var tmp=t[j]; t[j]=t[k]; t[k]=tmp; }
return t;
}
var practice=genTrials(PRACTICE_N);
var formal=genTrials(FORMAL_N);
// 单个试次
async function runTrial(tr,isPractice){
showHTML(htmlFix());
await sleep(FIX_MS);
showHTML(htmlStim(tr));
await sleep(STIM_MS);
showHTML(htmlProbe(tr.probe_on_left));
var t0=performance.now();
var side=await pressSide();
var rt=performance.now()-t0;
hideBtns();
var correct=(side===(tr.probe_on_left?'left':'right'))?1:0;
if(!isPractice){
records.push({rt:Math.round(rt),correct:correct,condition:tr.cond,
body_type:tr.body_type,probe_on_left:tr.probe_on_left});
}
await sleep(ITI_MS);
}
// 主流程
async function main(){
showHTML(
'<div class="ins"><h1>点探测任务</h1>'+
'<p>每个试次流程:</p>'+
'<p>1. 中央出现注视点 <b>+</b></p>'+
'<p>2. 左右两侧同时出现两张图片</p>'+
'<p>3. 图片消失后,一侧出现红色圆点 <span style="color:#e74c3c;font-weight:bold;">●</span></p>'+
'<p>4. 又快又准判断圆点在哪一侧,<b>点击屏幕底部对应按钮</b>:</p>'+
'<p style="text-align:center;color:#3498db;font-weight:bold;">● 左(蓝色按钮)</p>'+
'<p style="text-align:center;color:#e67e22;font-weight:bold;">右 ●(橙色按钮)</p>'+
'<button class="start-btn">点我开始练习(10次)</button></div>');
await waitStart();
for(var i=0;i<practice.length;i++){ await runTrial(practice[i],true); }
showHTML('<div class="ins"><h1>练习结束</h1>'+
'<p style="text-align:center;">现在进入正式实验(120次,约15分钟)</p>'+
'<button class="start-btn">点我开始正式实验</button></div>');
await waitStart();
for(var j=0;j<formal.length;j++){ await runTrial(formal[j],false); }
var n=records.length, hit=0, rtSum=0;
for(var r=0;r<records.length;r++){ hit+=records[r].correct; rtSum+=records[r].rt; }
showHTML('<div class="ins"><h1>实验完成</h1>'+
'<p>有效试次:'+n+'</p>'+
'<p>正确率:'+(hit/n*100).toFixed(1)+'%</p>'+
'<p>平均反应时:'+(rtSum/n).toFixed(0)+'ms</p>'+
'<p style="color:#888;font-size:14px;">完整数据已输出到浏览器控制台(F12→Console)</p></div>');
console.log(JSON.stringify(records));
}
main();
</script>
</body>
</html>