首页
Search
1
解决visual studio code (vscode)安装时没有选择安装路径问题
314 阅读
2
如何在 Clash for Windows 上配置服务
210 阅读
3
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
150 阅读
4
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
148 阅读
5
uniapp打包app提示通讯录权限问题,如何取消通讯录权限
112 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,083
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
1083
篇与
的结果
2024-08-24
Web 可访问性整理
定义 可访问性 Accessibility为障碍用户提供同等用户体验。使障碍用户能对产品感知、理解、定位和交互,并能平等地参与贡献。 可用性 Usability可用性和用户体验设计(User Experience Design)是为了让目标用户使用产品高效、满意地达到特定目标。 包容性 Inclusion包容性设计(Inclusive Design)、通用设计(Universal Design)和人性化设计(Design For All)尽可能让每个人都能容易地用上产品。包容性要解决的问题非常广,包括软、硬件的可访问性和质量、互联网连通性、计算机文化和技能、经济状况、教育、地理位置、语言、以及年龄和残障。 体验屏幕阅读要解决问题首先得知道问题是怎样的,体验屏幕阅读器: ChromeVox ,Chrome 扩展 NVDA ,开源跨平台 VoiceOver (MacOS) 工具 校验 Chrome 自带 Audits aXe 扩展、命令行工具等。 webaim.org 调试 Chrome 自带实验性 Accessibility Inspector chrome://flags/#enable-devtools-experiments Web Developer 扩展 Accessibility Developer Tools 扩展 颜色 Color-Oracle 模拟色盲,开源跨平台 Lea Verous Contrast Ratio WCAG Color Contrast Analyzer 参考规范有什么问题查规范准没错。在自行实现支持 ARIA 的元素时很有用。 Web Content Accessibility Guidelines (WCAG) 2.0 Accessible Rich Internet Applications (WAI-ARIA) HTML 5.1 2nd Edition 其它资料文章 Accessibility, Usability, and Inclusion: Related Aspects of a Web for All Accessibility - W3C Web Fundamentals - Accessibility Common idioms without dedicated elements 本文原文 Web 可访问性整理 视频 Google 的 A11ycasts Accessibility by Thomas Bradley Pragmatic Accessibility: A How-To Guide for Teams (Google I/O '17) 课程 官方教程 Web Accessibility Tutorials Web Accessibility Tab对于纯键盘使用者来说, Tab 键承担了重要责任。Tab 顺序要点: Tab 顺序是按照 DOM 结构,而不是 CSSOM 结构。所以通过 CSS 将元素提前(如 flex order)并不会影响 tab 顺序。在排版的时候需要考虑。其它元素Tab 默认只会识别部分元素,如果需要让其它元素也被识别,加上 tabindex="0" 属性。不鼓励使用大于 0 的其它数字,除了会造成混乱,一些屏幕阅读器也不一定会遵循。应该用 DOM 顺序来体现 tab 顺序。忽略 Tab要让 tab 忽略一个元素,即直接跳过去,设置 tabindex="-1" 属性。或者用还在草稿阶段的 inert 属性 (polyfill)。侧导航栏问题在一些响应式的网页,侧导航栏在宽度变小时可能会隐藏起来,这时用户如果使用 tab 跳转可能会发现焦点突然不见了,怎么按也没反应。其实是因为 tab 跳到导航栏的链接去了。解决方式要么改变 DOM 结构,将导航栏移到最后;要么使用前面提到的 inert 属性。Skip Link将导航的每个项目绝对定位到屏幕之上,再设置 :focus 样式移下来。就可以实现用户按 tab 时一次只显示一个导航链接,用户再按回车即可跳到该位置。可以参考 Github 网页。原生元素尽可能使用原生支持的元素,如 ,而不是用 或 模拟。原因: 原生符合语义。 原生对屏幕阅读友好,否则需要写一堆 ARIA 属性来提示阅读器。 原生不需要 JS 支持。 原生元素用键盘可以代替鼠标点击,否则还要监听键盘事件。 Focus Ring 浏览器自动识别原生元素,否则要另外写 CSS 隐藏。 CSS 很容易就能改变原生元素的样式,与整体设计统一。 语义标签使用符合语义的标签,辅助设备会自动理解。 :完整、独立的内容。 每篇 应该包含一个标题(-)。 嵌套的 主题应该跟父 相关联。 考虑使用 应该看其内容是否会出现在文档的提纲(outline)中。 辅助设备会将其 role 理解为“article”。 :按主题归在一起的部分内容。 每块 应该包含一个标题(-)。 如果内容是完整、独立的,应该考虑使用 。 不要将 当 使用,只为样式时应该用 。 考虑使用 应该看其内容是否会出现在文档的提纲(outline)中。 辅助设备会将其 role 理解为“region”,见下方。 :包含一系列链接可以跳转到其它页面或者本页面的某部分。 使用列表元素帮助辅助设备理解。 只为主要导航使用。 辅助设备会将其 role 理解为“navigation”,见下方。 :侧边栏,其内容应该不属于主体内容的一部分。 可以放导航组、广告等。 辅助设备会将其 role 理解为“complementary”,见下方。 -:一个块或子块的标题。 请勿为了样式而用标题来表示副标题、子标题、额外标题、标语等,变通可参考这里。 辅助设备会将其 role 理解为“heading”加上“1”到“6”。 :对最近的父 sectioning content 或 sectioning root 的介绍。 如果父 sectioning root 是 ,辅助设备会将其 role 理解为“banner”,见下方。 :代表其最近的父 sectioning content 或 sectioning root 的页脚。 如果父 sectioning root 是 ,辅助设备会将其 role 理解为“content information”,见下方。 :为其最近的父 或 元素提供联系信息。 只提供必要的联系信息,不要包含其它信息。 sectioning root 包括 , , , , , .Sectioning content 包括 , , , .LandmarksLandmark roles 定义网页的几个主要部分,可以让辅助设备快速跳转。总共有八个。 banner:唯一。跟网站相关的内容,如 logo 、赞助商、站内搜索等。 complementary:对主体内容的补充。与主体内容相关,但本身独立。如相关文章。 contentinfo:唯一。版权信息、隐私声明等。 form:表单。应该设置可见的标题,并用 aria-labelledby 引用标题来告知辅助设备这个表单是干什么的。如果用 JS 提交表单,没有触发 onsubmit 事件,则应该用其它方式通知表单提交。 main:唯一。主体内容。 navigation:导航栏。 region:认为用户可能会感兴趣的,需要让用户可以快速跳转的内容。辅助设备会收集它来生成一个概要页面。每个 region 必须有可见的标题,并用 aria-labelledby 引用。 search:搜索框。 视觉隐藏使用视觉隐藏而不是 display: none; 来隐藏元素同时让辅助设备识别。.visually-hidden { position: absolute; overflow: hidden; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; clip: rect(0 0 0 0); }【完】
2024年08月24日
5 阅读
0 评论
0 点赞
2024-08-24
理解 RxJS :四次元编程
学习 RxJS 最大的问题是官方造了很多概念,但文档又解释得不太全面和易懂,需要结合阅读各种文章(特别是 Ben Lesh 的,包括视频)。本文试图整体梳理一遍再用另外的角度来介绍,希望能帮助初学者或者对 RxJS 的一些概念比较含糊的使用者。为什么需要 RxJSRxJS 属于响应式编程,其思想是将时间看作数组,随着时间发生的事件被看作是数组的项,然后以操作数组的方式变换事件。其强大的地方在于站在四维的角度看问题,这就像是拥有了上帝视野。在处理事件之间的关系时,对于传统方式,我们需要设置各种状态变量来记录这些关系,比如对点击 Shift 键进行计数,需要手动设置一个 let shiftPressCount: number,如果需要每 600ms 清零,又需要添加计时的状态,这些状态都需要手动维护,当它们变得复杂和庞大的时候我们很快就会乱了,因为没有明确的方向,不好判断这些状态同步了没有。而这正是 RxJS 发光发热的地方。因为从四维的角度看,这些状态就不是单个变量,而是一系列变量。比如对按键计数:Rx.Observable.fromEvent(document, 'keydown') .filter(({ key }) => key === 'Shift') .scan(count => count + 1, 0) .subscribe(count => console.log(`按了${count}遍 Shift 键`))相信有使用过数组方法的人第一次看也大概能知道这里干了些什么(把 scan 看作是会输出中间结果的 reduce)。中间状态都在变换的过程中被封装起来,每一次事件的 count 都是独立的,不容易乱,也使得可以用纯函数去表达状态的变换。链式调用(或者 RxJS5 的 pipeable)在一定程度上限制了状态数据的流动方向,增加了可预测性,更加容易理解。理解 RxJS基本概念使用 RxJS 前先理解它要做什么,这里引入了两个概念,Producer (生产者)和 Observer (观察者)。先看一个熟悉的例子:document.addEventListener('click', function handler (e) { console.log(e.clientX) })这里的 Producer 是 DOM 事件机制,会不定期产出 MouseEvent 事件。Observer 就是 handler,对事件作出反应。再看前面的例子:Rx.Observable.fromEvent(document, 'keydown') .filter(({ key }) => key === 'Shift') .scan(count => count + 1, 0) .subscribe(count => console.log(`按了${count}遍 Shift 键`))Producer 还是 DOM 事件机制,Observer 是 subscribe 的参数。所以可以理解 RxJS 为连接 Producer 和 Observer 的纽带。于是这个纽带的成分叫 Observable (可被观察的)就不难理解了。Observable 就是由事件组成的四维数组。RxJS 将 Producer 转换为 Observable,然后对 Observable 进行各种变换,最后再交给 Observer。对 Observable 进行变换的操作符叫做 Operator,比如上面的 filter 和 'scan',它们输入 Observable 再输出新的 Observable。RxJS 有巨量的 Operators ,这也是学习 RxJS 的第二难点,我已经分类整理了六十多个,整理完会再写一篇文章介绍,敬请关注。创建 ObservableRxJS 封装了许多有用的方法来将 Producer 转换为 Observable,比如 fromEvent、fromPromise,但其根本是一个叫 create 的方法。var observable = Rx.Observable.create(observer => { observer.next(0) observer.next(1) observer.next(2) setTimeout(() => { observer.next(3) observer.complete() }, 1000) })这其实跟 Promise 的思路很像,Promise 只能 resolve 一遍,但这里可以 observer.next 很多个值(事件),最后还能 complete(不是必须的,可以有无限事件)。官方把这个类 resolve 的参数也叫做 observer,因为 observer.next(0) 的意思是“Subscribe 我的那个 Observer 接下来会获得这个值 0”。我认为这是一个不好的决定,重名对于新人太容易混淆了,这个其实可以从另一个角度看,把它叫做 producer,“产生”了下个值。Subscribe 不是订阅者模式一个常见的误解是认为 RxJS 就是 addEventListener 那样的订阅者模式,subscribe 这个方法名也很有误导性。然而两者并不是一回事,订阅者模式会维护一个订阅者列表,事件来了就一一调用列表上的每个订阅者传递通知。但 RxJS 并没有这么一个列表,它就是一个函数,可以跟 Promise 类比,Promise 的 executor 是在 new Promise(executor) 时马上执行的,而 RxJS Rx.Observable.create(observer) 的 observer 则是在每次执行 subscribe 后都调用一遍,即每次 subscribe 的 Observables 都是独立的,都会重新走一遍整个流程。这个时候你也许会想,这样每次都完整调用一遍岂不是很浪费性能?没错,如果需要多次 subscribe 同个 Producer 这么做会比较浪费,但如果只是 subscribe 一遍,维护一个订阅者列表也没有必要。所以 RxJS 引入了 Hot 和 Cold Observable 的概念。Hot & ColdObservable 冷热概念其实就是看 Producer 的创建受不受 RxJS 控制。前面我们知道,create 会将 Producer 转化为 Observable 。如果这个 Producer 也是在 create 回调里面产生的,那么就是 Cold ,因为 Producer 还不存在,只有 subscribe 了之后才会被创建。但如果 Producer 在之前就创建了,比如 DOM 事件,create 回调里仅仅是对 Producer 添加 listener,那么这就叫做 Hot ,因为不需要 subscribe 来启动 Producer 。只有 Hot Observable 才可以实现订阅者模式。可以通过一个特殊的 Observable 叫 Subject 来创建,其内部会维护一个订阅者列表。通过 share 方法可以将一个 Cold 的 Observable 转换为 Hot 。原理是内部用 Subject subscribe 上流的 Observable 实现转接。使用 RxJS理解了基本概念之后就可以直接开写了,本身没有什么魔法,参考一下 api 依样画葫芦即可。使用 RxJS 最常见的问题是不知道什么时候该用哪个 Operator 。这其实跟数组操作是一样的,RxJS 提供了数量庞大的 Operators ,基本覆盖了各种可以想到的数组操作,建议先从 JavaScript 常见的数组操作开始,如 map、filter、scan(也有 reduce ,但这个通常不是我们想要的,我们一般不需要在 complete 之后才输出结果,而是每次都输出阶段性的结果)。多翻官方文档,常用的 Operators 都描述得非常详细,有弹珠图(Marble Graph)和一句话总结;缺点是措辞有时可能会比较抽象,不是那么好理解。另外就是第三方的 learnrxjs 和 Rxjs 5 ultimate,按作者的思路组织,更通俗易懂些,可以作为补充理解;缺点是可能跟官方不同步,以及不全。我整理完也会再写一篇文章介绍,敬请期待。
2024年08月24日
6 阅读
0 评论
0 点赞
2024-08-24
深入 React Render Props 模式
随着 React 的新 Context API 出来,render props 模式再次发挥重要作用。本文将尝试深入理解 render props 的利弊,并结合高阶组件寻找合适的处理方式。基础先看官方给出的简单例子: ( Hello{data.target} )}/>加个 DataProvider 的简单实现,class DataProvider extends React.Component { state = { target: '' } handleMouseMove = e => this.setState({ target: e.target.title }) render() { return ( {this.props.render(this.state)} ) } }这里是将一个返回 React 元素的函数传给 DataProvider 的 props.render,DataProvider render 的时候调用 this.props.render(this.state) 渲染这个函数。这也是“render props”名字的来源,而现在更流行的是“children props”,虽然依然沿用 render props 的说法。Children props 即将 render 换为 children ,同时 JSX 中不需要显式写 chidlren ,所以成了这个样子:{data => Hello{data.target}}子组件通过 render props 传进父组件渲染,让父组件不再依赖子组件,达到重用父组件的目的,即依赖反转。Render Props 与 SFC前面提到的“返回 React 元素的函数”,一看是不是跟无状态函数组件(Stateless Fuctional Component)的签名很接近。这么看:{props => Hello{props.target}}但不一样的地方在于 render props 只是一个普通函数,是直接函数调用。且 inline render props 可以跳过 DataProvider 直接访问其它父组件:const App = props => ( {data => Hello{data.target}{props.target}} )这就造成了 DataProvider 不能优化为纯组件。这也是非常不好的习惯,一个解决方式是人为限制 render props 为 SFC 从而让只有一个数据来源。理想很美好,但没法强制所有人这么干。Render Props 与 HOC如果将 render props 限制为传入子组件,其实很容易联想到高阶组件(Higher-Order Components)。高阶组件是一个函数,接受一个组件,返回新的组件。所以可以用高阶组件包装起来,并隐藏 render props 接口。const withData = Base => () => {Base} const BaseTarget = props => Hello{props.target} const EnhancedComponent = withData(BaseTarget) const App = () => 这样就限制了 DataProvider 的使用方式。如果需要多方数据,可以修改 DataProvider 为 this.props.render({ ...this.props, ...this.state }) 将其它数据作为 props 传入 DataProvider。const withData = Base => props => {Base} const BaseTarget = props => Hello{props.target} const EnhancedComponent = withData(BaseTarget) const App = props => 「完」
2024年08月24日
5 阅读
0 评论
0 点赞
2024-08-24
选中鼠标附近的文字
最近终于抽空给 Saladict 实现了鼠标悬浮取词功能,使用了较为简洁的实现方式,这里分享一下原理以及坑的处理。初尝试这个需求其实很早就被人提 issue 了,当时做了一番搜索,最后尝试了 document.caretPositionFromPoint / document.caretRangeFromPoint ,效果不太理想。如果看 mdn 给的例子,就会发现,它是遍历每个元素添加事件的。这么做的原因是当使用这个方法的时候,如果鼠标指向元素空白的地方,它会就近取位置。所以例子通过给粒度更细的元素绑定来避免这个问题。然而实际上这么做还是不足够的,一个段落末行也许只有几个字符,这时空出接近一行,也会有上面的问题。所以当时就搁置了这个功能。灵感直到最近,看到一个同类的开源划词翻译扩展 FairyDict 实现了取词功能,遍观摩了一番源码。它的原理是深度优先递归遍历这个元素以及其子元素,通过不断试探选中区域,并与鼠标座标对比来定位确切位置。有没有发现问题,这个遍历过程不正是上面 document.caretPositionFromPoint 干的事么,那么我们只需要最后量一下鼠标是否在取词范围中即可。原理现在总结一下原理: 通过 document.caretPositionFromPoint 获得鼠标所指最接近的元素以及文本位置 offset。 找出 offset 最接近的单词。 通过 Range 获得部分文本(单词)的尺寸和座标。 验证鼠标此时在单词区域范围中。 选中这个单词。Selection 支持直接添加 Range 。 实现按原理来实现就很简单了。本文上按 alt 可体验取词效果。/** * @param {MouseEvent} e * @returns {void} */ function selectCursorWord (e) { const x = e.clientX const y = e.clientY let offsetNode let offset const sel = window.getSelection() sel.removeAllRanges() if (document['caretPositionFromPoint']) { const pos = document['caretPositionFromPoint'](x, y) if (!pos) { return } offsetNode = pos.offsetNode offset = pos.offset } else if (document['caretRangeFromPoint']) { const pos = document['caretRangeFromPoint'](x, y) if (!pos) { return } offsetNode = pos.startContainer offset = pos.startOffset } else { return } if (offsetNode.nodeType === Node.TEXT_NODE) { const textNode = offsetNode const content = textNode.data const head = (content.slice(0, offset).match(/[-_a-z]+$/i) || [''])[0] const tail = (content.slice(offset).match(/^([-_a-z]+|[\u4e00-\u9fa5])/i) || [''])[0] if (head.length
2024年08月24日
12 阅读
0 评论
0 点赞
2024-08-24
React 黑魔法之 Portal + SyntheticEvent + iframe
在实现划词扩展的时候,查词面板等模块需要植入到源网页,为了更方便地隔离样式污染,植入的模块均使用了 包装。在前一个 Vue 实现的版本 Saladict 5 中,有几个不太舒服的小小小小地方: 内外事件不通。 内外环境不一样,变量不能共用,需要 postMessage 交流。 Vue 组件必须要有一个根元素,且 v-if 隐藏之后元素原地还是会留下注释标记。在浏览器审查元素时看起来不太干净。 当然这个不影响呈现效果,所以当时也不怎么纠结了。然而后来在逛 React 文档的时候,意外发现了 Portal 这个神器。Portal 精妙的地方在于从 React 的角度(也是代码作者的角度),组件可以保持原来的结构,但实际可以渲染到任意地方(甚至是其它窗口!)。更令人拍案叫绝的是, SyntheticEvent 也是抽象到 React 层的,所以组件事件捕获冒泡全正常使用。对于 需要做些小改变,有人发现,只需要把 组件上所有事件 null 掉即可。这样就方便地解决了前两个小痛点了!最后一点的解决方式是在组件不显示的时候将 Portal 的载体元素 remove 掉,显示时再 appendChild 回去。甚至配合动画也非常简单,在动画结束时 remove 即可。最后如果对实现感兴趣的话可以参考: 封装的 PortalFrame 组件。 在 PortalFrame 外部监听内部事件。 与动画组件配合移除载体元素。 值得一提的是,也有人吸取灵感实现了 Vue 版的 Portal。然而这个功能没有得到官方青睐,只是社区实现。由于没有内核权限,相比于 React Portal 这仅是部分实现。
2024年08月24日
8 阅读
0 评论
0 点赞
1
...
142
143
144
...
217