style: 打字练习改为标准键盘提示并增加特殊字符输入说明

This commit is contained in:
guoqingshan
2026-08-02 20:53:12 +08:00
parent 835514f927
commit 23f5133a07
3 changed files with 93 additions and 31 deletions
+22 -5
View File
@@ -170,6 +170,7 @@
gap: 0.35rem; gap: 0.35rem;
} }
.kb-key { .kb-key {
flex: 0 0 auto;
min-width: 34px; min-width: 34px;
height: 40px; height: 40px;
padding: 0 0.4rem; padding: 0 0.4rem;
@@ -179,8 +180,14 @@
color: var(--text); color: var(--text);
font-size: 0.9rem; font-size: 0.9rem;
font-family: var(--font-mono); font-family: var(--font-mono);
cursor: pointer; cursor: default;
user-select: none;
display: inline-flex;
align-items: center;
justify-content: center;
} }
.kb-key.wide { min-width: 64px; }
.kb-key.altgr { min-width: 64px; }
.kb-key.space { flex: 1; max-width: 220px; } .kb-key.space { flex: 1; max-width: 220px; }
.kb-key.hint { .kb-key.hint {
border-color: var(--accent); border-color: var(--accent);
@@ -188,6 +195,13 @@
color: #fff; color: #fff;
font-weight: 700; font-weight: 700;
} }
#special-hint {
min-height: 1.6rem;
margin-top: 0.75rem;
font-size: 0.85rem;
color: var(--text-muted);
line-height: 1.5;
}
.finish-box { .finish-box {
text-align: center; text-align: center;
padding: 2rem 0; padding: 2rem 0;
@@ -205,10 +219,13 @@
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.kb-key { .kb-key {
min-width: 28px; min-width: 20px;
height: 38px; height: 34px;
font-size: 0.8rem; font-size: 0.65rem;
padding: 0 0.25rem; padding: 0 0.1rem;
}
.kb-key.wide, .kb-key.altgr {
min-width: 44px;
} }
.kb-row { gap: 0.25rem; } .kb-row { gap: 0.25rem; }
} }
+1
View File
@@ -53,6 +53,7 @@
<input id="typing-input" type="text" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder=""> <input id="typing-input" type="text" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="">
<div id="next-key"></div> <div id="next-key"></div>
<div id="keyboard"></div> <div id="keyboard"></div>
<div id="special-hint"></div>
</div> </div>
</div> </div>
+70 -26
View File
@@ -5,13 +5,62 @@ var typingStats = { correct: 0, errors: 0 };
var typed = ''; var typed = '';
var KEY_ROWS = [ var KEY_ROWS = [
['1','2','3','4','5','6','7','8','9','0'], ['1','2','3','4','5','6','7','8','9','0','-','='],
['q','w','e','r','t','y','u','i','o','p'], ['q','w','e','r','t','y','u','i','o','p','[',']','\\'],
['a','s','d','f','g','h','j','k','l','ñ'], ['a','s','d','f','g','h','j','k','l',';','\''],
['z','x','c','v','b','n','m',',','.',';'], ['Shift','z','x','c','v','b','n','m',',','.','/','Shift'],
['á','é','í','ó','ú','ü','¿','¡',':','?','!',' '] ['Ctrl','Alt','Space','AltGr','Ctrl']
]; ];
var SPECIAL_HINTS = {
'á': 'Windows 美式国际:先按 \' 再按 a;macOS:Option + e 再按 a;手机长按 a',
'é': 'Windows 美式国际:先按 \' 再按 e;macOS:Option + e 再按 e;手机长按 e',
'í': 'Windows 美式国际:先按 \' 再按 i;macOS:Option + e 再按 i;手机长按 i',
'ó': 'Windows 美式国际:先按 \' 再按 o;macOS:Option + e 再按 o;手机长按 o',
'ú': 'Windows 美式国际:先按 \' 再按 u;macOS:Option + e 再按 u;手机长按 u',
'ü': 'Windows 美式国际:先按 " 再按 u;macOS:Option + u 再按 u;手机长按 u',
'ñ': 'Windows:AltGr + n 或先按 ~ 再按 n;macOS:Option + n 再按 n;手机长按 n',
'¿': 'Windows:AltGr + ?;macOS:Option + Shift + ?;Linux:Compose ? ?',
'¡': 'Windows:AltGr + 1;macOS:Option + 1;Linux:Compose ! !',
'?': 'Shift + /',
'!': 'Shift + 1',
':': 'Shift + ;'
};
function standardKeyForChar(ch) {
if (ch === ' ') return 'Space';
var lower = ch.toLowerCase();
if (/[a-z0-9]/.test(lower)) return lower;
var map = {
'á':'a','é':'e','í':'i','ó':'o','ú':'u','ü':'u','ñ':'n',
'?':'/','¿':'/','!':'1','¡':'1',
',':',','.':'.',';':';',':':';','-':'-','=':'=',
'[':'[',']':']','\\':'\\',"'":"'",'\"':"'"
};
return map[ch] || '';
}
function renderSpecialHint(ch) {
var el = document.getElementById('special-hint');
if (!ch) {
el.textContent = '';
return;
}
if (SPECIAL_HINTS[ch]) {
el.textContent = '输入提示:' + SPECIAL_HINTS[ch];
return;
}
if (ch === ' ') {
el.textContent = '输入提示:按空格键';
return;
}
if (ch !== ch.toLowerCase() && ch === ch.toUpperCase() && /[a-z]/i.test(ch)) {
el.textContent = '输入提示:先按 Shift,再按 ' + ch.toLowerCase();
return;
}
el.textContent = '输入提示:直接按标准键盘上的 ' + (standardKeyForChar(ch) || ch) + ' 键';
}
function escTyping(s) { function escTyping(s) {
var div = document.createElement('div'); var div = document.createElement('div');
div.textContent = s == null ? '' : String(s); div.textContent = s == null ? '' : String(s);
@@ -95,9 +144,10 @@ function renderPractice() {
function renderNextKey(target) { function renderNextKey(target) {
var next = target[typed.length]; var next = target[typed.length];
var label = next === ' ' ? '空格' : next;
document.getElementById('next-key').innerHTML = typed.length >= target.length document.getElementById('next-key').innerHTML = typed.length >= target.length
? '已完成' ? '已完成'
: '下一键:<b>' + escTyping(next) + '</b>'; : '下一键:<b>' + escTyping(label) + '</b>';
} }
function renderTypingAccuracy() { function renderTypingAccuracy() {
@@ -109,18 +159,25 @@ function renderTypingAccuracy() {
function renderKeyboard(target) { function renderKeyboard(target) {
var next = target[typed.length] || ''; var next = target[typed.length] || '';
var base = standardKeyForChar(next);
var shiftNeeded = next && next !== next.toLowerCase() && next === next.toUpperCase() && /[a-z]/i.test(next);
var specialNeeded = next && /[áéíóúüñ¿¡]/.test(next);
var shiftPunctuation = next === '?' || next === '!' || next === ':';
var container = document.getElementById('keyboard'); var container = document.getElementById('keyboard');
container.innerHTML = KEY_ROWS.map(function(row) { container.innerHTML = KEY_ROWS.map(function(row) {
return '<div class="kb-row">' + row.map(function(key) { return '<div class="kb-row">' + row.map(function(key) {
var label = key === ' ' ? '␣' : key; var isHint = key.toLowerCase() === String(base || '').toLowerCase() ||
var isHint = next && ( (key === 'Shift' && (shiftNeeded || shiftPunctuation)) ||
key === next || (key === 'AltGr' && specialNeeded);
key.toLowerCase() === next.toLowerCase() || var cls = ['kb-key'];
(next === ' ' && key === ' ') if (isHint) cls.push('hint');
); if (key === 'Shift' || key === 'Ctrl' || key === 'Alt') cls.push('wide');
return '<button type="button" class="kb-key' + (isHint ? ' hint' : '') + (key === ' ' ? ' space' : '') + '" data-key="' + escTyping(key) + '">' + escTyping(label) + '</button>'; if (key === 'AltGr') cls.push('altgr');
if (key === 'Space') cls.push('space');
return '<div class="' + cls.join(' ') + '">' + escTyping(key) + '</div>';
}).join('') + '</div>'; }).join('') + '</div>';
}).join(''); }).join('');
renderSpecialHint(next);
} }
function handleTypingInput() { function handleTypingInput() {
@@ -200,18 +257,5 @@ document.querySelectorAll('.mode-select button').forEach(function(btn) {
document.getElementById('btn-start').addEventListener('click', startTyping); document.getElementById('btn-start').addEventListener('click', startTyping);
document.getElementById('btn-restart').addEventListener('click', startTyping); document.getElementById('btn-restart').addEventListener('click', startTyping);
document.getElementById('typing-input').addEventListener('input', handleTypingInput); document.getElementById('typing-input').addEventListener('input', handleTypingInput);
document.getElementById('keyboard').addEventListener('click', function(e) {
var btn = e.target.closest('.kb-key');
if (!btn) return;
var input = document.getElementById('typing-input');
var key = btn.dataset.key;
var next = typingItems[typingIndex] && typingItems[typingIndex].es[typed.length];
if (next && key.toLowerCase() === next.toLowerCase() && key !== next) {
key = next;
}
input.value += key;
handleTypingInput();
input.focus();
});
renderCategories(); renderCategories();