UNPKG

wbf

Version:

[![LICENSE](https://img.shields.io/github/license/halodong/web-barrier-free?style=flat-square)](./LICENSE) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/halodong/web-barrier

270 lines (248 loc) 8.88 kB
import defaultOptions from './default' import { consoleClassName, consoleDomId, emphasizeClassName, getGather, IGather, optionsArr, showBarDomId, testReadMode } from './util' import { outHandler, overHandler } from './handlers' import './index.css' let instance: Wbf | null = null let compatible = true class Wbf { public opening: boolean = false public readMode: readMode = 'finger' public language: language public rate: number public pitch: number public volume: number public showBarEl: HTMLDivElement | null = null private readonly needConsole: boolean = true private readonly overHandler private readonly outHandler static getInstance = (options?: Options): Wbf => { if (instance === null) { instance = new Wbf(options) } return instance } static clearInstance = (): void => { instance = null } constructor (options?: Options) { if (typeof SpeechSynthesisUtterance === 'undefined' || typeof speechSynthesis === 'undefined') { compatible = false console.warn('Current browser does not support SpeechSynthesisUtterance and speechSynthesis') } // options init if (options == null) options = defaultOptions options?.readMode !== undefined && (this.readMode = options.readMode) options?.needConsole !== undefined && (this.needConsole = options.needConsole) this.language = options?.language ?? defaultOptions.language this.rate = options?.rate ?? defaultOptions.rate this.pitch = options?.pitch ?? defaultOptions.pitch this.volume = options?.volume ?? defaultOptions.volume this.overHandler = (e: { target: HTMLElement }) => overHandler(e, this) this.outHandler = (e: { target: HTMLElement }) => outHandler(e, this) if (instance != null) { instance = this console.warn('There are currently multiple wbf instances') } } open (): void { /** * Change mode to start wbf * And according to the options to determine whether to open the console dom */ if (this.opening) return this.changeMode(this.readMode) if (this.showBarEl == null) { const showBar = this.createShowBarDom() this.showBarEl = showBar } this.addHandler() this.opening = true this.needConsole && this.createConsole() } close (): void { /** * Remove was added emphasize elements * Cancel current speechSynthesis * Remove the corresponding listener event * Remove console and showBar * */ const emphasizeEls = document.querySelectorAll(`.${emphasizeClassName}`) emphasizeEls.forEach((el) => { this.removeEmphasize(el) }) compatible && window.speechSynthesis?.cancel() document.removeEventListener('mouseover', this.overHandler) document.removeEventListener('mouseout', this.outHandler) this.removeShowBarDom() this.removeConsole() this.opening = false } // You can modify the properties of wbf through this method, but you cannot modify the opening state setOption (keyName: string, value): void { if (optionsArr.includes[keyName] === false && this[keyName] !== undefined) { throw new Error(`${keyName} options do not exist on wbf`) } if (keyName === 'opening') throw new Error(`${keyName} cannot be changed `) switch (keyName) { case 'rate': // rate can range between 0.1 (lowest) and 10 (highest) if (value > 10) value = 10 if (value < 0.1) value = 0.1 break case 'pitch': // pitch can range between 0 (lowest) and 2 (highest) if (value > 2) value = 2 if (value < 0) value = 0 break case 'volume': // volume can range between 0 (lowest) and 1 (highest.) if (value > 1) value = 1 if (value < 0) value = 0 break default: break } this[keyName] = value } // You can modify the wbf by this method reading mode changeMode (readMode: readMode): void { if (!testReadMode(readMode)) { throw new Error(`readMode not includes this ${readMode}`) } this.readMode = readMode if (readMode !== 'finger') { const allText = document.body.innerText this.playAudio(allText) } } playAudio (str: string): SpeechSynthesisUtterance | undefined { if (!compatible) return window.speechSynthesis?.cancel() const msg = this.createUtterance(str) as SpeechSynthesisUtterance window.speechSynthesis?.speak(msg) return msg } emphasize (el: HTMLElement | Element): void { el.classList.add(emphasizeClassName) } removeEmphasize (el: HTMLElement | Element): void { el.classList.remove(emphasizeClassName) } private addHandler (): void { document.addEventListener('mouseover', this.overHandler) document.addEventListener('mouseout', this.outHandler) } private createUtterance (str): SpeechSynthesisUtterance | undefined { if (!compatible) return const msg = new SpeechSynthesisUtterance() msg.text = str msg.lang = this.language msg.pitch = this.pitch msg.rate = this.rate msg.volume = this.volume return msg } private createShowBarDom (): HTMLDivElement { const prev = document.getElementById(showBarDomId) as HTMLDivElement | null if (prev != null) return prev const showBar = document.createElement('div') showBar.id = showBarDomId showBar.style.position = 'fixed' showBar.style.bottom = '0px' showBar.style.left = '0px' showBar.style.width = '100%' showBar.style.minHeight = '50px' showBar.style.maxHeight = '300px' showBar.style.fontWeight = 'bold' showBar.style.textAlign = 'center' showBar.style.wordBreak = 'break-word;' showBar.style.overflow = 'hidden' showBar.style.background = 'white' showBar.style.border = '2px solid #eee' document.body.appendChild(showBar) return showBar } private createConsole (): void { const prev = document.getElementById(consoleDomId) if (prev != null) return const consoleEl = document.createElement('div') consoleEl.id = consoleDomId const gather: IGather = getGather(this.language) consoleEl.classList.add(consoleClassName) consoleEl.innerHTML = ` <div class="${consoleClassName}-main"> <div> <button id="_wbfClose">${gather.close}</button> <button id="_wbfContinuousRead">${gather.continuousRead}</button> <button id="_wbfFingerRead">${gather.fingerRead}</button> </div> | <div> ${gather.volume} <button id="_wbfAddVolume">+</button> <button id="_wbfReduceVolume">-</button> </div> | <div> ${gather.rate} <button id="_wbfAddRate">+</button> <button id="_wbfReduceRate">-</button> </div> </div>` document.body.insertBefore(consoleEl, document.body.firstChild) const closeBtn = document.getElementById('_wbfClose') const continuousReadBtn = document.getElementById('_wbfContinuousRead') const fingerReadBtn = document.getElementById('_wbfFingerRead') const addVolumeBtn = document.getElementById('_wbfAddVolume') const reduceVolumeBtn = document.getElementById('_wbfReduceVolume') const addRateBtn = document.getElementById('_wbfAddRate') const reduceRateBtn = document.getElementById('_wbfReduceRate') closeBtn != null && (closeBtn.onclick = () => this.close()) continuousReadBtn != null && (continuousReadBtn.onclick = () => this.changeMode('continuous')) fingerReadBtn != null && (fingerReadBtn.onclick = () => this.changeMode('finger')) addVolumeBtn != null && (addVolumeBtn.onclick = () => this.setOption('volume', this.volume + 0.1)) reduceVolumeBtn != null && (reduceVolumeBtn.onclick = () => this.setOption('volume', this.volume - 0.1)) addRateBtn != null && (addRateBtn.onclick = () => this.setOption('rate', this.rate + 0.1)) reduceRateBtn != null && (reduceRateBtn.onclick = () => this.setOption('rate', this.rate - 0.1)) } private removeConsole (): void { const consoleEl = document.getElementById(consoleDomId) if (consoleEl != null) { consoleEl.remove() } } private removeShowBarDom (): void { if (this.showBarEl != null) { this.showBarEl.remove() this.showBarEl = null } } } type readMode = 'finger' | 'continuous' export type language = 'en' | 'zh-CN' interface Options { readMode?: readMode language?: language rate?: number pitch?: number volume?: number needConsole?: boolean } export default Wbf