*{box-sizing:border-box;margin:0;padding:0;font-size:18px;}
body{padding:16px;background:#fff;color:#000;line-height:1.6;}
h1{font-size:20px;margin-bottom:12px;}
#target{font-size:22px;margin:12px 0;padding:10px;border:1px solid #333;border-radius:6px;}
#inputBox{width:100%;padding:10px;margin:8px 0;border:1px solid #333;border-radius:6px;}
.info{margin:8px 0;}
.diff{color:#c00;font-weight:bold;text-decoration:underline;}
.same{color:#080;}
.miss{color:#c60;font-weight:bold;}
button{padding:8px 14px;margin:4px;border:1px solid #333;border-radius:6px;background:#f5f5f5;cursor:pointer;}
button:focus{outline:3px solid #06f;outline-offset:2px;}
#result{min-height:28px;font-weight:bold;}
.ok{color:#080;}
.bad{color:#c00;}
#stat{margin-top:6px;}
.tip{color:#555;font-size:16px;}
spellcheck="false" aria-label="输入练习文本" aria-describedby="result stat">
判定(回车)
下一句
跳过
重置统计
// 自然码双拼练习句子库
const sentences = [
"今天天气很好,适合练习双拼打字",
"无障碍软件,方便视障朋友使用手机",
"指尖输入法,双指操作非常顺手",
"学好双拼,打字速度会越来越快",
"读屏软件,让我们独立操作智能手机",
"AI工具,给我们生活带来很多便利",
"平时多练习,打字手感会越来越熟练",
"坚持每天练习,进步会看得见"
];
let current = "";
let right = 0;
let wrong = 0;
let answered = false; // 当前句是否已判定,避免重复计数
let startTime = 0; // 当前句开始时间
const targetDom = document.getElementById("target");
const inputDom = document.getElementById("inputBox");
const resDom = document.getElementById("result");
const statDom = document.getElementById("stat");
function escapeHtml(s){
return s.replace(/[&<>"']/g, c => ({
"&":"&","<":"<",">":">",'"':""","'":"'"
}[c]));
}
function getNewSentence(){
const idx = Math.floor(Math.random() * sentences.length);
current = sentences[idx];
targetDom.innerText = current;
inputDom.value = "";
resDom.innerText = "";
resDom.className = "";
answered = false;
startTime = Date.now();
inputDom.focus();
// 生成对比:标出第一个错字位置
function highlight(val){
let i = 0;
while(i < val.length && i < current.length && val[i] === current[i]) i++;
const same = escapeHtml(current.slice(0, i));
const diffYou = escapeHtml(val.slice(i, i + 1)) || "(缺)";
const diffOri = escapeHtml(current.slice(i, i + 1)) || "(多)";
const rest = escapeHtml(current.slice(i + 1));
return `${same}${diffYou}` +
`${rest ? " → 应为 " : ""}${diffOri}${rest}`;
function check(){
if(answered) return;
const val = inputDom.value.trim();
if(!val){
resDom.className = "bad";
resDom.innerText = "⚠ 还没有输入内容。";
return;
answered = true;
const cost = ((Date.now() - startTime) / 1000).toFixed(1);
if(val === current){
right++;
resDom.className = "ok";
resDom.innerText = `✅ 完全正确!用时 ${cost} 秒`;
updateStat();
// 正确后短暂延迟自动下一句
setTimeout(() => { if(answered) getNewSentence(); }, 900);
}else{
wrong++;
resDom.innerHTML = `❌ 第 ${firstDiff(val) + 1} 个字不对。对比:${highlight(val)}`;
function firstDiff(val){
return i;
function updateStat(){
const total = right + wrong;
const rate = total > 0 ? (right / total * 100).toFixed(1) : "0";
statDom.innerText = `累计:正确 ${right},错误 ${wrong},正确率 ${rate}%`;
// 回车判定
inputDom.addEventListener("keydown", e => {
if(e.key === "Enter"){
e.preventDefault();
if(answered){
// 已判定后再回车 = 下一句
getNewSentence();
check();
});
document.getElementById("checkBtn").onclick = check;
document.getElementById("nextBtn").onclick = getNewSentence;
document.getElementById("skipBtn").onclick = () => {
resDom.innerText = "已跳过。";
};
document.getElementById("resetBtn").onclick = () => {
right = 0; wrong = 0;
目录
*{box-sizing:border-box;margin:0;padding:0;font-size:18px;}
body{padding:16px;background:#fff;color:#000;line-height:1.6;}
h1{font-size:20px;margin-bottom:12px;}
#target{font-size:22px;margin:12px 0;padding:10px;border:1px solid #333;border-radius:6px;}
#inputBox{width:100%;padding:10px;margin:8px 0;border:1px solid #333;border-radius:6px;}
.info{margin:8px 0;}
.diff{color:#c00;font-weight:bold;text-decoration:underline;}
.same{color:#080;}
.miss{color:#c60;font-weight:bold;}
button{padding:8px 14px;margin:4px;border:1px solid #333;border-radius:6px;background:#f5f5f5;cursor:pointer;}
button:focus{outline:3px solid #06f;outline-offset:2px;}
#result{min-height:28px;font-weight:bold;}
.ok{color:#080;}
.bad{color:#c00;}
#stat{margin-top:6px;}
.tip{color:#555;font-size:16px;}
双拼打字练习
spellcheck="false" aria-label="输入练习文本" aria-describedby="result stat">
// 自然码双拼练习句子库
const sentences = [
"今天天气很好,适合练习双拼打字",
"无障碍软件,方便视障朋友使用手机",
"指尖输入法,双指操作非常顺手",
"学好双拼,打字速度会越来越快",
"读屏软件,让我们独立操作智能手机",
"AI工具,给我们生活带来很多便利",
"平时多练习,打字手感会越来越熟练",
"坚持每天练习,进步会看得见"
];
let current = "";
let right = 0;
let wrong = 0;
let answered = false; // 当前句是否已判定,避免重复计数
let startTime = 0; // 当前句开始时间
const targetDom = document.getElementById("target");
const inputDom = document.getElementById("inputBox");
const resDom = document.getElementById("result");
const statDom = document.getElementById("stat");
function escapeHtml(s){
return s.replace(/[&<>"']/g, c => ({
"&":"&","<":"<",">":">",'"':""","'":"'"
}[c]));
}
function getNewSentence(){
const idx = Math.floor(Math.random() * sentences.length);
current = sentences[idx];
targetDom.innerText = current;
inputDom.value = "";
resDom.innerText = "";
resDom.className = "";
answered = false;
startTime = Date.now();
inputDom.focus();
}
// 生成对比:标出第一个错字位置
function highlight(val){
let i = 0;
while(i < val.length && i < current.length && val[i] === current[i]) i++;
const same = escapeHtml(current.slice(0, i));
const diffYou = escapeHtml(val.slice(i, i + 1)) || "(缺)";
const diffOri = escapeHtml(current.slice(i, i + 1)) || "(多)";
const rest = escapeHtml(current.slice(i + 1));
return `${same}${diffYou}` +
`${rest ? " → 应为 " : ""}${diffOri}${rest}`;
}
function check(){
if(answered) return;
const val = inputDom.value.trim();
if(!val){
resDom.className = "bad";
resDom.innerText = "⚠ 还没有输入内容。";
return;
}
answered = true;
const cost = ((Date.now() - startTime) / 1000).toFixed(1);
if(val === current){
right++;
resDom.className = "ok";
resDom.innerText = `✅ 完全正确!用时 ${cost} 秒`;
updateStat();
// 正确后短暂延迟自动下一句
setTimeout(() => { if(answered) getNewSentence(); }, 900);
}else{
wrong++;
resDom.className = "bad";
resDom.innerHTML = `❌ 第 ${firstDiff(val) + 1} 个字不对。
对比:${highlight(val)}`;
updateStat();
}
}
function firstDiff(val){
let i = 0;
while(i < val.length && i < current.length && val[i] === current[i]) i++;
return i;
}
function updateStat(){
const total = right + wrong;
const rate = total > 0 ? (right / total * 100).toFixed(1) : "0";
statDom.innerText = `累计:正确 ${right},错误 ${wrong},正确率 ${rate}%`;
}
// 回车判定
inputDom.addEventListener("keydown", e => {
if(e.key === "Enter"){
e.preventDefault();
if(answered){
// 已判定后再回车 = 下一句
getNewSentence();
}else{
check();
}
}
});
document.getElementById("checkBtn").onclick = check;
document.getElementById("nextBtn").onclick = getNewSentence;
document.getElementById("skipBtn").onclick = () => {
resDom.className = "";
resDom.innerText = "已跳过。";
answered = true;
getNewSentence();
};
document.getElementById("resetBtn").onclick = () => {
right = 0; wrong = 0;
updateStat();
getNewSentence();
};
getNewSentence();