首页
Search
1
解决visual studio code (vscode)安装时没有选择安装路径问题
321 阅读
2
如何在 Clash for Windows 上配置服务
216 阅读
3
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
150 阅读
4
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
149 阅读
5
uniapp打包app提示通讯录权限问题,如何取消通讯录权限
113 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,205
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
768
篇与
的结果
2024-10-20
页面中长列表滚动的优化
当我们有 10 万条或者更多的数据需要展示在页面中,我们应该怎样处理呢?这里主要的方式有两种: 懒加载:即监听 scroll 事件或使用IntersecionObserver监听; 可视区域的渲染:仅在可视区域展示数据,为保证滚动条的完整性,非可视区域使用占位元素的高度后者容器的位移来撑开。 1. 懒加载 # 懒加载的方式有两种:监听 scroll 事件或使用IntersecionObserver监听某个元素。 1.1 scroll 事件 # 监听滚动事件应该是我们使用的最多的事件了,当滚动条滑动到页面最底部或者将要滑动到最底部的时候,去加载下一页的数据。同时图片懒加载或者其他组件的懒加载也可以依赖于滚动事件!为了优化滚动事件,我们也会给滚动事件添加上防抖和节流。这是一个很常见的 demo,无限加载数据系列,当滚动条滑动到底部时,加载下一页的数据:监听滚动事件懒加载数据。请分别用微信和 QQ 扫下这个二维码体验一下,体验时,请点击“向左”的按钮关闭代码的部分: 其实我们发现会有一个很有意思的体验:在 android 中的微信和 QQ 中基本可以无限的滑动,在 iOS 的微信中也可以,但是在 iOS 的 QQ 中,当我们一直滑动时,需要滑动到底部,然后才能加载新的数据,才能继续滑动!这种原因是 iOS 不同的 webview 造成的。在 iOS 中,UIWebView 是在 iOS2 中出现的,性能更好的 WKWebView 跟随 iOS8 一起出现。从性能方面来说,WKWebView 会比 UIWebView 高很多,可以算是一次飞跃。它采用了跨进程的方案,用 Nitro JS 解析器,高达 60fps 的刷新率。同时,提供了很好的 H5 页面支持,类比 UIWebView 还多提供了一个加载进度的属性。同时,在 UIWebView 中,无论是页面的 body 滚动,还是 div 级别的局部滚动,都不会实时触发,而是在滚动结束后才会触发!可以分别体验下面的两个二维码,左侧的为 body 级别的滚动,右侧为 div 的局部滚动: 总结下滚动事件在不同的 webview 中的表现情况: 机型 body 滚动 局部滚动 iOS WKWebView 实时触发 实时触发 iOS UIWebView 无法实时触发 无法实时触发 Android 实时触发 实时触发 那么该如何优化滚动的性能呢?首先就是防抖和节流。在 Chrome 浏览器中使用performance.now()测试滚动每次触发的间隔,可以看到,频率大概在 16ms 左右,但很多时候我们并不需要这么快地触发我们的监听函数,而且,比如图片懒加载等场景,也要及时的更新一次,这时候就用到了防抖和节流!再有就是使用requestAnimationFrame与requestIdleCallback代替定时器。若使用定时器进行间歇性触发监听函数,会出现掉帧的情况,我们也知道setTimeout(fn, 100),不一定是正好在 100ms 后触发 fn 函数,而是等浏览器空闲之后才调用 fn 函数,当多次积累后就会产生掉帧的,那么使用requestAnimationFrame或requestIdleCallback,则浏览器会根据自己渲染的频率适时地执行回调。在追求高性能的渲染效果时,可以考虑用 requestIdleCallback()和 requestAnimationFrame()代替定时器。前者适合流畅的动画效果场景,后者适用于分离一些优先级低的操作逻辑,使用时需要考虑清楚 1.2 IntersecionObserver # 使用 IntersecionObserver 也可以实现无限滚动,比如在底部监听一个透明的元素,当该元素可见时就加载新的资源!demo: IntersecionObserver 实现无限滚动不过也应当看到的是 IntersecionObserver 的支持程度还不太好,如果要使用的话,还是需要 polyfill 方案!而且 IntersecionObserver 在图片懒加载和组件懒加载的过程中,非常有用,我们后续会进行了解! 2. 模拟滚动 # 模拟滚动最代表的例子就是iScroll,监听手势的 touchmove 事件,然后使用 CSS3 中的 transform 产生位移。不过在模拟滚动的过程中,若图片比较多时,可以感觉到滚动时有明显的卡顿感。查看这个样例:https://www.xiabingbao.com/demos/20190423/iscroll-scroll.html。这是利用 IScroll 中的scroll事件,无限地向页面中添加元素。不过当页面的数据是无限加载时,应当使用iscroll-infinite这个类库更好,这部分留到后面讲解! 3. 可视区域数据的渲染 # 上面的几种方法里,我们都是无限地直接往页面中添加元素,滑动的页码越大,页面中的 DOM 元素也就越多。但实际上,这是没有必要的,滚动到可视区域外的元素,用户是看不到的,也没不要保留在页面中,可以用一个占位元素或者 transform 撑开上面所有不可见元素的高度,让滚动条能够正常的上下滑动即可!撑开上半部分不可见区域的高度,有两种方法,一种是在容器内的最顶部设置一个占位元素,这个占位元素的高度就是消失的所有 DOM 的高度;再一个就是给容器或者占位元素一个 transform 的位移!这里我们使用给占位元素一个高度撑起顶部的滚动区域。 3.1 固定高度的 item # 若每个 item 的 height/margin/padding 等数据都是固定写死的,这种情况比较容易实现!获取一次数据后,不用每次都重新计算。每次滚动时,都要计算应当渲染列表的哪部分!start表示列表开始的位置,fixedScrollTop表示顶部占位元素的距离// 滚动处理函数 function handleScroller() { let lastStart = 0; // 上次开始的位置 const item = document.querySelector('.container .item'); const itemStyle = getComputedStyle(item); const itemHeight = item.offsetHeight + parseInt(itemStyle['marginTop']) + parseInt(itemStyle['marginBottom']); return function () { const currentScrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop); const fixedScrollTop = currentScrollTop - (currentScrollTop % itemHeight); let start = Math.floor(currentScrollTop / itemHeight); // 可视区域开始渲染的位置 if (lastStart !== start) { lastStart = start; createDom(start, count, fixedScrollTop); } }; } 设置顶部滚动的高度:function createDom(start, count, height) { const container = document.querySelector('.container'); // container.style.transform = `translateY(${height}px)`; // 给容易一个位移 let div = document.createDocumentFragment(); // 创建占位元素 if (height) { let p = document.createElement('p'); p.style.height = height + 'px'; div.appendChild(p); } for (let i = start, len = start + count; i < len; i++) { let item = document.createElement('div'); item.className = 'item'; item.innerHTML = i; div.appendChild(item); } // 为了方便处理,我们这里采用了更新container中的全部元素 // 你也可以尝试只增加/删除首位的元素,中间元素不变 container.innerHTML = ''; container.appendChild(div); } 可以点击链接查看样例:可视区域渲染之 DOM 元素增减,审查元素可以看得更清晰,无论我们怎么滚动,.container中永远只有那么几个元素! 3.2 每个 item 的高度都不一定 # 待定,有个问题一直搞不定 3.3 DOM 的复用 # 重点来了,在上面的章节里,我们进行了进一步的优化,只渲染可视区域内的数据。但是还是存在一个重要的问题:频繁的改动 DOM,这样会频繁的引起页面的重绘。这里我们的思想是对页面中 DOM 元素进行复用,如下图中所示,从上面滑出的元素,可以直接定位下面再重新装填元素,反之亦然!可以点击链接查看 demo,在审查元素中我们可以看到,在滚动的过程中,DOM 并没有被删掉,而是改变了 transform,放到了最下面:可视区域渲染之 DOM 元素复用。// 更新页面中DOM元素的位置 function updateDom(start, count, itemHeight, height) { document.querySelector('.container .content').style.transform = 'translateY(' + height + 'px)'; for (var i = start, len = start + count; i < len; i++) { var index = i % count; var cssIndex = (i - start) % len; document.querySelector('.item' + index).innerHTML = i; document.querySelector('.item' + index).style.transform = 'translateY(' + itemHeight * cssIndex + 'px)'; } } 这里我们还是监听了元素的 scroll 事件,我们在上面一笔带过的iScroll-infinite类库,是使用了模拟滚动+DOM 复用。点击链接查看 demo:iScroll-infinite 实现的无限加载。 4. 总结 # 最有效的方案应当是模拟滚动+DOM 复用,既解决了滚动不能实时生效的问题,又能解决页面中 DOM 元素过多,造成滚动中卡顿的问题。总是想着要讲的有很多,不过最后好像哪方面都欠缺了一点,后面有机会再针对其中的某一个点再深入进行探讨!例如使用IntersecionObserver实现 Vue 组件的懒加载,如何自己实现一个简单的模拟滚动等。参考: 自定义事件;
2024年10月20日
14 阅读
0 评论
0 点赞
2024-10-20
基于观察者模式实现一个EventEmitter类
我们在之前的文章中讲解中发布订阅者模式,不太了解这种模式的,可以先点击查看之前的文章浅谈javascript设计模式之发布订阅者模式。从发布订阅者模式的思想出发,来实现一个EventEmitter模块。在实现一些基础类库时,我们是经常需要用到EventEmitter模块的,比如在微信中下载某个APP时,前端需要了解当前下载处在哪个状态:刚开始下载、在在下载中、下载的进度、下载已完成、下载失败等等,都是需要我们向发布事件的。那么对应的就是实现emit和on。 1. 简单的实现 # 一个简单的EventEmitter模块,是只至少要实现emit和on两个方法:class EventEmitter { handles = {}; on(evName, listener) { if (!this.handles[evName]) { this.handles[evName] = []; } this.handles[evName].push(listener); } emit(evName, ...args) { const handler = this.handles[evName]; if (handler) { for(let i=0, len=handler.length; i console.log(step)); // 监听下载的进度 downapp.emit('steps', 0); // 输出: 0 downapp.emit('steps', 30); // 输出: 30 downapp.emit('steps', 100); // 输出; 100 也可以被其他类继承后使用:class People extends EventEmitter { constructor() { super(); } start() { let num = 10; let timer = setInterval(() => { this.emit('countdown', num--); if (num console.log('Tom', num)); Tom.start(); const Jerry = new People(); Jerry.on('countdown', num => console.log('Jerry', num)); Jerry.start(); 2. 对EventEmitter扩展方法 # 基于emit和on我们可以扩展出多个方法,方便使用: 方法名 说明 on(eventName, listener) 监听所有触发的事件 emit(eventName, ...args) 主动触发事件 once(eventName, listener) 与on事件类似,但只监听一次事件 off(eventName, listener) 移除某个事件 offAll([eventName]) 移除所有的事件,若传入具体的eventName,则移除该名称下所有的事件 newListener 有添加新的监听器时触发 removeListener 有监听器被移除时触发 2.1 off # 在如何实现once的方法时,必须得先知道事件是如何移除的:class EventEmitter { off(evName, listener) { if (this.handles[evName]) { // 从数组中移除lisnter this.handles[evName] = this.handles[evName].filter(fn => fn!==listener); } } } // 接上面的People类 // const Tom = new People(); const listener = (num) => { console.log('Tom', num) } const listener2 = (num) => { console.log('Tom', num*num); } Tom.on('countdown', listener); Tom.on('countdown', listener2); Tom.on('countdown', listener); Tom.off('countdown', listener); // 移除所有的listener Tom.start(); // 只有listener有输出 通过Array.prototype.filter方法,过滤掉所有要移除的监听器。注意,跟为DOM添加和移除事件一样,不能传入匿名函数,例如下面的例子是不生效的,因为两个匿名函数虽然从函数体上看起来一样,但两者是不相等的:Tom.on('countdown', (num) => {console.log(num)}); Tom.off('countdown', (num) => {console.log(num)}); // 移除 Tom.start(); // 依然有输出 2.2 once # once,只要监听到第1个事件后,则立刻移除监听器,后续不再监听此事件:Tom.once('countdown', (num) => {console.log(num)}); // 10 Tom.start(); countdown事件会从10每秒输出一个数字,至到0为止。但我们使用once后,只输出了一个10后,则不再有后续的输出,说明监听器已经被移除。 2.3 newListener和removeListener # 这两者都是表示监听器有变化时,程序主动触发的:class EventEmitter { on(evName, listener) { if (!this.handles[evName]) { this.handles[evName] = []; } this.handles[evName].push(listener); // 有新增事件时,触发newListener this.emit('newListener', evName, listener); return this; } off(evName, listener) { if (this.handles[evName]) { // 从数组中移除lisnter this.handles[evName] = this.handles[evName].filter(fn => { if (fn!==listener) { // 监听器有移除时,触发removeListener this.emit('removeListener', evName, listener); return true; } return false; }) } } } 那么这两个如何使用的呢,其实once这一个监听事件,就能触发上面的这两个监听器。先添加一个countdown监听器,执行一次监听后,马上移除countdown中的监听器:const listener = (num) => { console.log('Tom', num) } Tom.on('newListener', (evName, listener) => console.log('newListener', evName, listener) ); Tom.on('removeListener', (evName, listener) => console.log('removeListener', evName, listener) ); Tom.once('countdown', listener); Tom.start(); 3. 完整的EventEmitter模块 # 我们从上面的章节中,已经了解了EventEmitter的原理,并实现了几个常用的模块。下面我们把完成的代码展示出来:class EventEmitter { handles = {}; on(evName, listener) { if (!this.handles[evName]) { this.handles[evName] = []; } this.handles[evName].push(listener); this.emit('newListener', evName, listener); return this; } once(evName, listener) { let fired = false; let magic = (...args) => { this.off(evName, magic); if (!fired) { fired = true; listener.apply(this, args); } } this.on(evName, magic); return this; } off(evName, listener) { if (this.handles[evName]) { // 从数组中移除lisnter this.handles[evName] = this.handles[evName].filter(fn => { if (fn!==listener) { this.emit('removeListener', evName, listener) return true; } return false; }) } } emit(evName, ...args) { const handler = this.handles[evName]; if (handler) { for(let i=0, len=handler.length; i
2024年10月20日
7 阅读
0 评论
0 点赞
2024-10-20
React:实现一个带有loading效果的按钮组件
在业务经常需要点击按钮去请求接口,在接口还未返回结果前,需要给用户一个等待的感觉,同时锁住按钮,防止产生二次点击!我们实现带有loading效果的按钮组件,主要是实现以下的几个功能: 有菊花或者圈圈的loading图标,且loading颜色与字体颜色相同; loading的过程中,点击无效; 1. 按钮的结构 # 针对第1个功能,我最开始是把接口返回结果之前的loading集成到了按钮中,这个loading是用多个div拼接而成的,而loading的颜色则是渲染div中的after伪元素的背景色:/** * .lds-spinner div:after { content: ''; display: block; position: absolute; top: 3px; left: 29px; width: 5px; height: 14px; border-radius: 20%; background: #f3434a; } */ render() { return ( { Array(12).fill(0).map((item, index) => ) } ); } 这种方式如果要loading效果跟按钮字体保持一个颜色,则需要通过props传入一个色值,然后在Button组件中才能修改。实在是不太方便,我既要在CSS中设置按钮的颜色,还要通过props传给Button组件,当按钮的字体颜色需要更新时,则需要修改两个地方。再一个,loading图案的大小也要跟着按钮的大小进行变化,而div整体不太好调整,能想到的方法是使用transform: scale来对loading图案放大或者缩小。这时,loading效果用SVG实现最好了,配上currentColor,能天然继承其父级元素的color值。再使用em单位适应按钮的大小:render() { return ( 立即领取 ); } 2. loading效果 # 按钮最少要接收2个参数,一个是表示是否正在loading中,再一个是接收click事件:/** .i-button { .i-icon-loading { margin-right: 8px; } } .i-button-loading { position: relative; &:before{ content: ''; position: absolute; background-color: #ffffff; opacity: 0.4; top: -1px; right: -1px; bottom: -1px; left: -1px; z-index: 1; transition: opacity .2s; border-radius: inherit; } } */ interface ButtonProps { loading: boolean; onClick: React.MouseEventHandler; } export default class Button extends React.Component { handleClick = (e: any) => { const { loading, onClick } = this.props; if (!!loading) { // 正在loading中时,直接返回 return; } if (onClick) { (onClick as React.MouseEventHandler)(e); } } render() { const { loading } = this.props; // 将loading效果提取出来 const iconNode = loading ? : null; // loading时添加loading的class const loddingClass = loading ? ' i-button-loading' : ''; return ( {iconNode} {this.props.children} ); } } loading的icon组件如下:/** .i-icon-loading { font-size: 36px; transition: transform .3s ease-in-out; transition: transform .3s ease-in-out; will-change: transform; display: inline-block; color: inherit; font-style: normal; line-height: 0; text-align: center; text-transform: none; vertical-align: -0.125em; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; .i-icon-loading-spin { display: inline-block; animation: loadingCircle 1s infinite linear; } @-webkit-keyframes loadingCircle { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg) } } @keyframes loadingCircle { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg) } } } * */ export default class IconLoading extends Component { render() { return ( ); } } 3. 总结 # 第一次开始使用React写项目,之前都是用Vue在写项目。用Vue写了几个项目了,想着换一种技术栈来写项目,React跟Vue有一些相似之处,不过还是有些不同的,感觉React中很多地方需要自己完善,比如react的路由守卫beforeEach,CSS没有scoped属性,老担心不同的组件之前会产生样式冲突,虽然可以使用*.module.css来规避,不过这样在jsx中写className就不太方便了!另一方面来讲,写React特别能锻炼自己的js能力,比如高阶组件、ES6等新特性的使用,同时react和typescript结合的特别好。以后,随着对React更深入的了解,当然,这些也就不是问题了!参考:https://www.zhangxinxu.com/wordpress/2014/07/svg-sprites-fill-color-currentcolor/
2024年10月20日
16 阅读
0 评论
0 点赞
2024-10-20
实现react组件的递归调用
项目中有侧边栏的层级菜单或者需要根据接口生成DOM树等操作时,这里就需要用到了组件的递归调用。在本篇文章里,我们使用React来简单实现下组件的递归。在React可以通过props和children来向子组件传递信息,不过在这里大致的思路都是一样的。我们先定义一下要渲染的json结构:list = [ { name: '1', children: [ { name: '1-1' }, { name: '1-2', children: [ { name: '1-2-1' } ] }, { name: '1-3' } ] }, { name: '2', children: [ { name: '2-1' } ] } ] 我们的Item组件可以这样实现:render() { const list: any = this.props.children || []; return ( { list.map((item: ItemData, index: number) => { return {item.name} { // 当该节点还有children时,则递归调用本身 item.children && item.children.length ? {item.children} : null } }) } ) } 最后调用这个Item组件:{this.list}
2024年10月20日
13 阅读
0 评论
0 点赞
2024-10-20
能用js重写的都会被typescript重写
没用过typescript可能会下面的两个疑问: javascript越来越完善,还有没有必要学习typescript? typescript学习的成本如何呢? 1. typescript的生态 # 针对这两个疑问我们来稍微地解答下:typescript是javascript的超集,所有es2016, es2017, es2018等等,包括未来的标准就会被包含在typescript中:尤其在多人合作的项目中,typescript能强制开发者的标准,对其他的开发者能天然的做到文档提示的功能,例如这段代码:/** * 判断当前商品的状态 * qcoin/object/redpack/flow类型的商品里,1表示待领取,2表示已领取; * code,url等类型的商品,会直接进行发送,1就表示已领取,没有状态2 * 统一后:state: 0:未成功, 1:待领取, 2:已领取, 3:已过期, 4:已下架 * * @param state 当前商品的状态 * @param type qcoin(QB), object(实物), code(兑换码), url(纯链接), redpack(现金红包), flow(流量型) */ const getOrderState = (state: number, type: string): number => { if (state === 1 && (type === 'code' || type === 'url')) { return 2; } return state; }; 在调用getOrderState方法时,能直接提示该方法有几个参数,每个参数的类型是什么,返回的数据是什么类型的,如果错误地调用了该方法,则在编写阶段就会提示出来!同时通过Google的搜索量上来看,大概有1850万的搜索结果,typescript的生态是越来越好。而且,typescript对angular和react有着优良的支持,在使用react编写代码的过程中,同时尤雨溪也在用typescript对Vue3.0进行重写。之前尤雨溪也在知乎上说压flow压错了宝,typescript真香https://www.zhihu.com/question/310485097/answer/591869966。 2. typescript的学习成本 # typescript的学习非常的低,针对一些平常的操作,随手就可以写上相应的类型。function sum(a: number, b:number): number { return a + b; } sum(23, 56); 针对比较复杂的数据类型,可以通过interface来实现:interface GoodsItem { cost: number; // 现价 cover: string; // 图片 customer_service: string; // 规则 desc: string; // 描述 discount_score: number; // 积分价格 icon_url: string; // icon prize_enname: string; prize_zhname: string; rule: string[] | string; // 规则名 score_cost: number; // 积分兑换的价格 sending_count_today: number; // 今日发放的上限 sending_count_total: number; // 总量 short_name: string; // 短名称 sort: number; // 权重 state: number; // 状态 type: string; // 类型 validity: string; // 有效期 } let goods: GoodsItem; // 使用GoodsItem来定义goods变量 当然,对一些更加复杂的,比如泛型等则需要特意地学习下。这里可以看下知乎上总结的typescript骚操作。 3. 总结 # 最开始写页面时,使用的是Vue,那时候就是纯按照教程上的方式来编写的,例如data, computed, watch等方法。在后来的项目中,使用vue-property-decorator来进行实现,虽然对于默认的对象书写方式有类型推导支持,但里面的实现绕了很多弯。问题的本质其实很简单:因为当初 API 的设计根本就没有考虑类型系统,Vue2.x对typescript支持的非常不好,我们也期待Vue3.0出现的变更。最近改用react写新的项目时,typescript发挥它很大的优势,类型提醒、数据提示(如dom对象下的方法)等,支持地都特别棒.自从接触了typescript,总想着把之前所有用javascript实现的代码再用typescript重构一下。但是,历史代码能不动还是别动了,在以后新的项目中好好使用typescript即可!
2024年10月20日
7 阅读
0 评论
0 点赞
1
...
61
62
63
...
154