Empower growth and innovation with the latest Front-end Dev insights

It's 2026—why does a campaign page countdown fall behind after a few minutes on the lock screen, and why do users send screenshots asking if we cut their time?

Oct 27, 2026 Read: 22

Conclusion first: When a campaign page countdown falls far behind after the screen is locked or the page is switched to the background and then back, it is usually not that setTimeout itself is inaccurate. It is that after the page becomes invisible, the browser and host reduce timer trigger frequency or even suspend it, while the code still accumulates the count by triggering once and subtracting one second. In 2026, a more stable approach comes back to three things: store only the end timestamp in state, use the timer only as a refresh tick, and immediately recalculate once with target timestamp minus current time when the page becomes visible again. Do these three things, and most background drift can be eliminated the same day.

1. What slows down is not the arithmetic; it is background timer throttling

To save power, after a page is switched to the background, the screen is locked, or the user switches to another app, browsers stretch the minimum timer trigger interval, and host WebViews and mini program containers may suspend the entire page. Once the premise that setInterval triggers once per second no longer holds, code written with the decrement method subtracts fewer seconds, so after returning to the foreground it is permanently behind, and the deviation keeps accumulating instead of recovering by itself.

The diagnosis is direct: in the callback, print both the remaining seconds shown in the UI and the real remaining time calculated as end timestamp minus current time. The difference between the two is the drift amount. Based on the experience range for common devices and host versions in 2026, returning after one or two minutes in the background can differ by several seconds to a dozen-plus seconds, and after long lock screen or in low power mode, a difference of around half a minute is not rare. If the difference keeps growing and does not fall back, it can basically be identified as cumulative counting.

  • Look at accumulation, not a single instance: A single number means little; check whether drift stacks up after repeated switching.
  • Event names differ across platforms: Mini programs use onHide/onShow; H5 inside WeChat and App WebView use visibilitychange together with pageshow. Do not listen to only one.
  • Typical counterexample: Remaining seconds are stored in component state or a closure variable, and only decremented by one in the timer callback.

2. Anchor, tick, calibration: separate the three concerns

A countdown must satisfy two competing requirements at the same time: the display must be continuous and look good, and the numbers must have point-in-time accuracy. Delegate correctness to the timestamp anchor, continuity to the tick, and the moment of returning to the foreground to calibration. Once responsibilities are separated, problems are easier to locate, and testing can re-verify with the same criteria.

  1. Anchor: When entering the page, determine a fixed end timestamp from server-issued data or campaign configuration. Store only that in state, not the remaining seconds. When local clock and server differ noticeably, compute the offset during a handshake; do not treat the local clock as authoritative.
  2. Tick: setInterval only triggers repaint; a typical range of 200 to 1000 ms is enough. If displaying milliseconds, tune it lower. Every trigger recalculates from the anchor; do not subtract from the previous result.
  3. Calibration: When the page becomes visible again, recalculate immediately and determine whether it has expired. If expired, enter the ended state directly; do not count back from a negative number. Mini program onShow and App foreground/background switching all connect to the same place.

The easiest step to miss in implementation is the third: you do anchor recalculation but do not actively refresh once when returning to the foreground, so it waits for the next tick to update. What users see is that after returning to the foreground it pauses briefly and then jumps numbers. Extracting calibration into a function and calling it at initialization, visibility change, and expiry is a relatively worry-free implementation in 2026 delivery.

3. Decrement counting vs. difference recalculation: what is the difference

Both implementations can run on small pages; the difference is entirely in background recovery capability. Choose based on whether there is a cross-background requirement; there is no need to introduce the heavier server time sync approach right away.

  • Decrement counting: A few lines of code, quick to start; after returning from background, error accumulates and does not self-correct; suitable for verification code resend or short-lived prompts that stay for tens of seconds.
  • Difference recalculation: Less state and naturally resistant to suspension; needs to handle time zone and server time offset; suitable for scenarios that need point-in-time accuracy, such as flash sales, coupon expiry, and order timeout.
  • Cost and cycle (experience range): Compared with the decrement method, the extra development for difference recalculation is in the typical range of less than half a day, plus one more round of integration testing for switching to the background and back. It is a controllable increment.

4. In delivery: rework on an hour-scale campaign page

Based on delivery experience with site building and frontend interaction, hour-scale campaign pages are where this pitfall is most easily hit. Common constraints are: a campaign window of two or three hours, prizes and copy still changing close to launch, budget quoted per single page, and the need to run in both H5 inside WeChat and App WebView. The first version used the decrement method to save time. On Android phones, after a few minutes on the lock screen and back, it was about half a minute slow, and Campaign ended appeared earlier than the actual time. Testing regression plus one round of revision hours were both consumed.

The later approach was: keep only the end timestamp in state, route onShow and visibilitychange through the same calibration function, and at expiry pull the latest state from the server once, completely separating display and determination. The cost was a dozen more lines of code in the first version, one more round of re-verification, and the need for product and QA to accept the convention of separating display and determination. In return, after launch there was no need to explain time differences one by one based on user screenshots.

5. Testable criteria before launch and several high-frequency pitfalls

Countdown features are easily dismissed with looks fine. Giving QA criteria they can follow is much less trouble than investigating user screenshots after launch.

  • Acceptance criteria: Switch to the background for 3 to 5 minutes and return; display error should be within 1 second. After 10 minutes on the lock screen and back, no negative numbers and no number jumps back. At expiry it must stop and trigger the end logic once, not merely display 00:00.
  • Pitfall 1: Reading React state or a closure variable in the callback gets the value from the round when it was created, so it drifts further as it runs.
  • Pitfall 2: Not clearing the timer on component unmount or route switch, so it keeps running after the page is left; opening several pages results in multiple timers overwriting each other.
  • Pitfall 3: Using Math.floor directly for display causes a visual jump from 59 to 57; keep raw milliseconds in the logic layer and round only in the display layer.
  • Performance note: Full re-render every few hundred milliseconds still costs on low-end devices. Update only the number node; do not let the whole campaign page re-render with it.

6. When to use it and when not to

Whether to sync to the point depends on whether the countdown participates in business decisions. Purely atmospheric ones can be coarser; those involved in prize issuing, ordering, and timeout need point-in-time accuracy and server-side recheck.

  • Suitable for difference recalculation: Flash sales, coupon expiry, order payment timeout, verification code resend, live stream start reminders.
  • Not necessary: Forms where page dwell time is typically under 30 seconds, purely atmospheric X days until campaign, and one-time notice bars. The decrement method is enough; do not add a time-sync API layer for them.
  • Not suitable to rely only on frontend: In scenarios involving amount, inventory, or eligibility determination, the frontend only displays; whether ended or available must be finally determined by the server. Frontend countdown cannot be used as a transaction credential.

FAQ

Does replacing setInterval with requestAnimationFrame make it accurate?

No. rAF pauses directly when the page goes to the background, and is even less accurate on return. It is suitable for foreground animation ticks; timing should still be based on timestamp recalculation.

Do mini programs also need calibration, and is writing it once in onShow enough?

Yes. Mini programs also suspend timers when switched to the background, and recalculating once in onShow costs little; using one unified calibration function is less likely to be missed than writing it on every page.

Is using server time directly more stable than local clock?

More stable, but every check requires a request. A typical approach is to calculate the offset between local and server during a handshake, then keep using the local clock for recalculation to avoid continuous requests.

What else should be done after the countdown reaches 00:00?

Stop the timer, switch to the ended state, and trigger a state refresh or redirect once, so users do not stay on a button that looks clickable but is actually invalid.

If the frontend calculates time accurately, is server-side determination no longer needed?

No. The frontend only displays; whether expired, whether an order can be placed, and whether eligible must still be rechecked by the server at submission; otherwise changing local time can bypass it.


If you have a campaign page or order-timeout page scheduled, start with a small change: delete the remaining seconds state, keep only the end timestamp, and connect visibility changes to the same calibration function. Usually a change in the experience range of around half a day can eliminate most background drift. Conversely, purely atmospheric displays and pages with tens of seconds of dwell time do not need time sync and server APIs for this; just ensure foreground display continuity first.

Have a similar project in mind?
Contact us for a one-to-one project reference proposal
Obtain Proposal
Interested in this topic?
10-year tech team — reference proposal within 24 hours
Obtain Proposal
Are you ready?
Then reach out to us!
+86-13370032918
Discover more services, feel free to contact us anytime.
Please fill in your requirements
What services would you like us to provide for you?
Your Budget
ct.
Our WeChat
Professional technical solutions
Phone
+86-13370032918 (Manager Jin)
The phone is busy or unavailable; feel free to add me on WeChat.
E-mail
349077570@qq.com
Submitted successfully
Thank you for your trust. We will contact you soon!
Recommended projects for you