├── example ├── allaw.cur └── default.html ├── .gitignore ├── src ├── assets │ ├── allaw.cur │ ├── audio.png │ ├── bigtext.png │ ├── close.png │ ├── cursor.png │ ├── pointer.png │ ├── reset.png │ ├── speed.png │ ├── zoomin.png │ ├── zoomout.png │ ├── audio-close.png │ ├── audio-hover.png │ ├── close-hover.png │ ├── cursor-hover.png │ ├── pointeread.png │ ├── reset-hover.png │ ├── speed-hover.png │ ├── speed-quick.png │ ├── zoomin-hover.png │ ├── bigtext-hover.png │ ├── pointer-hover.png │ ├── zoomout-hover.png │ ├── audio-close-hover.png │ ├── pointeread-hover.png │ └── speed-quick-hover.png ├── config.json ├── modules │ ├── CursorAuto │ │ ├── index.scss │ │ └── index.js │ ├── hack.js │ ├── PointerFllow │ │ ├── index.tmpl.js │ │ ├── index.scss │ │ └── index.js │ ├── Audio │ │ ├── index.tmpl.js │ │ ├── index.scss │ │ └── index.js │ ├── BigText │ │ ├── index.tmpl.js │ │ ├── index.scss │ │ └── index.js │ ├── constans.js │ ├── pubSub.js │ ├── TopBar │ │ ├── index.scss │ │ ├── index.js │ │ └── index.tmpl.js │ ├── ZoomPage.js │ ├── base.js │ └── utils.js ├── assist-entry.js └── assist.js ├── DEVELOPER.md ├── package.json ├── rollup.config.js └── README.md /example/allaw.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/example/allaw.cur -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # system ignore 3 | .DS_Store 4 | node_modules 5 | node_modules/ 6 | .tmpl -------------------------------------------------------------------------------- /src/assets/allaw.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/allaw.cur -------------------------------------------------------------------------------- /src/assets/audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/audio.png -------------------------------------------------------------------------------- /src/assets/bigtext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/bigtext.png -------------------------------------------------------------------------------- /src/assets/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/close.png -------------------------------------------------------------------------------- /src/assets/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/cursor.png -------------------------------------------------------------------------------- /src/assets/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/pointer.png -------------------------------------------------------------------------------- /src/assets/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/reset.png -------------------------------------------------------------------------------- /src/assets/speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/speed.png -------------------------------------------------------------------------------- /src/assets/zoomin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/zoomin.png -------------------------------------------------------------------------------- /src/assets/zoomout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/zoomout.png -------------------------------------------------------------------------------- /src/assets/audio-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/audio-close.png -------------------------------------------------------------------------------- /src/assets/audio-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/audio-hover.png -------------------------------------------------------------------------------- /src/assets/close-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/close-hover.png -------------------------------------------------------------------------------- /src/assets/cursor-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/cursor-hover.png -------------------------------------------------------------------------------- /src/assets/pointeread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/pointeread.png -------------------------------------------------------------------------------- /src/assets/reset-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/reset-hover.png -------------------------------------------------------------------------------- /src/assets/speed-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/speed-hover.png -------------------------------------------------------------------------------- /src/assets/speed-quick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/speed-quick.png -------------------------------------------------------------------------------- /src/assets/zoomin-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/zoomin-hover.png -------------------------------------------------------------------------------- /src/assets/bigtext-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/bigtext-hover.png -------------------------------------------------------------------------------- /src/assets/pointer-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/pointer-hover.png -------------------------------------------------------------------------------- /src/assets/zoomout-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/zoomout-hover.png -------------------------------------------------------------------------------- /src/assets/audio-close-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/audio-close-hover.png -------------------------------------------------------------------------------- /src/assets/pointeread-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/pointeread-hover.png -------------------------------------------------------------------------------- /src/assets/speed-quick-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duheng/assist/HEAD/src/assets/speed-quick-hover.png -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "mozi-assist", 3 | "domain": "", 4 | "url": "//tts.baidu.com/text2audio" 5 | } -------------------------------------------------------------------------------- /src/modules/CursorAuto/index.scss: -------------------------------------------------------------------------------- 1 | * { 2 | cursor:url("./allaw.cur"),auto !important; 3 | } 4 | a { 5 | cursor:url("./linkaw.cur"),auto !important; 6 | } -------------------------------------------------------------------------------- /src/assist-entry.js: -------------------------------------------------------------------------------- 1 | import Assist from './assist.js'; 2 | import config from './config.json'; 3 | 4 | const AssistEntry = new Assist(config) 5 | export default AssistEntry; -------------------------------------------------------------------------------- /src/modules/hack.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ie10 不支持 location.origin,以下是hack方法 4 | */ 5 | 6 | if (!window.location.origin) { 7 | window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/modules/PointerFllow/index.tmpl.js: -------------------------------------------------------------------------------- 1 | const PointerFllowHtml = () => { 2 | return `
3 |
4 |
5 |
` 6 | } 7 | 8 | export default PointerFllowHtml; -------------------------------------------------------------------------------- /src/modules/Audio/index.tmpl.js: -------------------------------------------------------------------------------- 1 | const BigTextHtml = (namespace) => { 2 | return ` 6 | ` 7 | } 8 | 9 | export default BigTextHtml; -------------------------------------------------------------------------------- /src/modules/PointerFllow/index.scss: -------------------------------------------------------------------------------- 1 | @mixin pointer { 2 | z-index: 99999999999; 3 | transform: none; 4 | transform-origin: 0px 0px; 5 | position:fixed; top:0;left:0; background-color:#ff0000 !important; width:100%; height:4px; 6 | } 7 | .pointer-follow-html { 8 | &-x,&-y { 9 | @include pointer; 10 | } 11 | &-y { height:100%; width:4px; } 12 | } -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | ## 开发者需关注的 2 | 3 | #### 基础配置(src/config.json) 4 | 1 - namespace 给插件起一个命名空间 5 | 6 | 2 - domain 在你的具体项目中你需要设置如 .mozi.com 的 domain 来保持你网站多页面之间的状态保持 7 | 8 | 3 - url 文本转语音的api接口,目前用的是百度免费的,如用于生产环境你需要自行开发 9 | 10 | ``` 11 | { 12 | "namespace": "mozi-assist", 13 | "domain": "", 14 | "url": "//tts.baidu.com/text2audio" 15 | } 16 | ``` 17 | 18 | #### 开发 19 | 20 | ``` 21 | npm i 22 | npm start (启动开发) 23 | ``` 24 | 访问 http://localhost:3000/example/default.html -------------------------------------------------------------------------------- /src/modules/BigText/index.tmpl.js: -------------------------------------------------------------------------------- 1 | const BigTextHtml = (namespace) => { 2 | return `
3 |
4 |
5 | X 6 |
7 |
` 8 | } 9 | 10 | const BigTextBone = () => { 11 | return `
` 12 | } 13 | 14 | export { 15 | BigTextHtml, 16 | BigTextBone 17 | }; -------------------------------------------------------------------------------- /src/modules/constans.js: -------------------------------------------------------------------------------- 1 | const audioTabText = { 2 | pointeread: '已开启指读模式', 3 | bigtextOpen: '大字幕已开启', 4 | bigtextClose: '大字幕已关闭', 5 | pointerFollowOpen: '十字线已开启', 6 | pointerFollowClose: '十字线已关闭', 7 | cursorAutoOpen: '大鼠标已开启', 8 | cursorAutoClose: '大鼠标已关闭', 9 | zoomOut: '页面已放大', 10 | zoomOutEnd: '页面已放到最大', 11 | zoomMin: '页面已缩小', 12 | zoomMinEnd: '页面已缩至最小', 13 | speedQuick: '语速已加快', 14 | speedMiddle: '语速已正常', 15 | audioOpen: '声音已开启', 16 | audioClose: '声音关闭', 17 | reset: '已重置', 18 | } 19 | export const symbolsReg = new RegExp("[]") 20 | export default audioTabText 21 | 22 | -------------------------------------------------------------------------------- /src/modules/Audio/index.scss: -------------------------------------------------------------------------------- 1 | @mixin pointer { 2 | z-index: 99999999999; 3 | transform: none; 4 | transform-origin: 0px 0px; 5 | 6 | position:fixed; top:0;left:0; background-color:blue !important; width:100%; height:2px; 7 | } 8 | .bigtext-html { 9 | z-index: 99999999999; 10 | height: 150px; 11 | text-align: center; 12 | position: fixed; 13 | bottom: 0; 14 | right: 0; 15 | left: 0; 16 | border-top: 1px solid #505050; 17 | &-content { 18 | height: 100%; 19 | background-color: #FFFFFF; 20 | font-size: 53px; 21 | color: #333 !important; 22 | text-align: center; 23 | font-weight: bold; 24 | } 25 | } -------------------------------------------------------------------------------- /src/modules/pubSub.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | list: {}, 4 | subscribe: function(key, fn) { 5 | if (!this.list[key]) this.list[key] = []; 6 | 7 | this.list[key].push(fn); 8 | }, 9 | unsubscribe: function(key, fn) { 10 | let fnList = this.list[key]; 11 | 12 | if (!fnList) return false; 13 | 14 | if (!fn) { // 不传入指定的方法,清空所用 key 下的订阅 15 | fnList && (fnList.length = 0); 16 | } else { 17 | fnList.forEach((item, index) => { 18 | item === fn && fnList.splice(index, 1); 19 | }); 20 | } 21 | }, 22 | publish: function(key, ...args) { 23 | let fnList = this.list[key]; 24 | if (!fnList) return false; 25 | 26 | for (let fn of this.list[key]) { 27 | !!fn && fn.call(this, ...args); 28 | } 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /example/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 墨子工程 6 | 7 | 8 |
无障碍
9 | 10 |
11 |

无障碍文本标题

12 | 跳转到 墨子工程 13 | “图片小鲁班” 14 |
15 | 16 | 17 |
18 |

无障碍role标注

19 |
跳转到 墨子工程
20 | “图片小鲁班” 21 |
22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/modules/BigText/index.scss: -------------------------------------------------------------------------------- 1 | @mixin pointer { 2 | z-index: 99999999999; 3 | transform: none; 4 | transform-origin: 0px 0px; 5 | 6 | position:fixed; top:0;left:0; background-color:blue !important; width:100%; height:2px; 7 | } 8 | .bigtext-html { 9 | z-index: 99999999999; 10 | height: 150px; 11 | text-align: center; 12 | position: fixed; 13 | bottom: 0; 14 | right: 0; 15 | left: 0; 16 | border-top: 1px solid #797F8D; 17 | background-color: #FFFFFF; 18 | &-content { 19 | height: 100%; 20 | background-color: #FFFFFF; 21 | font-size: 53px; 22 | color: #333 !important; 23 | text-align: center; 24 | font-weight: bold; 25 | width: 93%; 26 | margin: 0 auto; 27 | } 28 | 29 | &-btn { 30 | width: 20px; 31 | height: 20px; 32 | background-color: red; 33 | position: absolute; 34 | right: 20px; 35 | top: 20px; 36 | border-radius: 10px; 37 | color: #FFFFFF; 38 | cursor: pointer; 39 | } 40 | 41 | &-bone { 42 | width: 100%; 43 | height: 151px; 44 | } 45 | } -------------------------------------------------------------------------------- /src/assist.js: -------------------------------------------------------------------------------- 1 | import './modules/hack'; 2 | import Base from './modules/base'; 3 | import TopBar from './modules/TopBar'; 4 | import Audio from './modules/Audio'; 5 | import ZoomPage from './modules/ZoomPage'; 6 | import PointerFllow from './modules/PointerFllow'; 7 | import CursorAuto from './modules/CursorAuto'; 8 | import BigText from './modules/BigText'; 9 | 10 | const InitModules = [ TopBar, Audio, ZoomPage, PointerFllow, CursorAuto, BigText ] 11 | class Assist extends Base { 12 | 13 | constructor(opts = {}) { 14 | super(opts); 15 | // 合并参数 16 | this.mergeConfig(opts); 17 | // 初始化插件 18 | this.init(); 19 | this.isShow(); 20 | this.resetAction = this.reset 21 | } 22 | 23 | init() { 24 | // 初始化dom 25 | InitModules.map(item=>this.use(item)) 26 | this.registeStyle() 27 | this.registeHtml() 28 | // 初始化事件 29 | InitModules.map(item=>this.useEvent(item)) 30 | // 格式化大段文本 31 | this.formatLongText() 32 | 33 | } 34 | 35 | reset() { 36 | Audio.reset() 37 | ZoomPage.reset() 38 | CursorAuto.reset(this) 39 | PointerFllow.reset(this) 40 | BigText.reset(this) 41 | } 42 | 43 | } 44 | export default Assist; 45 | -------------------------------------------------------------------------------- /src/modules/TopBar/index.scss: -------------------------------------------------------------------------------- 1 | 2 | .topbar-html { 3 | width: 100%; 4 | background: #797F8D; 5 | overflow: hidden; 6 | z-index: 2147483645; 7 | position: fixed; 8 | top: 0; 9 | box-shadow: 0 0 10px 2px #999; 10 | left: 0; 11 | right: 0; 12 | &-mright { 13 | margin-right: 50px !important; 14 | } 15 | &-margin0 { 16 | margin: 0 !important; 17 | } 18 | &-content { 19 | width: 1080px; 20 | height: 100px; 21 | margin: 0 auto; 22 | display: flex; 23 | justify-content: center; 24 | &-item { 25 | cursor: pointer; 26 | margin: 0 6px; 27 | display: flex; 28 | flex-direction: column; 29 | align-items: center; 30 | span { 31 | height: 30px; 32 | line-height: 30px; 33 | color: #FFFFFF; 34 | font-size: 16px; 35 | font-weight: 500; 36 | font-family: PingFangSC-Medium; 37 | } 38 | 39 | img { 40 | height: 52px; 41 | width: 52px; 42 | border-radius: 5px; 43 | display: block; 44 | } 45 | 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/modules/CursorAuto/index.js: -------------------------------------------------------------------------------- 1 | import { cookie, removeNode } from '../utils' 2 | import audioTabText from '../constans' 3 | import Audio from '../Audio'; 4 | 5 | import styles from './index.scss' 6 | 7 | const CursorAuto = { 8 | init(core) { 9 | const { namespace } = core.config 10 | 11 | }, 12 | setEvents(core) { 13 | const { namespace } = core.config 14 | if(cookie.get('cursor',namespace)) { 15 | core.creatStyle('cursor-auto-style',styles,true) 16 | } 17 | const tabBarBtn = document.getElementById(`${namespace}-cursor-auto`) 18 | tabBarBtn.onclick = () => { 19 | const activeBtn = document.getElementById(`${namespace}-cursor-auto-style`) 20 | if(activeBtn) { 21 | removeNode(activeBtn) 22 | cookie.set('cursor', false, namespace) 23 | Audio.playAudio(audioTabText.cursorAutoClose) 24 | } else { 25 | cookie.set('cursor', true, namespace) 26 | core.creatStyle('cursor-auto-style',styles,true) 27 | Audio.playAudio(audioTabText.cursorAutoOpen) 28 | } 29 | } 30 | }, 31 | reset(core) { 32 | const { namespace } = core.config 33 | const activeBtn = document.getElementById(`${namespace}-cursor-auto-style`) 34 | activeBtn && removeNode(activeBtn) 35 | } 36 | 37 | }; 38 | 39 | export default CursorAuto; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assist", 3 | "version": "0.0.1", 4 | "description": "盲人辅助工具", 5 | "scripts": { 6 | "start": "export ENV=development && rollup --config rollup.config.js --watch", 7 | "build": "rollup --config rollup.config.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/duheng/assist.git" 12 | }, 13 | "author": "墨子", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/duheng/assist/issues" 17 | }, 18 | "browserslist": [ 19 | "> 1%", 20 | "last 2 versions", 21 | "ie 6-11" 22 | ], 23 | "homepage": "https://github.com/duheng/assist#readme", 24 | "devDependencies": { 25 | "@babel/core": "^7.8.7", 26 | "@babel/plugin-proposal-object-rest-spread": "^7.8.3", 27 | "@babel/plugin-transform-async-to-generator": "^7.10.1", 28 | "@babel/plugin-transform-classes": "^7.13.0", 29 | "@babel/preset-env": "^7.14.2", 30 | "@rollup/plugin-commonjs": "^12.0.0", 31 | "@rollup/plugin-image": "^2.0.6", 32 | "@rollup/plugin-json": "^4.0.2", 33 | "@rollup/plugin-node-resolve": "^8.0.0", 34 | "core-js": "^3.12.1", 35 | "eslint": "^6.8.0", 36 | "eslint-config-airbnb": "^18.1.0", 37 | "eslint-plugin-import": "^2.20.1", 38 | "lerna": "^3.5.1", 39 | "rollup": "^2.0.6", 40 | "rollup-plugin-babel": "^4.4.0", 41 | "rollup-plugin-copy": "^3.4.0", 42 | "rollup-plugin-eslint": "^7.0.0", 43 | "rollup-plugin-hash": "^1.3.0", 44 | "rollup-plugin-sass": "^1.2.2", 45 | "rollup-plugin-serve": "^1.1.0", 46 | "rollup-plugin-terser": "^7.0.2" 47 | }, 48 | "dependencies": { 49 | "es6-proxy-polyfill": "^2.1.1", 50 | "js-cookie": "^2.2.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/modules/ZoomPage.js: -------------------------------------------------------------------------------- 1 | import { cookie, addEvent, removeEvent } from './utils' 2 | import audioTabText from './constans' 3 | import Audio from './Audio'; 4 | 5 | const ZoomPage = { 6 | init(core) { 7 | const { namespace } = core.config 8 | this.size = cookie.get('zomm',namespace) || 1.0 9 | this.ignore = ['LINK','SCRIPT'] 10 | this.namespace = namespace 11 | this.set() 12 | 13 | }, 14 | setEvents(core) { 15 | const { namespace } = core.config 16 | document.getElementById(`${namespace}-zoom-out`).onclick = () => { 17 | this.zoomOut(core) 18 | } 19 | 20 | document.getElementById(`${namespace}-zoom-min`).onclick = () => { 21 | this.zoomMin(core) 22 | } 23 | addEvent(window,'DOMContentLoaded',()=>{ 24 | this.updateZoomState(core) 25 | }) 26 | }, 27 | updateZoomState(core) { 28 | let { message } = core 29 | message.publish('zoomState',this.size) 30 | }, 31 | zoomOut(core) { 32 | if(this.size >= 1.3) { 33 | console.log('已最大') 34 | Audio.playAudio(audioTabText.zoomOutEnd) 35 | this.updateZoomState(core) 36 | return 37 | } 38 | this.size = parseFloat((this.size+0.1).toFixed(10)); 39 | this.updateZoomState(core) 40 | this.set(); 41 | Audio.playAudio(audioTabText.zoomOut) 42 | }, 43 | zoomMin(core) { 44 | if(this.size <= 1.0) { 45 | console.log('已最小') 46 | Audio.playAudio(audioTabText.zoomMinEnd) 47 | this.updateZoomState(core) 48 | return 49 | } 50 | this.size = parseFloat((this.size-0.1).toFixed(10)); 51 | this.updateZoomState(core) 52 | this.set(); 53 | Audio.playAudio(audioTabText.zoomMin) 54 | }, 55 | set() { 56 | [].forEach.call( document.body.children, (el)=> { 57 | const __el = el.tagName.toUpperCase() 58 | if(this.ignore.indexOf(__el) > -1 || el.id == this.namespace) { 59 | return 60 | } 61 | el.style.zoom = this.size; 62 | // el.style.transform = `scale(${this.size})` 63 | //el.style.transformOrigin = '0px 0px' 64 | }); 65 | cookie.set('zomm', this.size, this.namespace) 66 | }, 67 | reset() { 68 | this.size = 1.0 69 | this.set() 70 | } 71 | 72 | }; 73 | 74 | export default ZoomPage; -------------------------------------------------------------------------------- /src/modules/PointerFllow/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { cookie, addEvent, removeEvent } from '../utils' 3 | import audioTabText from '../constans' 4 | import Audio from '../Audio'; 5 | 6 | import styles from './index.scss' 7 | import tmpl from './index.tmpl.js' 8 | const PointerFllow = { 9 | init(core) { 10 | const { namespace } = core.config 11 | this.body = document.body 12 | core.creatStyle('pointer-follow-style',styles) 13 | core.creatHtml('pointer-follow-html',tmpl) 14 | 15 | }, 16 | setEvents(core) { 17 | const { namespace } = core.config 18 | if(cookie.get('pointer',namespace)) { 19 | this.show(core) 20 | } 21 | this.togglePointer(core, namespace) 22 | }, 23 | addEventMove() { 24 | addEvent(this.body,'mousemove',this.mouseMove) 25 | }, 26 | removeEventMove() { 27 | removeEvent(this.body,'mousemove',this.mouseMove) 28 | }, 29 | togglePointer(core, namespace) { 30 | const tabBarBtn = document.getElementById(`${namespace}-pointer-follow`) 31 | const activeBtn = document.getElementById(`${namespace}-pointer-follow-html`) 32 | tabBarBtn.onclick = () => { 33 | if( activeBtn.style.display == 'block' ) { 34 | this.reset(core) 35 | Audio.playAudio(audioTabText.pointerFollowClose) 36 | } else { 37 | this.show(core) 38 | Audio.playAudio(audioTabText.pointerFollowOpen) 39 | } 40 | } 41 | }, 42 | mouseMove(event){ 43 | var event = window.event || event; 44 | var pointerX = document.getElementById("pointer-follow-html-x"), 45 | pointerY = document.getElementById("pointer-follow-html-y"); 46 | pointerX.style.top = event.clientY - 10 + "px"; 47 | pointerY.style.left = event.clientX - 10 + "px"; 48 | }, 49 | show(core) { 50 | const { namespace } = core.config 51 | const activeBtn = document.getElementById(`${namespace}-pointer-follow-html`) 52 | activeBtn.style.display = 'block' 53 | this.addEventMove() 54 | cookie.set('pointer',true,namespace) 55 | }, 56 | reset(core) { 57 | const { namespace } = core.config 58 | const activeBtn = document.getElementById(`${namespace}-pointer-follow-html`) 59 | activeBtn.style.display = 'none' 60 | this.removeEventMove() 61 | cookie.set('pointer',false,namespace) 62 | } 63 | }; 64 | 65 | export default PointerFllow; -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const babelPlugin = require('rollup-plugin-babel'); 4 | const json = require('@rollup/plugin-json'); 5 | 6 | //const { eslint } = require('rollup-plugin-eslint'); 7 | const cwd = process.cwd(); 8 | import sass from 'rollup-plugin-sass'; 9 | import resolve from '@rollup/plugin-node-resolve'; 10 | import commonjs from '@rollup/plugin-commonjs'; 11 | import image from '@rollup/plugin-image'; 12 | import { terser } from "rollup-plugin-terser"; 13 | import serve from "rollup-plugin-serve"; 14 | 15 | // import hash from 'rollup-plugin-hash'; 16 | 17 | import copy from 'rollup-plugin-copy' 18 | 19 | const entry = { 20 | assist: './src/assist.js', 21 | 'assist-entry':'./src/assist-entry.js' 22 | } 23 | 24 | const mark = `/** 25 | * 欢迎来到墨子工程 26 | * 邮箱: duheng1100@163.com 27 | * github: https://github.com/duheng/ 28 | **/` 29 | 30 | function generateWebConfig(isBrowser,input) { 31 | return { 32 | input, 33 | output: { 34 | banner: mark, 35 | dir: 'dist', 36 | format: isBrowser ? 'umd' : 'cjs', 37 | name: 'AssistEntry' 38 | }, 39 | plugins: [ 40 | sass(), 41 | json(), 42 | image(), 43 | babelPlugin({ 44 | exclude: 'node_modules/**', 45 | presets: [ 46 | [ 47 | '@babel/env', 48 | { 49 | modules: false, 50 | targets: { 51 | browsers: [ 52 | "> 1%", 53 | "last 2 versions", 54 | "ie 9-11" 55 | ], 56 | // node: 8 57 | }, 58 | corejs: 3, // 声明corejs版本 59 | useBuiltIns: 'usage' 60 | } 61 | ] 62 | ], 63 | plugins: [ 64 | '@babel/plugin-proposal-object-rest-spread', 65 | '@babel/plugin-transform-classes', 66 | ], 67 | }), 68 | resolve({ 69 | browser: isBrowser, 70 | }), 71 | commonjs(), 72 | // terser({ compress: { drop_console: true } }) 73 | copy({ 74 | targets: [ 75 | { src: 'src/assets/allaw.cur', dest: 'example' } 76 | ] 77 | }), 78 | process.env.ENV === "development" ? serve({ 79 | port: 3000, 80 | contentBase: ["./"], // 静态资源所在目录 81 | }) : null 82 | ], 83 | }; 84 | } 85 | 86 | const build = () => { 87 | return Object.keys(entry).map(item=>{ 88 | return generateWebConfig(true,entry[item]) 89 | }) 90 | } 91 | module.exports = build(); -------------------------------------------------------------------------------- /src/modules/TopBar/index.js: -------------------------------------------------------------------------------- 1 | import { cookie, addEvent,isFirefox, removeEvent } from '../utils' 2 | import styles from './index.scss' 3 | import tmpl from './index.tmpl.js' 4 | const TopBar = { 5 | init(core) { 6 | const { namespace } = core.config 7 | this.namespace = namespace 8 | core.creatStyle('topbar-style',styles) 9 | core.creatHtml('topbar-html',tmpl) 10 | }, 11 | setEvents(core) { 12 | const { namespace } = core.config 13 | const BtnClose = document.getElementById(`${namespace}-close`) 14 | const BtnReset = document.getElementById(`${namespace}-reset`) 15 | const HoverItem = document.getElementById(`${namespace}-topbar-html-content`) 16 | 17 | BtnClose.onclick = () => { 18 | core.close() 19 | } 20 | 21 | BtnReset.onclick = () => { 22 | core.resetAction() 23 | } 24 | 25 | if(isFirefox()) { 26 | document.getElementById(`${namespace}-zoom-out`).style.display = 'none' 27 | document.getElementById(`${namespace}-zoom-min`).style.display = 'none' 28 | } 29 | 30 | [].forEach.call( HoverItem.children, (el)=> { 31 | addEvent(el,'mouseover',this.mouseOver) 32 | addEvent(el,'mouseout',this.mouseOut) 33 | }); 34 | 35 | 36 | }, 37 | mouseOver(event){ 38 | var event = window.event || event; 39 | var target = event.target || event.srcElement; 40 | if(target.tagName !== 'IMG') { 41 | return 42 | } 43 | const __name = target.getAttribute('name') 44 | const __hover = target.getAttribute('hover-src') 45 | 46 | if(__name == 'audio') { 47 | if(cookie.get('audio',TopBar.namespace)) { 48 | target.src = __hover 49 | } else { 50 | target.src = target.getAttribute('selected-hover-src') 51 | } 52 | 53 | } else if (__name == 'speed') { 54 | if(cookie.get('speed',TopBar.namespace) == 'fast' ) { 55 | target.src = target.getAttribute('selected-hover-src') 56 | } else { 57 | target.src = __hover 58 | } 59 | } else { 60 | target.src = __hover 61 | } 62 | 63 | 64 | }, 65 | mouseOut(event) { 66 | var event = window.event || event; 67 | var target = event.target || event.srcElement; 68 | if(target.tagName !== 'IMG') { 69 | return 70 | } 71 | const __name = target.getAttribute('name') 72 | const __source = target.getAttribute('source-src') 73 | if(__name == 'audio') { 74 | if(cookie.get('audio',TopBar.namespace)) { 75 | target.src = __source 76 | } else { 77 | target.src = target.getAttribute('selected-src') 78 | } 79 | 80 | } else if (__name == 'speed') { 81 | if(cookie.get('speed',TopBar.namespace) == 'fast' ) { 82 | target.src = target.getAttribute('selected-src') 83 | } else { 84 | target.src = __source 85 | } 86 | } else { 87 | target.src = __source 88 | } 89 | } 90 | }; 91 | 92 | export default TopBar; -------------------------------------------------------------------------------- /src/modules/BigText/index.js: -------------------------------------------------------------------------------- 1 | import { cookie, addEvent, removeEvent, parseTagText } from '../utils' 2 | import audioTabText,{ symbolsReg } from '../constans' 3 | import Audio from '../Audio'; 4 | 5 | import styles from './index.scss' 6 | import { BigTextHtml, BigTextBone } from './index.tmpl.js' 7 | 8 | const BigText = { 9 | init(core) { 10 | const { namespace } = core.config 11 | this.body = document.body 12 | this.namespace = namespace 13 | core.creatStyle('bigtext-style',styles) 14 | core.creatHtml('bigtext-html',BigTextHtml) 15 | core.creatHtml('bigtext-bone',BigTextBone) 16 | }, 17 | setEvents(core) { 18 | const { namespace } = core.config 19 | addEvent(window,'DOMContentLoaded',()=>{ 20 | if(cookie.get('bigtext',namespace)) { 21 | this.show(core) 22 | } 23 | }) 24 | this.toggleBigText(core, namespace) 25 | }, 26 | addEventMove() { 27 | addEvent(this.body,'mouseover',this.mouseOver) 28 | }, 29 | removeEventMove() { 30 | removeEvent(this.body,'mouseover',this.mouseOver) 31 | }, 32 | toggleBigText(core, namespace) { 33 | const tabBarBtn = document.getElementById(`${namespace}-bigtext`) 34 | const tabBarBtnClose = document.getElementById(`${namespace}-bigtext-close`) 35 | tabBarBtn.onclick = () => { 36 | const activeBtn = document.getElementById(`${namespace}-bigtext-html`) 37 | if( activeBtn.style.display == 'block' ) { 38 | this.reset(core) 39 | Audio.playAudio(audioTabText.bigtextClose) 40 | } else { 41 | this.show(core) 42 | Audio.playAudio(audioTabText.bigtextOpen) 43 | } 44 | } 45 | 46 | tabBarBtnClose.onclick = ()=> { 47 | this.reset(core) 48 | Audio.playAudio(audioTabText.bigtextClose) 49 | } 50 | }, 51 | mouseOver(event){ 52 | var event = window.event || event; 53 | var target = event.target || event.srcElement; 54 | const { namespace } = BigText 55 | var __parentNodeId = target.parentNode.id 56 | var __isAssist = __parentNodeId.indexOf(namespace) > -1 57 | const activeBtn = document.getElementById(`${namespace}-bigtext-content`) 58 | activeBtn.innerText = parseTagText(target).replace(symbolsReg,''); 59 | if(__isAssist || activeBtn.innerText == '文本') { 60 | activeBtn.innerText = '' 61 | return 62 | } 63 | }, 64 | show(core) { 65 | const { namespace } = core.config 66 | const activeBtn = document.getElementById(`${namespace}-bigtext-html`) 67 | const tabBar = document.getElementById(`${namespace}-bigtext-bone`) 68 | activeBtn.style.display = 'block' 69 | tabBar.style.display = 'block' 70 | this.addEventMove() 71 | cookie.set('bigtext', true, namespace) 72 | core.message.publish('bigTextState',true) 73 | }, 74 | reset(core) { 75 | const { namespace } = core.config 76 | const activeBtn = document.getElementById(`${namespace}-bigtext-html`) 77 | const tabBar = document.getElementById(`${namespace}-bigtext-bone`) 78 | activeBtn.style.display = 'none' 79 | tabBar.style.display = 'none' 80 | this.removeEventMove() 81 | cookie.set('bigtext', false, namespace) 82 | core.message.publish('bigTextState',false) 83 | } 84 | }; 85 | 86 | export default BigText; -------------------------------------------------------------------------------- /src/modules/TopBar/index.tmpl.js: -------------------------------------------------------------------------------- 1 | import reset from '../../assets/reset.png'; 2 | import audio from '../../assets/audio.png'; 3 | import speed from '../../assets/speed.png'; 4 | import zoomout from '../../assets/zoomout.png'; 5 | import zoomin from '../../assets/zoomin.png'; 6 | import cursor from '../../assets/cursor.png'; 7 | import pointer from '../../assets/pointer.png'; 8 | import bigtext from '../../assets/bigtext.png'; 9 | import pointeread from '../../assets/pointeread.png'; 10 | import close from '../../assets/close.png'; 11 | 12 | import resetHover from '../../assets/reset-hover.png'; 13 | import audioHover from '../../assets/audio-hover.png'; 14 | import speedHover from '../../assets/speed-hover.png'; 15 | import zoomoutHover from '../../assets/zoomout-hover.png'; 16 | import zoominHover from '../../assets/zoomin-hover.png'; 17 | import cursorHover from '../../assets/cursor-hover.png'; 18 | import pointerHover from '../../assets/pointer-hover.png'; 19 | import bigtextHover from '../../assets/bigtext-hover.png'; 20 | import pointereadHover from '../../assets/pointeread-hover.png'; 21 | import closeHover from '../../assets/close-hover.png'; 22 | 23 | import closeAudio from '../../assets/audio-close.png'; 24 | import speedQuick from '../../assets/speed-quick.png'; 25 | 26 | import closeAudioHover from '../../assets/audio-close-hover.png'; 27 | import speedQuickHover from '../../assets/speed-quick-hover.png'; 28 | 29 | const topBarHtml = (namespace) => { 30 | return `
31 |
32 | 重置 33 | 34 |
35 |
36 | 声音开关 37 | 38 |
39 |
40 | 语速 41 | 42 |
43 |
44 | 放大 45 | 46 |
47 |
48 | 缩小 49 | 50 |
51 |
52 | 鼠标样式 53 | 54 |
55 |
56 | 十字线 57 | 58 |
59 |
60 | 大字幕 61 | 62 |
63 |
64 | 指读 65 | 66 |
67 |
68 | 退出服务 69 | 70 |
71 |
` 72 | } 73 | 74 | export default topBarHtml; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 无障碍插件 2 | > 这是一个针对于 有视障、听说障碍、读写障碍、肢体障碍,机体功能衰退的老年人群开发的一个辅助插件使用网站的js插件 3 | 4 | **项目演示** 5 | 6 | --- 7 | 8 |

9 | 10 | 11 | 12 |

13 | 14 | --- 15 | ### 插件接入方式: 16 | * 1 - 在页面合适的地方如banner处加入id为 assist-open 的标签 17 | * 2 - 在页面底部 body 之前引入插件; 18 | * 3 - 如果需要特殊处理的地方使用后面的API做对应处理; 19 | 20 | ``` 21 | 22 | 23 | 24 | 25 | 墨子工程 26 | 27 | 28 |
无障碍
29 | 30 | 31 | 32 | ``` 33 | 34 | [开发者关注](./DEVELOPER.md "开发者关注") 35 | 36 | ## API 37 | 38 | - showTag; 用于打开无障碍标识,(点击无障碍后并不在当前页面打开,而是跳转到其他没有调用showTag的页面打开) 39 | ``` 40 | 41 | 42 | 43 | 44 | 墨子工程 45 | 46 | 47 | 无障碍 48 | 49 | 54 | 55 | 56 | ``` 57 | - zoomState; 返回页面放大倍数 58 | ``` 59 | 60 | 61 | 62 | 63 | 墨子工程 64 | 65 | 66 | 无障碍 67 | 68 | 75 | 76 | 77 | ``` 78 | - openState; 返回插件打开状态 79 | 80 | ``` 81 | 82 | 83 | 84 | 85 | 墨子工程 86 | 87 | 88 | 无障碍 89 | 90 | 97 | 98 | 99 | ``` 100 | 101 | - bigTextState; 大字幕开启状态 102 | 103 | 104 | ``` 105 | 106 | 107 | 108 | 109 | 墨子工程 110 | 111 | 112 | 无障碍 113 | 114 | 121 | 122 | 123 | ``` 124 | 125 | 126 | ### 页面标注: 127 | > 对于插件无法识别或识别不准的标签需业务自行标注,标注规范如下 128 | 129 | 1 - 对于img标签,需设置 alt ,如 130 | 131 | ``` 132 | 133 | 134 | 135 | 136 | 墨子工程 137 | 138 | 139 | 墨子工程无障碍图片 140 | 141 | 142 | ``` 143 | 144 | 2 - 对于其他标签,需使用 title 进行标注,如 145 | 146 | ``` 147 | 148 | 149 | 150 | 151 | 152 | 墨子工程 153 | 154 | 155 |
156 | 墨子工程无障碍 157 |
158 | 159 | 160 | ``` 161 | 162 | 3 - 对于非语意化标签,需加入 role来标注其真实属性,如果不标注title,则取标签内容,如 163 | 164 | ``` 165 | 166 | 167 | 168 | 169 | 墨子工程 170 | 171 | 172 |
173 | 提交 174 |
175 | 176 | 177 | ``` 178 | 179 | ### 隐藏模块: 180 | > 对业务中需要隐藏的模块加一个class名 qunar-assist-hide ,插件在打开的时候会自动监测这个class名统一隐藏 181 | 182 | ### 页面缩放影响: 183 | > 随着页面放大,部分非自适应或者绝对定位的组件可能会出现错位问题,需业务开发自行调整 184 | 185 | ### 大段文本识别: 186 | > 对于可能出现大段文本的地方,需要在当前标签加一个名为 qunar-assist-long-text 的class,插件会自动对这个class下的内容进行分割 187 | 188 | 189 | 190 | 191 | 192 | ### 兼容性 193 | 194 | - ie10+ 195 | - 所有主流浏览器 196 | - 火狐(不支持页面放大功能) 197 | -------------------------------------------------------------------------------- /src/modules/Audio/index.js: -------------------------------------------------------------------------------- 1 | import { cookie, ajax, addEvent, removeEvent, parseTagText, triggerEvent, trim } from '../utils' 2 | import audioTabText,{ symbolsReg } from '../constans' 3 | 4 | import styles from './index.scss' 5 | import tmpl from './index.tmpl.js' 6 | const Audio = { 7 | init(core) { 8 | const { namespace, url } = core.config 9 | this.body = document.body 10 | this.namespace = namespace 11 | this.AudioApi = url 12 | core.creatStyle('audio-style',styles) 13 | core.creatHtml('audio-html',tmpl) 14 | this.isAudio = cookie.get('audio',namespace) 15 | }, 16 | setEvents(core) { 17 | const { namespace } = core.config 18 | this.registeDom(namespace) 19 | this.toggleAudio() 20 | if(this.isAudio) { 21 | this.addEventMove() 22 | addEvent(document,'click',this.forceSafariPlayAudio) // 苹果浏览器需要用户跟浏览器有个交互才可以播放语音 23 | } else { 24 | this.audioTabImg.src = this.audioTabImg.getAttribute('selected-src') 25 | } 26 | 27 | if(this.speed == 'fast') { 28 | this.speedTabImg.src = this.speedTabImg.getAttribute('selected-src') 29 | } else { 30 | this.speedTabImg.src = this.speedTabImg.getAttribute('source-src') 31 | } 32 | }, 33 | registeDom(namespace) { 34 | this.audio = document.getElementById(`${namespace}-audio-media`) || '' 35 | this.audioTab = document.getElementById(`${namespace}-audio`) || '' 36 | this.audioTabImg = this.audioTab.getElementsByTagName('img')[0] 37 | 38 | this.speed = cookie.get('speed',namespace) 39 | this.speedTab = document.getElementById(`${namespace}-audio-speed`) || '' 40 | this.speedTabImg = this.speedTab.getElementsByTagName('img')[0] 41 | 42 | this.pointeReadTab = document.getElementById(`${namespace}-pointeread`) || '' 43 | }, 44 | toggleAudio() { 45 | const { namespace } = Audio 46 | this.audioTab.onclick = () => { 47 | if(this.isAudio) { 48 | this.closeAudio() 49 | }else { 50 | this.showAudio() 51 | Audio.playAudio(audioTabText.audioOpen) 52 | } 53 | } 54 | 55 | this.speedTab.onclick = () => { 56 | if(this.speed == 'middle') { 57 | this.speed = 'fast' 58 | this.speedTabImg.src = this.speedTabImg.getAttribute('selected-src') 59 | Audio.playAudio(audioTabText.speedQuick) 60 | } else { 61 | this.speed = 'middle' 62 | this.speedTabImg.src = this.speedTabImg.getAttribute('source-src') 63 | Audio.playAudio(audioTabText.speedMiddle) 64 | } 65 | cookie.set('speed', this.speed, namespace) 66 | } 67 | 68 | this.pointeReadTab.onclick = () => { 69 | this.showAudio(); 70 | Audio.playAudio(audioTabText.pointeread) 71 | } 72 | 73 | 74 | 75 | }, 76 | showAudio() { 77 | const { namespace } = Audio 78 | this.isAudio = true 79 | this.addEventMove() 80 | cookie.set('audio', true, namespace) 81 | this.audioTabImg.src = this.audioTabImg.getAttribute('source-src') 82 | }, 83 | closeAudio() { 84 | const { namespace } = Audio 85 | this.isAudio = false 86 | this.audio.pause() 87 | this.removeEventMove() 88 | cookie.set('audio', false, namespace) 89 | this.audioTabImg.src = this.audioTabImg.getAttribute('selected-src') 90 | }, 91 | addEventMove() { 92 | addEvent(this.body,'mouseover',this.mouseOver) 93 | }, 94 | removeEventMove() { 95 | removeEvent(this.body,'mouseover',this.mouseOver) 96 | }, 97 | forceSafariPlayAudio() { 98 | const { audio } = Audio 99 | audio.load(); 100 | audio.play(); 101 | }, 102 | mouseOver(event){ 103 | var event = window.event || event; 104 | var target = event.target || event.srcElement; 105 | var __text = parseTagText(target).replace(symbolsReg,'') 106 | var __parentNodeId = target.parentNode.id 107 | var __isAssist = __parentNodeId.indexOf(Audio.namespace) > -1 108 | 109 | if(__text == '' || trim(__text) == '文本' || __isAssist ) { 110 | return 111 | } 112 | Audio.playAudio(__text) 113 | }, 114 | playAudio(text) { 115 | const { namespace, AudioApi, isAudio, audio, speed, forceSafariPlayAudio } = Audio 116 | 117 | if(!isAudio) { 118 | return 119 | } 120 | 121 | let __speed = speed == 'middle' ? 5 : 7 122 | let AudioParam = `lan=zh&ie=UTF-8&spd=${__speed}&text=${encodeURI(text)}` 123 | let AudioUrl = `${AudioApi}?${AudioParam}` 124 | audio.src = AudioUrl 125 | document.getElementById(`${namespace}-audio-source`).src = AudioUrl 126 | document.getElementById(`${namespace}-audio-embed`).src = AudioUrl 127 | let playPromise = audio.play(); 128 | if(playPromise) { 129 | playPromise.then(_ => { 130 | // audio.pause(); 131 | removeEvent(document,'click', forceSafariPlayAudio) 132 | }) 133 | .catch(error => { 134 | console.log(error) 135 | }); 136 | } 137 | }, 138 | reset() { 139 | const { namespace } = Audio 140 | this.closeAudio() 141 | this.speedTabImg.src = this.speedTabImg.getAttribute('source-src') 142 | this.speed = 'middle' 143 | cookie.set('speed', 'middle', namespace) 144 | } 145 | 146 | }; 147 | 148 | export default Audio; -------------------------------------------------------------------------------- /src/modules/base.js: -------------------------------------------------------------------------------- 1 | import { cookie , addEvent, removeEvent } from './utils' 2 | import pubSub from './pubSub' 3 | 4 | export default class Base { 5 | constructor() { 6 | this.config = { 7 | namespace: 'mozi-assist', 8 | domain: '.mozi.com', 9 | url: '' 10 | }; 11 | this.message = pubSub 12 | this.tmplStyle = [] 13 | this.tmplHtml = [] 14 | this.registeOpen() 15 | 16 | } 17 | 18 | mergeConfig(opts) { 19 | this.config = { ...this.config, ...opts }; 20 | this.creatContainer() 21 | } 22 | 23 | use(plugin, ...args) { 24 | plugin.init(this, ...args); 25 | } 26 | 27 | useEvent(plugin, ...args) { 28 | plugin.setEvents(this, ...args); 29 | } 30 | 31 | creatContainer() { 32 | const { namespace } = this.config 33 | if(document.getElementById(namespace)) { 34 | return 35 | } 36 | const Container = document.createElement("div"); 37 | Container.id = namespace 38 | document.body.appendChild(Container) 39 | 40 | } 41 | 42 | registeOpen() { 43 | addEvent(document,'click',(event)=>{ 44 | var event = window.event || event; 45 | var target = event.target || event.srcElement; 46 | if(target.getAttribute('id') == 'assist-open') { 47 | if(!this.existIgnore()) { 48 | this.show() 49 | this.message.publish('openState',true) 50 | } else { 51 | const { namespace } = this.config 52 | cookie.set('show',true, namespace) 53 | const __href = document.getElementById('assist-open').getAttribute('assist-href') 54 | window.location.href = __href 55 | } 56 | } 57 | }) 58 | } 59 | 60 | isShow() { 61 | const { namespace } = this.config 62 | addEvent(window,'DOMContentLoaded',()=>{ 63 | if(cookie.get('show',namespace) && !this.existIgnore()) { 64 | this.isShowTopBar(true) 65 | this.message.publish('openState',true) 66 | } else { 67 | this.message.publish('openState',false) 68 | } 69 | }) 70 | } 71 | 72 | existIgnore() { 73 | const { namespace } = this.config 74 | const { origin, pathname } = location 75 | const __key = `${origin}${pathname}` 76 | return cookie.getTag(namespace).includes(__key) 77 | } 78 | 79 | show() { 80 | this.isShowTopBar(true) 81 | } 82 | 83 | close() { 84 | this.isShowTopBar(false) 85 | } 86 | 87 | showTag() { 88 | const { namespace } = this.config 89 | cookie.setTag(namespace) // 设置忽略 90 | this.resetAction() // 重置插件状态 91 | } 92 | 93 | isShowTopBar(isShow) { 94 | const { namespace } = this.config 95 | const activeBtn = document.getElementById(`${namespace}-topbar-html`) 96 | if(isShow) { 97 | document.body.style.marginTop = '100px' 98 | activeBtn.style.display = 'block' 99 | cookie.set('show',true, namespace) 100 | this.hideModules() 101 | } else { 102 | document.body.style.cssText = '' 103 | //activeBtn.style.display = 'none' 104 | cookie.remove(`${namespace}`) 105 | location.reload() 106 | } 107 | 108 | } 109 | 110 | hideModules() { 111 | const { namespace } = this.config 112 | const classList = document.getElementsByClassName(`${namespace}-hide`) 113 | for(let i=0;i< classList.length;i++){ 114 | classList[i].style.display = 'none' 115 | } 116 | } 117 | 118 | creatStyle(id, css, flag = false) { 119 | if(!flag) { 120 | this.tmplStyle.push(css) 121 | } else { 122 | const { namespace } = this.config 123 | let styleNode = document.createElement('style') 124 | styleNode.type = 'text/css' 125 | styleNode.id = `${namespace}-${id}` 126 | styleNode.className = id 127 | if(styleNode.styleSheet) { 128 | styleNode.styleSheet.cssText = css; 129 | } else { 130 | styleNode.innerHTML = css 131 | } 132 | document.getElementsByTagName('head')[0].appendChild(styleNode); 133 | } 134 | } 135 | 136 | creatHtml(id,htmlFn) { 137 | const { namespace } = this.config 138 | const DomContainer = document.createElement("div"); 139 | DomContainer.id = `${namespace}-${id}` 140 | DomContainer.className = id 141 | DomContainer.style.display = 'none' 142 | if(typeof(htmlFn) !== 'function') { 143 | console.error('htmlFn不是一个函数') 144 | return 145 | } 146 | const __html = htmlFn(namespace) 147 | DomContainer.innerHTML = __html 148 | this.tmplHtml.push(DomContainer.outerHTML) 149 | } 150 | 151 | registeHtml() { 152 | const { namespace } = this.config 153 | document.getElementById(namespace).innerHTML = this.tmplHtml.join('') 154 | } 155 | 156 | registeStyle() { 157 | const { namespace } = this.config 158 | let styleNode = document.createElement('style') 159 | styleNode.type = 'text/css' 160 | styleNode.id = `${namespace}-style` 161 | let __css = this.tmplStyle.join('\n') 162 | if(styleNode.styleSheet) { 163 | styleNode.styleSheet.cssText = __css; 164 | } else { 165 | styleNode.innerHTML = __css 166 | } 167 | document.getElementsByTagName('head')[0].appendChild(styleNode); 168 | } 169 | 170 | formatLongText() { 171 | const { namespace } = this.config 172 | const longText = document.getElementsByClassName(`${namespace}-long-text`) || []; 173 | [].forEach.call( longText, (el)=> { 174 | const __el = el.innerText.split('。') 175 | let __elItem = [] 176 | __el.map(item=> { 177 | __elItem.push(``) 178 | }) 179 | el.innerHTML = __elItem.join('。') 180 | }); 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /src/modules/utils.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | import config from '../config.json'; 3 | const __domain = config.domain 4 | const cookie = { 5 | set:(key,value, namespace) => { 6 | let memory = { 7 | show: false, // 是否展示无障碍 8 | audio: false, // 是否开启声音 9 | speed: 'middle', // 语速 10 | zomm: 0.1, // 缩放倍数 11 | cursor: false, // 是否替换鼠标样式 12 | pointer: false,// 是否开启十字线 13 | bigtext: false,// 是否开启大字幕 14 | overead: false // 是否开启指读 15 | } 16 | 17 | if(Cookies.get(namespace)) { 18 | memory = JSON.parse(Cookies.get(namespace)) 19 | } 20 | memory[key] = value 21 | Cookies.set(namespace, JSON.stringify(memory) , { domain: __domain }) 22 | }, 23 | get:(key, namespace)=> { 24 | let __key = '' 25 | if(Cookies.get(namespace)) { 26 | __key = JSON.parse(Cookies.get(namespace))[key] 27 | } 28 | return __key 29 | }, 30 | remove: (namespace) => { 31 | Cookies.remove(namespace, { domain: __domain}) 32 | }, 33 | setTag: (namespace) => { 34 | const __key = `${namespace}-ignore` 35 | let __data = [] 36 | if(Cookies.get(__key)) { 37 | __data = JSON.parse(Cookies.get(__key)) 38 | } 39 | const { origin, pathname } = location 40 | const __ignoreUrl = `${origin}${pathname}` 41 | !__data.includes(__ignoreUrl) && __data.push(`${origin}${pathname}`) 42 | Cookies.set(__key, JSON.stringify(__data) , { domain: __domain }) 43 | }, 44 | getTag: (namespace) => { 45 | const __key = `${namespace}-ignore` 46 | let __data = [] 47 | if(Cookies.get(__key)) { 48 | __data = JSON.parse(Cookies.get(__key)) 49 | } 50 | return __data 51 | } 52 | } 53 | 54 | const addEvent = (element, type, callback) => { 55 | const ignore = ['DOMContentLoaded'] 56 | let __type = ignore.includes(type) ? type : 'on' + type 57 | if(element.addEventListener){ 58 | element.addEventListener(type, callback, false); 59 | } else if(element.attachEvent){ 60 | element.attachEvent(__type, callback); 61 | } else { 62 | element[__type] = callback; 63 | } 64 | } 65 | 66 | const removeEvent = (element, type, callback) => { 67 | const ignore = ['DOMContentLoaded'] 68 | let __type = ignore.includes(type) ? type : 'on' + type 69 | if(element.removeEventListener){ 70 | element.removeEventListener(type, callback); 71 | } else if(element.detachEvent){ 72 | element.detachEvent(__type, callback); 73 | } else { 74 | element[__type] = null; 75 | } 76 | } 77 | const ruleType = (target) => { 78 | const __role = !!target.getAttribute('role') && target.getAttribute('role').toUpperCase() || target.tagName.toUpperCase() 79 | 80 | const __roleName = { 81 | IMG: '图片', 82 | BUTTON: '按钮', 83 | INPUT: '输入框', 84 | CHECKBOX: '复选框', 85 | RADIO: '单选框', 86 | OPTION: '下拉框', 87 | A: '链接' 88 | } 89 | if(__role == 'INPUT') { 90 | if(target.type == 'radio') { 91 | return '单选框'; 92 | }else if(target.type == 'checkbox') { // button reset file 93 | return '复选框'; 94 | } else if(target.type == 'text') { // button reset file 95 | return '文本框'; 96 | } else if(target.type == 'submit') { 97 | return '提交按钮'; 98 | } else if(target.type == 'reset') { 99 | return '重置按钮'; 100 | } else if(target.type == 'password') { 101 | return '密码输入框'; 102 | } else { 103 | return '输入框'; 104 | } 105 | } 106 | return __roleName[__role] || '文本' 107 | } 108 | 109 | const parseTagText = (target) => { 110 | const __name = ruleType(target) 111 | const __role = !!target.getAttribute('role') && target.getAttribute('role').toUpperCase() 112 | 113 | if (__role === 'A' || target.tagName === 'A') { 114 | console.log('这是一个链接:' + target.alt || target.title || target.innerText); 115 | return `链接 ${target.alt || target.title || target.innerText}`; 116 | } 117 | 118 | if (target.children.length === 0){ 119 | if (__role === 'IMG' ||target.tagName === 'IMG') { 120 | console.log('这是一张图片:' + target.alt || target.title); 121 | return `图片 ${target.alt || target.title}`; 122 | } 123 | if (__role === 'BUTTON' ||target.tagName === 'BUTTON') { 124 | console.log('这是一个按钮:' + target.innerText); 125 | return `按钮 ${target.alt || target.title || target.innerText}`; 126 | } 127 | if (__role === 'INPUT' ||target.tagName === 'INPUT') { 128 | console.log(`这是一个${__name}:` + target.alt || target.title || target.value); 129 | return `${__name} ${target.alt || target.title || target.value}`; 130 | } 131 | 132 | if (__role === 'LABEL' ||target.tagName === 'LABEL') { 133 | const __linkId = target.getAttribute('for') 134 | const __linkDom = document.getElementById(__linkId) 135 | if(!!__linkDom && !!__linkDom.type && __linkDom.type == 'radio') { 136 | console.log(`这是一个单选:` + target.alt || target.title || target.innerText); 137 | return `单选 ${target.alt || target.title || target.innerText}`; 138 | } 139 | } 140 | 141 | if (target.alt || target.title || target.innerText){ 142 | console.log(`${__name} ${target.alt || target.title || target.innerText}`); 143 | return `${__name} ${target.alt || target.title || target.innerText}`; 144 | } 145 | return '' 146 | } 147 | 148 | if (target.children.length < 5 && (target.alt || target.title || target.innerText)){ 149 | console.log(`${__name} ${target.alt || target.title || target.innerText}`); 150 | return `${__name} ${target.alt || target.title || target.innerText}`; 151 | } 152 | 153 | return '' 154 | 155 | } 156 | 157 | const trim = (s) => { 158 | return s.replace(/(^\s*)|(\s*$)/g, ""); 159 | } 160 | 161 | const triggerEvent = (element, eventType) =>{ 162 | var e; 163 | if(element.dispatchEvent){//正常情况 164 | e = new Event(eventType); 165 | element.dispatchEvent(e); 166 | }else if(element.fireEvent){//IE 167 | e = document.createEventObject(); 168 | e.button = 1; 169 | element.fireEvent('on'+eventType,e); 170 | }else if(element['on'+eventType]){ 171 | element['on'+eventType].call(); 172 | } 173 | } 174 | 175 | const ajax = { 176 | get: (url, fn) => { 177 | // XMLHttpRequest对象用于在后台与服务器交换数据 178 | var xhr = new XMLHttpRequest(); 179 | xhr.open('GET', url, true); 180 | xhr.onreadystatechange = () => { 181 | // readyState == 4说明请求已完成 182 | if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { 183 | // 从服务器获得数据 184 | fn(xhr.responseText); 185 | } 186 | }; 187 | xhr.send(); 188 | }, 189 | // datat应为'a=a1&b=b1'这种字符串格式,在jq里如果data为对象会自动将对象转成这种字符串格式 190 | post: (url, data, fn) => { 191 | var xhr = new XMLHttpRequest(); 192 | xhr.open("POST", url, true); 193 | // 添加http头,发送信息至服务器时内容编码类型 194 | xhr.setRequestHeader("Content-Type", "application/json"); 195 | xhr.onreadystatechange = () => { 196 | if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) { 197 | fn(xhr.responseText); 198 | } 199 | }; 200 | xhr.send(data); 201 | } 202 | } 203 | 204 | /** 205 | * 判断是否是IE 206 | */ 207 | const isIE = () => { 208 | if (!!window.ActiveXobject || "ActiveXObject" in window) { 209 | return true; 210 | } else { 211 | return false; 212 | } 213 | } 214 | /** 215 | * 判断是否是IE11 216 | */ 217 | const isIE11 = () => { 218 | if((/Trident\/7\./).test(navigator.userAgent)) { 219 | return true; 220 | } else { 221 | return false; 222 | } 223 | } 224 | 225 | const isFirefox = () => { 226 | const ua = navigator.userAgent 227 | if(ua.indexOf('Firefox') > -1) { 228 | return true 229 | } 230 | return false 231 | } 232 | 233 | const removeNode = (item) => { 234 |   if( isIE()||isIE11() ) { 235 | item.removeNode(true); 236 |   } else { 237 |   item.remove(); 238 | } 239 | } 240 | export { 241 | cookie, 242 | addEvent, 243 | removeEvent, 244 | parseTagText, 245 | ajax, 246 | triggerEvent, 247 | removeNode, 248 | isFirefox, 249 | trim 250 | }; --------------------------------------------------------------------------------