Settings

Theme

Breaking down a ripple animation in JavaScript

bryanbraun.com

68 points by bryanbraun 5 years ago · 45 comments

Reader

netgusto 5 years ago

Cool, thank you! For a smoother animation, you may want to replace the setInterval() with:

  const anim = () => {
    drawRipple();
    requestAnimationFrame(anim);
  }
  anim();
  • bryanbraunOP 5 years ago

    Agreed, just trying to keep the tutorial simple for now. My other web-animation/education project (https://sparkbox.github.io/bouncy-ball) uses requestAnimationFrame more heavily.

    Now that I think of it, it might be worth adding an asterisk and footnote about requestAnimationFrame to the post.

  • beebeepka 5 years ago

    Also, setTimeout is more reliable for loops and such. As a fallback to requestAnimationFrame

    Edit: I wonder why was this downvoted. Is setTimeout not more reliable time wise than setInterval?

    • franciscop 5 years ago

      Probably because requestAnimationFrame is much better than setTimeout, AND requestAnimationFrame is supported even by IE10 so there's absolutely no need for a fallback. Also no, setTimeout is not more reliable than setInterval in many situations.

      • beebeepka 5 years ago

        Did I not day FALLBACK? Oh, I did but I am getting downvoted and mansplained for pointing that out a simple fact.

        RAF also stops executing when tab is inactive. Sometimes we don't want our loop halted and that's where setTimeout comes in as the better alternative to setInterval.

        • franciscop 5 years ago

          This is the perfect example of why it says not to ask why you were downvoted in the FAQ, and the last time I try to help. You literally asked, I just tried to give a plausible explanation. Instead you rant back, scream and insult those literally answering your question. Not the right behavior for HN.

          • beebeepka 5 years ago

            I provided a real world example that you haven't considered before.

            Your condescending responses are hardly genuine attempts at finding a plausible explanation for me being downvoted by people who can't think that pointing out thing A is better than thing B even though thing C exists and is the correct approach most of the time.

      • qweqwweqwe-90i 5 years ago

        No one should bother supporting IE now.

        • forgotmypw17 5 years ago

          I strongly disagree. If you can't be bothered to write resilient code, that's your choice, but don't speak for me on what I should and shouldn't support.

          • tomxor 5 years ago

            Supporting IE !== "writing resilient code"

            If anything your code will be much worse for it and likely to contain more bugs.

            • forgotmypw17 5 years ago

              Your code could also just be simpler and not include bug-prone ways of writing.

              For each browser you add support for, x more browsers and access limitations you've never even heard of also become supported.

        • franciscop 5 years ago

          At 2.3% in Japan, and a much higher percentage in some specific industries there like healthcare, some people probably might be bothered supporting IE. Not me or most people though:

          https://twitter.com/FPresencia/status/1428359948182818836

    • jsf01 5 years ago

      Absolutely. setInterval with a very tight interval runs the risk that your code execution time eventually catches up with the interval duration itself. Using setTimeout and raf recursively avoid this problem, but they do add a bit of baggage themselves by growing the call stack via many many recursive calls.

      Edit: looking into this some more, it seems like the recursive methods might not actually grow the call stack due to being asynchronous. Their only added baggage is the extra closure which would be negligible. Anyone know if this is actually the case?

      • fenomas 5 years ago

        There's no recursion involved with RAF or looping with setTimeout. In either case the "anim" function doesn't invoke itself, it calls a scheduling function and passes itself as an argument.

    • fenomas 5 years ago

      requestAnimationFrame is much better here. If you're just drawing into a canvas, there's no reason to do that every N milliseconds - you want to do it once per screen refresh, which is what RAF is for. There's also the added benefit that RAF won't fire when the document isn't visible, so you don't waste cycles drawing to a canvas that's in a background tab, etc.

      • tomxor 5 years ago

        > there's no reason to do that every N milliseconds

        Usually if you want to do any kind of posteriori procedural animation i.e real-time physics, you need a constant interval, and RAF does not give you this...

        RAF usually runs at 60FPS but it can drop to 30 or increase to 120 depending on the browser, hardware and power saving modes - Which means whatever you are running in that function can get called at drastically different intervals for different users.

        In this case the animation is a priori, meaning we don't have to simulate intermediate steps, so it's possible to correct for this inside the RAF alone by using Date.now() as the time parameter for shifting the sine wave... however this is not always the case. And in those cases setInterval can be far simpler when timing is important, but if you want to continue using RAF ultimately you would need to decouple rendering from "physics" or any timing related code that cannot be calculated a priori, and run the latter at a constant interval.

        • fenomas 5 years ago

          I think a few topics are conflated here. The part of the code that draws pixels into a canvas (or tells webgl to draw) should always be called from RAF - there's never a reason to do that more often than the screen refreshes. And since TFA does basically nothing except set canvas pixels, RAF is clearly the thing to use.

          OTOH whether you want your animation to advance in real time is a separate issue, regardless of whether the animation is interpolated. If you do want real-time animations, and you're using an interpolated physics engine or similar, there really aren't any good options besides decoupling the interpolation from the rendering. And if you've done that, then it doesn't matter much whether the interpolation is called from RAF or setInterval (personally I use both, so that physics ticks can happen between RAF events if the CPU load is heavy).

          But as a footnote, setInterval definitely does not give you a "constant" interval. Besides the normal variance, browsers will throttle the events (e.g. if the document is in a background tab).

          • tomxor 5 years ago

            Nothing gives you a constant interval, but it attempts to unlike RAF.

tomxor 5 years ago

Faster 125 FPS, 165B version

  <canvas id=c><svg onload=setInterval("for(c.width=64,i=4096;i--;)c.getContext('2d').fillRect(X=i&63,Y=i/64|0,1,Math.sin(Math.hypot(X-32,Y-32)/2-++t/4e4)/4+.5)",t=8)>
kroltan 5 years ago

A plea to anyone who wants to do that, I would highly recommend running that kind of thing on the GPU! Looping through all those pixels every frame is ought to hog a lot of UI thread time otherwise, especially with big resolutions!

  • zokier 5 years ago

    very basic shadertoy shader in the spirit of the article:

        void mainImage( out vec4 fragColor, in vec2 fragCoord )
        {
            vec2 center = iMouse.xy/iResolution.xy;
            float aspect = iResolution.x/iResolution.y;
            vec2 uv = fragCoord/iResolution.xy;
            uv.x *= aspect;
            center.x *= aspect;
            float r = distance(uv, center);
            float h = (sin((r*50.0)-(iTime*2.0))+1.0)/2.0;
            h *= 1.0 - min(1.0, r);
            fragColor = vec4(h,h,h,1.0);
        }
  • tomxor 5 years ago

    It's also not necessary to do such high resolutions to get smooth result with this animation due to the nature of the image. Interpolation is much faster based on a low res version, especially the hardware accelerated smooth interpolation built into the browser for canvas. My example in this thread is only 64x64 pixels but you can zoom in as much as you like and it remains fast and smooth.

  • geuis 5 years ago

    Good follow up article would be an intro to shaders, where you take the work from pixel looping and move it to shaders.

agys 5 years ago

This is the basic building block for cool old-school plasma effects!

Quick GLSL tutorial: https://web.archive.org/web/20210119091116/http://www.bidoui...

nayuki 5 years ago

    let distance = hypotenuseLength(reIndexedX, reIndexedY);
    
    function hypotenuseLength(x, y) {
      return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
    }
The above can be replaced with:

    let distance = Math.hypot(reIndexedX, reIndexedY);
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
  • wizzwizz4 5 years ago

    It's a tutorial, so there's a benefit to writing out the separate function in the article, but given that it's “in JavaScript” I agree with your suggestion.

    • bryanbraunOP 5 years ago

      Haha, yeah I discovered this after I wrote it. Probably worth going back and updating.

ktpsns 5 years ago

Story time! When I was around 12 years old, I've tried so hard to understand the JavaScript code to render an analog clock within HTML. I did not learn yet about sine and cosine and it was really a miracle how the code could determine the positions of the clock elements.

First learning how to code, then learning the math was a hard way to do the interesting stuff with computers. The other way around is much more straight forward.

  • atum47 5 years ago

    You just described my whole career, haha. I learned JavaScript by inspecting other's people code. I remember this cool effect I saw on the website for the matrix (the movie), it blew my mind back then, now I know is just image replacement on the mouse hover. The effects was a bunch of out of air tvs and when you position the mouse the tv would show a channel

memalign 5 years ago

Desmos (the graphing calculator tool used by the author) is really great! One thing that’s inconvenient about it is not being able to copy/paste formulas in. I ended up making my own graphing calculator web app to make drawing with math easier: FormulaGraph[0].

In a similar vein to the ripple, here’s[1] the Batman Curve in FormulaGraph.

[0] https://memalign.github.io/m/formulagraph/index.html

[1] https://memalign.github.io/p/batman-curve.html

__ryan__ 5 years ago

> To animate the wave in JavaScript, we can use setInterval to repeatedly call our drawRipple() function, and pass in a timestamp to adjust the wave position.

You described generating a ripple still-frame then completely glossed over the “animation” part.

  • agys 5 years ago

    While setInterval works, requestAnimationFrame(time) is probably better suited for animation purposes.

  • bryanbraunOP 5 years ago

    That's a good callout. I just updated it to add more detail there.

recursive 5 years ago

If you're into this kind of stuff, you might like dwitter.net

  • dcsan 5 years ago

    That's a really cool site. Almost like demo scene in JS. Any other similar places?

onion2k 5 years ago

This is a naive, but very fun, implementation of a 2D signed distance field. It's the same principle that artists like iq use to make amazing shader animations.

https://www.iquilezles.org/www/articles/distfunctions/distfu...

atum47 5 years ago

I once wrote something similar on a jsfiddle but can't find it now. Later in I found an article and a YouTube video that proposed a different approach to do ripples, which I followed:

https://github.com/victorqribeiro/rippleEffect

rojobuffalo 5 years ago

here's one that animates a CSS radial-gradient and centers it on the click location https://gist.github.com/rojobuffalo/3a4f108937ffcd318c70

TeeWEE 5 years ago

While its cool drawing this iteratively is not really something that scales well. Better to use the GPU which most devices have today.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection