style: 打字练习改为标准键盘提示并增加特殊字符输入说明
This commit is contained in:
+22
-5
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user