JavaScript counters the hard way - HTTP 203
Key Takeaways
The video discusses various techniques for creating accurate and efficient JavaScript counters, covering topics such as event queue scheduling, interval scheduling, and drift correction, with a focus on browser performance and optimization.
Full Transcript
is my microphone on it is your turn jake amuse me well yeah no promises there okay [Music] so summer i've been having a look at other web tech youtube videos uh and channels and how they do things and i've noticed that the the popular ones what they do is they take tricky topics and make them simple and easy to digest for people whereas what we do or i guess mostly what i do is i take something simple and make it really complicated and i think that's why we don't get the billions of views summer um and so are you now trying to to rectify your path no bad news i'm gonna do exactly that this episode uh because we're gonna make this uh it's a counter okay yeah i mean that's that that's some senior engineering right there absolutely it's the kind of thing you'll see in hundreds of tutorials you'll see loads of framework components are doing this kind of thing and they're all wrong um or the vast majority of them are or they're at least inaccurate or sub-optimal it tends to fall into one of those or both of those uh when you say inaccurate do you mean like not frame perfect no i mean worse than that i mean like it's displaying card number um and i'll show you why and and how like some sometimes the most obvious way is going to lead to stuff like that happening well i'm at the edge of my seat the stuff i'm going to look at it's not just going to apply to timers it's going to apply to like all kinds of low frequency animation from like blinking cursors to like drawn animation that kind of thing i'm going to cover how animation scheduling works as well um and some interesting browser bugs because it turns out a lot is involved in making a simple counter but there you go counter millions of views knowing you you implemented this timer in your javascript based slide framework is it using the correct technique or did you take a shortcut it's the correct technique absolutely the correct technique is gonna is actually quite small it's not many lines of code um but there's just there's a lot to it and a lot of gotchas along the way so i'm gonna start with a simple uh version there you go it's set interval um and that's going to call this callback uh while we've told it every 1000 milliseconds or every second as humans would call it i'm gonna channel my inner paul lewis who basically the second you even start using that interval he will call your code bad go on then what's what's wrong what have i done wrong well both set interval and set timeouts basically what they do is they schedule something put it into the event queue after a period of time but that doesn't mean that it will actually run after the period of time if i block the main thread for two hours this will never run seconds will not get increased and so this could drift any which way and interval specifically um will schedule a task every second with this callback function regardless of whether the previous scheduled task from the same interval has already run and that's often not what you want in this case it might actually be what you want but in most cases it's not well according to the spec it will call the callback and then once the callback is called it will then schedule the next one but you know so it's literally equivalent to a nested to a necessary timeout oh absolutely yeah certainly in the spec um you called it right drift is the problem here like even in your best case safari will drift about half a millisecond with every call so everything starts out fine um but as we go on and on like 125 seconds later we're now half a second late and by the time we get to 256 seconds but at this point we're now displaying the wrong time yes now we have crossed the boundary and that is best case in firefox it will take longer in safari in best case but as you said you know if something blocks the main thread for a bit those drifts are going to be exaggerated also if you put a tab in the background those drifts are massively exaggerated as well so things are going to get inaccurate really fast um interestingly chrome doesn't drift it actually auto corrects even though that is not in the spec you could say that's what developers might expect but does it make it spec uncompliant or is there leeway in the spec to do this there isn't leeway in the spec to do this so it's non-compliant although you could argue it i know right it might be more what developers expect but yeah it's it's not complying with the spec here um but again if it's in the background it's going to be throttled if there's a lot of main thread work it's going to you know be delayed by some and it could still miss those seconds okay so let's let's rate this solution not accurate over time that's bad but it does appear to update steadily like so this goes to what you're saying at the start like the frame accurate thing i don't care so much about that if it appears to update every second that's good enough for me um so it passes this one even though it's slightly out to the human eye it seems good enough it also runs code in the background like it's a visual thing so updating the dom when it's not visible that's bad right you're using cpu when you don't have to use cpu but otherwise cpu usage is pretty good well two out of four not too bad not too bad let's see if we can do better than that next solution so here i'm taking the time from date dot now so that's going to be accurate over time and that's really the the difference here but we still have that drift and that is still a problem so here's the drift again i've exaggerated the drift to make it easier to to see in this diagram but watch what happens so now with the drift we've reached a point where we've crossed that second boundary so what the user's actually going to see is 15 16 18. um it won't happen in chrome because it doesn't have that drift but this will happen in safari in firefox well this is more than drift this is more about it actually is longer than exactly 1000 milliseconds until the task gets run because if it was exactly 1000 milliseconds the drift wouldn't be that hurtful you would see 17 seconds for a very short amount of time but you would see it probably no because the drift is going to be consistent you're not you're unlike because it's going to be like a thousand milliseconds plus a bit right that's that's your drift um so so that's what we see here is it's you know it displays 16 but in reality it's it's mostly it's almost 17 so then the next tick is actually 18. so 15 16 18 each of those is going to appear like it lasts a second to the user but it's it's slightly longer which is why it ticks over to the next second um and like we saw in firefox it's like you're 250 seconds in before you will see this happen but that's best case if people are moving between tabs or there's a lot of main thread work you're more and more likely to see this as a user and it looks weird but let's say you add some code to stop the timer running in the background because that's one of the things we wanted to fix when that timer is reactivated there's a good chance it might be towards the end of that second boundary anyway so you're more likely to see that skip happen in firefox and safari but with chrome's drift correction it is possible to end up with this where chrome ends up correcting back and forth around the boundary of a second so the user is going to see 21 23 25 each for around two seconds because it's it's back and forth on that second boundary i mean you'd have to be pretty unlucky to land with this but you know if you've got thousands of users and they're changing tabs and whatever some user is going to report this timer is doing weird things and you are not going to know why like it's gonna be really hard to debug that also you didn't store the return value of set interval so this code doesn't even work jake look just gloss over that we will get to actually the actual solution that i'm going to put in the description of this video it will be cancelable but yes so i glossed over that i glossed over that it's fine all right so even if extra codes have stopped the timer in the background we've introduced a new problem the user is going to see skip numbers or uneven numbers or whatever it's not good back to the drawing board so this is pretty similar to our previous example but we're going to use request animation frame to do the scheduling like this might not feel like an animation but it is like it's something that visually updates over time yeah this is kind of shooting at birds with cannons or whatever the actual turn of races yes so this is good in some ways because request animation frame pauses when the page is hidden so we're getting that feature for free but go on what's the problem i guess about 984 milliseconds cpu usage are wasted because it there is nothing to update on the ui yeah so yeah 59 frames you have nothing to update for the one frame where you have something to update request animation frame pushes like it it runs as fast as updates are pushed to the monitor which is usually as you say 60 times a second some modern phones run at 90 144 times a second is popular on gaming monitors my ipad runs 120. oh no request animation frame doesn't the screen is 120 but safari doesn't skip that's another story for another but there are you know vr screens and all of that sort of stuff which are going to be running at that kind of rate if not higher we're running code 60 times more than we need to is the is the point and that shows up that shows up in your task manager or your like activity monitor whatever it it's bad uh we swapped one problem for another it might even show up in your battery drain it will absolutely show up in your battery drain um and in some cases it will turn fans on on your machine it's something you'll notice um so the way i tried to solve this i tried to be smart um i thought don't do that jake we've tried this before well as you'll see i failed so it's it's like all of the other times i try and be smart like animations felt like the right thing because it's the right part of the event loop and all that stuff so i thought can i make an animation that is every second rather than 60 times a second so i started with this so this is a bit like uh performance.now in that it's the time since the creation of the document but it's the time for the current frame like in a tight loop if you call date.now or performance.now you'll get different values which is useful in some ways whereas current time is always going to give you the same number within the same frame uh and this is how animation scheduling works on the web because if you start multiple animations in the same uh or part of the same frame they will all start in sync because they get their start time from the current time that seems very sensible absolutely um if you've used request animation frame before you'll notice it actually passes you in a time to your callback it's the same thing it's the current time of the frame so i've got my frame callback as before calculate which second we're in as before just using current time instead but to schedule a frame i'm going to use the web animation api with no keyframes i'm using the body element just because any element will do um no keyframes so i'm using null and it's got a duration of one second minus how far we're currently through the second so that's what we're using the modulus for there and that's our drift correction ah okay a bit like what chrome does with uh set interval and then we call frame again when the animation is done using the frame event like the finish event so using the on-frame thing i thought you were going to go with with a step function or something but this works okay yeah uh so i felt very clever doesn't work though does it um the cpu usage is about the same as request animation frame and that's in firefox safari and in chrome uh it turns out that browsers are using something very similar to request animation frame internally to queue this up thanks browsers i really thought i was onto something here um yeah i'm actually surprised because i figured at very least your code like the code that you wrote would at least be less frequent but i guess if the browser is under the hood still doing a wrap to check if the on finish needs to get invoked then you're not saving much maybe a tiny bit but yeah saving a little bit on the dom update but yeah it uses about the same cpu uh yeah so not great i mean you could actually do this without javascript at all like here is a two digit timer with pure css uh but it has the same problem chrome safari firefox all have high cpu usage here um also it doesn't work in safari you just get the cpu usage it doesn't actually change the display they are actually working on that fair enough i'm surprised you went with linear and not a step function i feel like that's what it was made i mean content probably just flips over at 50 percent but the step function would have probably been a bit more intuitive yes so with a what they call a discrete value animation it flips over at 50 which is why i've used an animation delay uh here to sort of correct for that 50 switch and you're right i could have used step when you use steps um which is an easing function you can also specify is it in the middle at the start or at the end yeah so i could have used that i was trying not to complicate it but thanks surma we've complicated it i don't know why i didn't try and complicate it because this whole episode is basically just an overcomplication of something really simple fine it's fine um but yes this uses way more cpu than our set interval version um which surprises me because i usually have this thing in my head that if you can do something without javascript and it's not a total hack then that's probably the best way to do it but that's not the case here i guess it makes it a hack then for now yeah unless browsers can optimize it so what now um we need to go back to the plain javascript timers but let's include some of the stuff that we learned from animations as before we're going to use the current time it's an animation time this is an animation we're also going to pass that into our frame function for the first call this bit same as before figure out the second display it but here's how we're going to schedule the next frame we're going to use set timeout which is a bit like set interval but it only happens once uh when that calls back we're going to use request animation frame as well and that gives us the well it synchronizes with other animations but it's also going to stop it running when the page is in the background and it's also going to provide us the time for the next frame as part of our callback great for the delay we're going to do similar to what we did before um wait a second but reduce that by the amount of second that's already passed but we're using set time at this time this is good in firefox and safari but not in chrome really and i don't know if it's a bug or if it's expected behavior um this is what we want something like this one frame per second but due to synchronization issues between set timeout and the current frame time it undershoots a bit um it actually happens before the second boundary so our drift correction schedules another callback almost immediately so you get this pattern like boom boom boom boom boom boom like it looks fine to the user um because we're using you know not date.now interesting so it actually yeah because it gets too early the actual second you were trying to complete hasn't quite completed yet and you schedule another one immediately almost yes exactly that um it's doing so is this a case for the double wrath doesn't help um or are you end oh but maybe double wrath would help but then you're pushing things out by a frame and like i i did experiment with just putting like an extra delay of like some milliseconds onto this but it's not always accurate um yeah so it felt like a bit of a hack to do something like that i mean it's not the end of the world we're running code twice as much as we need to um and that's only in chrome i mean saying this is not good cpu usage is also technically correct but also kind of unfair compared to how wasteful the other side yeah we were 60 times the amount of work before we're now twice the amount of work and that's a big difference um but this is http 203 so we're gonna we're gonna fix this properly yeah better when you've got an inaccuracy that can happen in both directions um positive and negative the solution is to round rather than floor um there's actually three bits of flooring like code in here can you spot them um well there's the floor but definitely yes i was hoping you would get that one um i'm guessing current time is also implicitly rounded or floored at the start maybe it's it's actually not um although that can depend on the browser um and that comes from performance.now is rounded in some browsers and not in some others and that can also depend on high resolution timers and stuff anyway so i guess the set timeout parameter is also implicitly rounded to milliseconds at the very least yeah well done actually i'm impressed you you got any more of these you're right uh set timeout um what it will do is floor whatever you give to it and that's as per the spec so if you pass it like 99.99 it's going to come down to 99 rather than go to 100. and the only last one i can think of would be the parameter frame which is given to you by request animation frame now the other one is the modulus operator which is a flooring like operator because it's it's giving you the amount that you've overshot the second it's not giving you the distance to the next second so this could give you 999 when it's actually one millisecond till the next second so it's it's not strictly flooring but it's going it's always counting back right oh fine i'll give you that one flooring but that's one of those things that we would look at at some point and yes the set timeout is the other one okay so let's get to work i'm gonna get rid of that schedule we had before i'm gonna swap that floor for a round there we go so you're only using the what we try and just from a floor into a round that is only used for the seconds variable which we only use to update the ui so it doesn't really affect how we schedule things we're going to use it to schedule things here we go oh we're going to figure out what time the next frame should be so that's the second that we are displaying which we've rounded plus one turn it into milliseconds and add it to the start time so that's when we want the next frame to happen and then similar to before we're using set timeout request animation frame but the target time it's going to take our delay minus whatever time it is now and that's a big difference because now we're using performance.now which takes the current time like literally when is this line of javascript executed not when was the last frame shipped which is what current time is yes and that's because that's what set timeout uses like it's when you give it like 100 it's 100 from the time it's called not the time of the the last frame so yes it makes sense to use performance dot now here we could round this as well but it's not necessary rounding the seconds is enough and that's it that's that is that's how you do a simple timer that achieves all of these things you know and we have no double frame anymore this is literally your ship a frame exactly when you need to on the second yes incredible and i will include a link to a version in the description that can schedule for intervals other than seconds so you know if you wanted to do something every you know 12 times a second which you know some animations drawn animations like that you can have that and it's cancelable with an abort controller as well but i'm not done yet i know this is a long episode but i'm not done yet um something was keeping me up at night and it's like this this should have been the answer like this should have worked all the the web animation api like it really bugged me that this performed badly in browsers so what i did is i i wanted other kinds of animation that are badly optimized like this so i went and started testing them uh other kinds of web animation that have like infrequent changes so we've seen empty web animations you can also do this with css not sure why you would want to but you can performs badly in all browsers they run internal code every every frame you know 60 times a second or whatever animations can have a delay where nothing happens for a bit so this is a five second animation but 10 seconds happen you know nothing happens before the the animation kicks in chrome and safari optimized for this but firefox doesn't and that's a shame because that could have been my work around for the web animation thing like i could have used a delay a zero duration animation anyway never mind okay here's a 10 second animation but you mentioned steps before it's only going to have two frames safari optimizes for this but firefox and chrome do not oh that's interesting so well done safari here um and then finally this one um so here we've got something it fades out a bit uh and then fades back in and then it waits so like most of the animation is just the thing sitting there opacity one i actually use this on the chrome dev summit website like when the the conference was live i had like a little red circle like a you know a record the record light and every now and then it would it would sort of pulse using this no browser optimizes for it it's that's interesting it's kind of sad in these examples at least it seems fairly easy to do this analysis whether there's you know basically that time in the animation but i'm guessing it will become increasingly hard when you have more complex animations yeah and but yeah fair play to safari for optimizing more cases than firefox and chrome uh but there are still real world improvements that can be made uh so i'll add them to the description because i've filed bugs with all of the browsers about this so you can track when they start fixing these things i think the empty animation uh which is the one that was important to me for this the the timer i think they're going to fix that first um probably probably safari that fixes it first which is great um but yeah you can follow along for that and finally that is all i had that's all i've got and so hopefully this episode's going to get the millions of views because everyone's desperate to watch a i don't even know how long this lasted like a 45 minute episode on creating a javascript counter just yeah just count up but do it efficiently oh yes good it's good coughing oh yes oh just do the rest of the episode in this voice oh sexy's sexy sexy javascript
Original Description
You’ve seen loads of counter tutorials online, but they’re all a bit wrong… or at least most of them are. Jake and Surma dissect different techniques and identify how to make the counter work accurately and efficiently across browsers.
The optimal reusable solution → http://goo.gle/2LY9GNI
Browser bugs for suboptimal CSS animations:
Chrome (empty JS anim) → http://goo.gle/3qS3vt7
Chrome (other animations) → http://goo.gle/39izDjS
WebKit → http://goo.gle/2Mnkt3F
Mozilla → http://goo.gle/2Nt4T74
Other videos in the series → https://goo.gle/2wneQLl
Subscribe to Google Chrome Developers here → https://goo.gle/ChromeDevs
Also, if you enjoyed this, you might like the HTTP203 podcast! → https://goo.gle/2y0I5Uo
Watch on YouTube ↗
(saves to browser)
Sign in to unlock AI tutor explanation · ⚡30
Playlist
Uploads from Chrome for Developers · Chrome for Developers · 0 of 60
← Previous
Next →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Polymer Performance Patterns (The Polymer Summit 2015)
Chrome for Developers
Polymer Power Tools (The Polymer Summit 2015)
Chrome for Developers
Chrome Dev Summit 2014 – Chrome Case Studies
Chrome for Developers
Web Directions Code 2015 round up
Chrome for Developers
Maintainable Code - HTTP203
Chrome for Developers
iron-ajax… wat?! -- Polycasts #26
Chrome for Developers
The Guardian - Supercharged
Chrome for Developers
ES2015 (next version of JavaScript), Totally Tooling Tips (S2 Ep1)
Chrome for Developers
#AskPolymer: Rob answers all the questions ever -- Polycasts #27
Chrome for Developers
The Future of JavaScript - HTTP203
Chrome for Developers
Data Binding 101 -- Polycasts #28
Chrome for Developers
The Guardian part 2 - Supercharged
Chrome for Developers
The Future of Web Audio: with Chris Wilson and Chris Lowis
Chrome for Developers
Chrome 46: New motion-path animations, client hints and service worker improvements
Chrome for Developers
Sublime Snippets, Totally Tooling Tips (S2 Ep2)
Chrome for Developers
#AskPolymer: How do you make the show? -- Polycasts #29
Chrome for Developers
Critical Path CSS, Totally Tooling Tips (S2 Mini Tip #1)
Chrome for Developers
Binding to Objects -- Polycasts #30
Chrome for Developers
Player FM - Supercharged
Chrome for Developers
Where’s the Designer? #AskPolymer -- Polycasts #31
Chrome for Developers
Jake Beats Wikipedia - HTTP203
Chrome for Developers
Supercharged Observers! -- Polycasts #32
Chrome for Developers
Jai's Web blog - Supercharged
Chrome for Developers
Windows Command-line Tooling, Totally Tooling Tips (S2, Ep4)
Chrome for Developers
What about internationalization? #AskPolymer -- Polycasts #33
Chrome for Developers
Developing for Billions (Chrome Dev Summit 2015)
Chrome for Developers
Google+ Performance Improvement Comparison
Chrome for Developers
Deploying HTTPS: The Green Lock and Beyond (Chrome Dev Summit 2015)
Chrome for Developers
Progressive Web Apps (Chrome Dev Summit 2015)
Chrome for Developers
Instant Loading with Service Workers (Chrome Dev Summit 2015)
Chrome for Developers
Increase Engagement with Web Push Notifications (Chrome Dev Summit 2015)
Chrome for Developers
Engaging with the Real World: Web Bluetooth and Physical Web (Chrome Dev Summit 2015)
Chrome for Developers
Asking for Permission: respectful, opinionated UI (Chrome Dev Summit 2015)
Chrome for Developers
Polymer - State of the Union (Chrome Dev Summit 2015)
Chrome for Developers
Building Progressive Web Apps with Polymer (Chrome Dev Summit 2015)
Chrome for Developers
Introduction to RAIL (Chrome Dev Summit 2015)
Chrome for Developers
DevTools in 2015: Authoring to the max (Chrome Dev Summit 2015)
Chrome for Developers
RAIL in the real world (Chrome Dev Summit 2015)
Chrome for Developers
#ChromeDevSummit talks are up - W00T! -- Polycast #34
Chrome for Developers
V8 Performance from the Driver's Seat (Chrome Dev Summit 2015)
Chrome for Developers
Quantify and improve real-world RAIL (Chrome Dev Summit 2015)
Chrome for Developers
Owning your performance: RAIL (Chrome Dev Summit 2015)
Chrome for Developers
HTTP/2 101 (Chrome Dev Summit 2015)
Chrome for Developers
Leadership Panel (Chrome Dev Summit 2015)
Chrome for Developers
Build Processes, Totally Tooling Tips (S2, Ep 5)
Chrome for Developers
Accessibility (Chrome Dev Summit 2015)
Chrome for Developers
Binding to Arrays -- Polycasts #35
Chrome for Developers
HTTP2 - HTTP203
Chrome for Developers
Chrome 47: Splash Screens, requestIdleCallback and better desktop notifications (New in Chrome)
Chrome for Developers
Call For Submissions - Supercharged
Chrome for Developers
Cross Device Testing, Totally Tooling Tips (S2 Ep6)
Chrome for Developers
Testing AJAX with Web Component Tester -- Polycasts #37
Chrome for Developers
Slack: Extended Xmas Special - Supercharged
Chrome for Developers
Browser testing with Travis & Sauce Labs -- Polycasts #38
Chrome for Developers
Optimize for production with Vulcanize -- Polycasts #39
Chrome for Developers
Highlights from Chrome Dev Summit 2015
Chrome for Developers
Chrome 48: Custom buttons in notifications, DevTools Security panel, and Presentation mode
Chrome for Developers
Crisper: Protecting your Polymer app with CSP -- Polycasts #40
Chrome for Developers
How do I use Sass with Polymer? #AskPolymer -- Polycasts #41
Chrome for Developers
Colors – DevTools Tonight #0 (Pilot)
Chrome for Developers
More on: Prompt Craft
View skill →Related Reads
📰
📰
📰
📰
The Enter key that submits your form while a Japanese user is still typing
Dev.to · greymoth
The two-Reacts bug: when packages aren't singletons
Dev.to · r9v
🚀 Introducing Prism Guard — An Open Source Frontend Architecture Intelligence Platform
Dev.to · Ritumoni Sarma
The Death of the Heavy Hydration Layer
Dev.to · Amodit Jha
🎓
Tutor Explanation
DeepCamp AI