.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}}

Background: the project after survey, boring, check the bug (test measured, not by myself I measured the ~), on the mobile end webview inside, there is a countdown, and then I touch sliding, sliding up and down all the time, the countdown will stop, loosen, the countdown to continue again, but in Chrome simulator is normal, the diagram below:

Presentation:


Look at the

Js is a single-threaded language. Although Web workers allow JavaScript scripts to create multiple threads, child threads are completely controlled by the main thread and cannot manipulate DOM. Therefore, this new standard does not change the nature of JavaScript single-threaded

Browsers are multithreaded, event-firing threads, timed trigger threads, asynchronous HTTP request threads

Rendering engine: Also called rendering engine, also called browser kernel, also called UI thread in terms of threads (Trident, Gecko, Blink, Webkit, Presto)

JavaScript interpreter: also called JavaScript parsing engine, also called JavaScript engine, also called JavaScript kernel, also called JavaScript engine thread in terms of threads (V8, Chakra, TraceMonkey)

UI thread and JS engine thread are mutually exclusive

In mobile development, some methods using UI threads (such as animation, setInterval, setTimeout, and other frequent DOM manipulation) will stall when the engine threads are occupied.

Conclusion: In some older Versions of the WebView kernel, touch sliding (scrolling) triggers a redraw, where the UI thread is occupied and the engine thread is suspended.

Solution — Web Workers

🤔 taking the scrollbar body out of the document flow should also solve this problem, which has not been tested yet.

Web Workers(MDN, Zhihu) can run a script operation in a background thread independent of the main thread. That is, time-consuming tasks can be executed in a separate thread, allowing the main thread (usually the UI thread) not to block/slow down.

Workers API, I will not repeat here, please see MDN, Zhihu, Zhihu

Write the Worker script

The Worker(aURL) constructor, which only executes the script specified by the URL, is created using a Blob when the URL is not specified. That is to say, the aURL can be both aURL (homology) and a Blob. Unfortunately, es modularization is not supported. Most projects today are packaged in Webpack, so with that in mind, there are three ways to load this script:

  1. Maintain this script separately and put it on the CDN
  2. Use relative paths to put scripts into static resourcesassetsCopy to the output directory when packagingdistIf the script is deployed in a non-root directory, to ensure that the script can still be found in the relative directory, the relative path of the corresponding environment should be includedpublic
  3. With the help ofBlobThe inline script is created through blob URL objects and modularized to make worker initialization faster because the network round-trip delay is eliminated

I prefer the third option, so let’s look at the implementation

/** Creates a blob URL for workers to use./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;


/** use 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= > {
    // Use web worker to fix render lag when using setInterval's touch swipe directly
    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() {
    // Pass 0 to clear the timer and close the 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>; }}Copy the code

Preview codesandBox online

Using the component

【 reference 】 :

  1. Developer.mozilla.org/zh-CN/docs/…
  2. Developer.mozilla.org/zh-CN/docs/…
  3. Developer.mozilla.org/zh-CN/docs/…
  4. zhuanlan.zhihu.com/p/25184390
  5. zhuanlan.zhihu.com/p/93470509