.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
背景:项目提测后,自己无聊,检查bug(测试没测出来,我自个儿测出来了~),在手机端的webview里面,有个倒计时,然后我触摸滑动,一直上下滑动,倒计时会停止,松开后,倒计时又继续了,然而在Chrome模拟器里面是正常的,如下图:
演示:
分析一下
js是单线程语言,虽然Web Worker
允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不能操作 DOM,所以,这个新标准并没有改变 JavaScript 单线程的本质
浏览器是多线程的,事件触发线程、定时触发器线程、异步 HTTP 请求线程
呈现引擎:又称渲染引擎,也被称为浏览器内核,在线程方面又称为 UI线程
(Trident、Gecko、Blink、Webkit、Presto)
JavaScript 解释器:又称为 JavaScript 解析引擎,又称为 JavaScript 引擎,也可以称为 JavaScript 内核,在线程方面又称为 JavaScript 引擎线程
(V8、Chakra、TraceMonkey)
UI线程
与 JS引擎线程
互斥
移动端开发,一些用到UI线程
的方法(比如:通过动画、setInterval、setTimeout等频繁操作dom),在引擎线程
被占用时,会发生卡顿的现象。
结论:在某些低版本的webview
内核中,触摸滑动(滚动)会触发重绘,也就是UI线程被占用,此时引擎线程
被挂起。
解决方案 — Web Workers
🤔 使带有滚动条的body脱离文档流应该也可以解决这个问题,还没去测试。
Web Workers
(MDN,知乎)可以在独立于主线程的后台线程中,运行一个脚本操作。也就是可以在独立线程中执行耗时的任务,从而允许主线程(通常是UI线程)不会因此被阻塞/放慢。
Workers API
,这里我就不再赘述,请看 MDN、知乎、知乎
写 Worker 脚本
根据API文档可知,Worker(aURL)
构造函数,它只执行URL指定的脚本,不指定URL时,而由使用Blob
创建,也就是说这个 aURL
除了可以是url(同源),还可以是Blob
,可惜的是不支持es模块化,当下的项目大都是用webpack进行打包,考虑到这点,有三种方式来加载这个脚本:
- 单独维护这个脚本,放到cdn上
- 使用相对路径,把脚本放到静态资源
assets
中,打包时copy到输出目录dist
,此时要考虑部署的问题,部署在非根目录时,为保证在相对目录仍能找到这个脚本,要在这个相对路径中要包含对应环境的public
- 借助
Blob
用内联脚本通过blob URL对象创建,进行模块化,使worker初始化更快,因为消除了网络来回的延迟
我比较喜欢第三种,下面来看看具体实现
/** 创建 blob URL,供worker使用 ./worker/countdown.ts */
const workerScript = ` self.onmessage = function(event) { var num = event.data; var T = setInterval(function() { self.postMessage(--num); if (num <= 0) { clearInterval(T); self.close(); console.log('clearInterval & worker closed'); } }, 1000); }; `;
const workerScriptBlob = new Blob([workerScript]);
const workerScriptBlobUrl = URL.createObjectURL(workerScriptBlob);
export default workerScriptBlobUrl;
/** 具体使用 index.tsx */
import * as React from "react";
import CountdownBolb from "./worker/countdown";
const transfDate = (second: number): (number | string)[] => {
if (second < 0) {
return ["--", "--", "--", "--"];
}
const DD = second / (24 * 60 * 60);
const HH = (second % (24 * 60 * 60)) / (60 * 60);
const mm = ((second % (24 * 60 * 60)) % (60 * 60)) / 60;
const ss = ((second % (24 * 60 * 60)) % (60 * 60)) % 60;
return [DD, HH, mm, ss].map(item => {
item = Math.floor(item);
if (item < 10) {
return `0${item}`;
}
return item;
});
};
interface IState {
remain_second: number;
}
export default class Index extends React.PureComponent<{}, IState> {
state = {
remain_second: 30
};
_worker = new Worker(CountdownBolb);
runTime = (): void => {
// 使用web worker 解决直接使用setInterval的触摸滑动时渲染卡顿问题
const { remain_second } = this.state;
this._worker.postMessage(remain_second);
this._worker.onmessage = event => {
const s = event.data;
this.setState({ remain_second: s });
};
};
componentDidMount() {
try {
this.runTime();
} catch (e) {
console.log(e);
}
}
componentWillUnmount() {
// 传入0,清除定时器并关闭worker
this._worker.postMessage(0);
}
renderCountdown = () => {
const { remain_second } = this.state;
const [DD, HH, mm, ss] = transfDate(remain_second);
return (
<div> <span style={{ width: "10px" }} /> {DD}Day {HH}h {mm}m {ss}s </div>
);
};
render() {
return <div className="App">{this.renderCountdown()}</div>;
}
}
在线预览 codesandbox
使用组件
【参考】:
今天的文章给自己提了个bug:setInterval卡顿问题分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14183.html