In vanilla JS, of course, but also compatible with SPA.
The trick is to attach to window
's onkeydown / onkeypress / onkeyup, and remove the handler when needed. (Using addEventListener / removeEventListener is OK too, but harder to prevent from attaching multiple times.)
async startQuiz() {
window.onkeypress = this.onQuizKeypress.bind(this)
this.isQuizModal = true
}
endQuiz() {
window.onkeypress = null
this.isQuizModal = false
}
beforeDestroy() {
window.onkeypress = null
}
If you use class
, function keyword
or this keyword
, you would also need to .bind(this)
.
About the real event, you might need several escaping mechanisms, before the real thing.
onQuizKeypress(evt: KeyboardEvent) {
if (!this.isQuizModal) {
return
}
if (
evt.target instanceof HTMLTextAreaElement ||
evt.target instanceof HTMLInputElement
) {
return
}
...
}
Add several utility functions,
const click = async (el: any) => {
if (!el) return
if (el.classList?.add) {
el.classList.add('active')
}
if (el.click) {
const r = el.click()
if (r instanceof Promise) {
await r
if (el.classList?.remove) {
el.classList.remove('active')
}
}
// else {
// await new Promise((resolve) => setTimeout(resolve, 500))
// if (el.classList?.remove) {
// el.classList.remove('active')
// }
// }
}
}
const mapKey = (map: Record<string, (() => any) | string>) => {
let action = map[evt.key]
while (typeof action === 'string') {
action = map[action]
}
if (typeof action === 'function') action()
}
Now, for the real key mapping,
onQuizKeypress(evt: KeyboardEvent) {
... // Escape several things
mapKey({
'1': () => click(this.$refs.btnMarkRight),
'2': () => click(this.$refs.btnMarkWrong),
'3': () => click(this.$refs.btnMarkRepeat),
q: () => click(this.$refs.btnHideAnswer),
' ': 'q',
e: () => click(this.$refs.btnEditModal),
})
}
And now, for the styling. I just make it simple, yet clear.
button:focus, button:active, button.active {
filter: grayscale(50%) saturate(75%);
}
If you are curious, I use this a real project -- patarapolw/zhquiz -- packages/nuxt/pages/quiz.vue. Visit my web app.
Top comments (0)