• [Time]: 2020/3/16
  • [keyword]: front-end, NPM, development, Typescript, countdown

An inevitable scene in front-end development is writing countdown. Before I touched this scene, I always thought that setInterval would be enough for this thing. At most, there would be a pit with small error caused by Event Loop. This article is mainly to record the author is how to solve the actual development of the countdown scene encountered several problems.

This article will introduce two concepts that are unfamiliar to students: Event Loop and requestAnimationFrame, which are related to the length of the article and will be written separately later, but please trust me, even if you do not understand these two things, it is not too late to look up information after reading.

The source code and an implementation based on React hooks are attached at the end.

Source code address: github.com/MonchiLin/c…

The current functions are as follows:

  • Flexible parameter configuration

  • Realization of timer based on RAF

  • Pause countdown/resume countdown

React-hooks Based on the library implementation: github.com/MonchiLin/c…

The Death of a timer

As we all know, the JS concurrency model is Event Loop, which is very simple and convenient to use. However, in the countdown scenario, the timer (setInterval/setTimeout) cannot accurately calculate the time. Under normal circumstances, this degree does not need special attention. Another problem that causes time errors is that in some browsers (Chrome/Edge) the timer will not be executed while the TAB is in the background, which can cause a big problem, as shown in the following example:

Xiao Hong used the phone number to log in xx website, send verification code after Xiao Hong found that the phone number filled in wrong, so in the waiting time to send verification code again she opened other websites to look for a while, then came back to find that the countdown is still continuing.

The following code is how to correct the countdown:

  /** * correction time */
  rectifyTime() {
    / / note: this. InfoForRectification. StartTime for this the start of the countdown time point
    / / note: this. InfoForRectification. EndTime for at the end of the countdown of the point in time
    / / this. InfoForRectification. EndTime = this the start of the countdown time + need time to countdown
    // Here is the main code already
      
    // How long has elapsed since the start of the countdown = current time - the start time of the countdown
    const now = new Date().getTime() - this.infoForRectification.startTime
    // Total time required to complete the countdown = the end of the countdown time - the start of the countdown time
    const total = this.infoForRectification.endTime - this.infoForRectification.startTime
    // Expected current remaining time = how long has elapsed since the start of the countdown - total time to complete the countdown (step is ignored first)
    const timeOfAnticipation = this.countdownConfig.step * (total - now)
    console.log("Expected current remaining time =>", timeOfAnticipation / 1000."s")
    console.log("Actual current remaining time =>".this.currentTime, "s")
    // Deviation = current countdown - expected current remaining time / 1000 (because expected remaining time is timestamp)
    const offset = this.currentTime - timeOfAnticipation / 1000
    console.log("Error = >", offset, "s")

    // If you leave the screen for too long, the countdown has already finished. If you return false, the countdown is finished
    if (offset > this.currentTime) {
      return false
    } else if (offset >= this.config.precision / 1000) {
      // this.config.precision: precision
      // If the error is greater than the allowable error, correct the current countdown
      this.currentTime -= offset
    }
    return true
  }
Copy the code

Better timer

RequestAnimationFrame provides a better countdown solution. The best feature of requestAnimationFrame is that the browser guarantees that it will be called every time the screen is refreshed. So how often does the browser refresh the screen? Depending on the refresh rate of your screen, which is generally above 60 Hz, assuming the browser refreshes 60 times a second, the interval between each call is 1000/60 = 0.16ms which means we can reduce more errors by using RAF. RAF setInterval implementation code is attached below:

    this.requestInterval = (fn, delay) = > {
        // Record the start time
        let start = new Date().getTime()
        // Create an object to hold the Raf timer for clearing raf
        const handle: Handle = {
          timer: null
        };

        // Create a closure
        const loop = (a)= > {
          // Each time the timer is stored, notice that the loop is recursively called. This is how raf is used
          handle.timer = requestAnimationFrame(loop);
          // loop Time to be called this time
          const current = new Date().getTime()
          // Calculate how long it has been since the last call to loop = time of this call - start time
          const delta = current - start;
          // If delta >= delay means that the delay time has passed, fn will be called again
          if (delta >= delay) {
            fn.call();
            // Re-record the start time
            start = new Date().getTime();
          }
        }
        handle.timer = requestAnimationFrame(loop);
        return handle;
      }
Copy the code

Solve practical problems

If it is to solve the actual problem is certainly not open around need to deviate from the train of thought of the dirty code, this code has no characteristic, main is to handle border, after long deliberation, I still feel that to remove this part of the article, if you have friend need me explain can tell me, the best way, of course, or directly to the source code, For some small partners to see the source code than to see others explain too fast.

That’s all I have to say

What, you said you also have to deal with the situation of refreshing the web page and reading it locally, Oh no! Presumably smart you after reading this article to create a countdown wheel, or by the back of the small partner with it, such as return “captcha frequent”.

In addition, please let me know if you have any new functional requirements.