Skip to the content

Scroll distortion

The pictures of a page bend with the scroll.

Become a member to unlock the full effect today.

Join now

Already a member? Sign in

Documentation

  1. Copy to Webflow

    Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed.

  2. Add custom CSS

    The canvas is put in the marked box and draws its pictures: anything laid over a picture, a badge or a title, needs position: relative and a z-index over 1 to stay in front. A bent picture reaches past its own box for the time of the scroll, so the marked box needs some room around its pictures. The radius and the object-fit of a picture are read on the picture itself.
  3. Add custom JS

  4. Use it on your own elements

    Custom attributes
    Select the element on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.
    Custom attributeWhat it does
    data-scroll-distortion Marks the box that holds the pictures: a gallery, a section, the wrapper of a whole page. It takes no value: leave the value field empty. One canvas is put in that box and every picture in it bends with the scroll, so mark the box around all the pictures rather than each picture, and one that leaves them some room above and below. Put on a picture itself, it is read as the box around that picture. Without it on any element of the page, the embed marks the section it was pasted into.
    data-scroll-distortion-image Marks one picture of the box, an Image. It takes no value. Without it on any picture, every picture of the box bends; with it on some, only those do. [ Optional ]
    data-scroll-distortion-state Written by the engine on the box: live while a picture is bent, idle at rest, still under reduced motion, plain when there is no WebGL or no picture could be read. Never written by hand. [ Optional ]
    data-scroll-distortion-live Written by the engine on each picture the canvas draws, which the stylesheet then makes clear. A picture without it is shown by the page itself. Never written by hand. [ Optional ]
    data-scroll-distortion-layer Written by the engine on the canvas it adds to the box. Never written by hand, and removed by destroy(). [ Optional ]
    Settings
    Every setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.
    AttributeDefaultWhat it is
    data-scroll-distortion-tint #ffab1a Colour of the fringe an edge takes while the picture moves: that colour on one side of the edge, its opposite on the other. Options: any CSS colour, a hex, an rgb(), a name.
    data-scroll-distortion-tint-amount 1 How much the fringe follows tint, 0 to 1.
    data-scroll-distortion-bend 0.1 Depth of the bow at full speed, in shares of the picture's width: how far the middle of the picture lags behind its two sides.
    data-scroll-distortion-stretch 0.08 Length the picture gains at full speed, in shares of its height: its trailing edge drags while its leading edge keeps its place.
    data-scroll-distortion-split 0.02 Gap between the colour layers at full speed, in shares of the picture's height.
    data-scroll-distortion-inertia 0.9 Share of the difference left after a frame, at 60 frames a second, between the speed a picture shows and the speed of the page; it sets how long the pictures take to pick the effect up and to lie flat again.
    data-scroll-distortion-speed 2400 Scroll speed that gives the full effect, in px a second.

What it does

Scroll distortion is a scroll animation for Webflow and vanilla JavaScript, with no library. Also called webgl scroll distortion, image distortion on scroll or scroll velocity image effect.

The pictures of a page bend with the scroll. Each one is an ordinary <img>, kept where it is for the layout, the alt text and the search engines, and drawn a second time on one WebGL canvas: its middle lags behind its edges along a bow, its trailing edge drags, and its colour layers part, the more the faster the page goes. When the scroll stops the speed eases out and the pictures lie flat again, on the pictures of the page: same place, same size, one device pixel per pixel. The speed is read on the pictures themselves, frame by frame, so the window's own scroll, a scroller inside the page and a smooth scroll library all drive it the same way, and none is needed. The canvas is a child of the marked box, so it scrolls with the page instead of chasing it, and it is only as tall as the window and a margin, however long the page is. A short WebGL shader, no library, no build. Without WebGL, under reduced motion, or when a picture comes from another site that does not allow its pixels to be read, the plain HTML picture stays, untouched.

Updated

Questions

Put the custom attribute data-scroll-distortion on the box that holds the pictures, a section or the wrapper of the page, and every <img> inside it bends with the scroll. Mark the box and not each picture: the script puts one canvas in it, because a browser only gives a page a few WebGL contexts. Settings go on that box, for instance data-scroll-distortion-bend="0.2".

No. It is raw WebGL1 with the shader written in the engine, no library and no build. It needs no smooth scroll either: the canvas is a child of the marked box and scrolls with the page, so a picture and its copy never come apart. With Lenis already on the page there is nothing to connect, the speed is read on the pictures themselves.

Yes. Each picture stays in the page with its box, its alt text and its clicks, and is only made transparent while the canvas draws it at the same place and the same size. Without WebGL, under prefers-reduced-motion, or when a picture's server does not allow its pixels to be read, no copy is made and the plain picture shows.

data-scroll-distortion-bend is the depth of the bow, 0.1 of the picture's width by default, data-scroll-distortion-stretch the length the picture gains and data-scroll-distortion-split the gap between its colour layers. data-scroll-distortion-inertia, 0.9 by default, sets how long the pictures take to lie flat: at 0.8 a third of a second, at 0.96 close to two. data-scroll-distortion-speed is the scroll speed, in px a second, that gives the full effect.

Yes, from the finger's scroll and from the glide that follows it; nothing is added to the gesture. The canvas is only as tall as the screen and a margin, however long the page is.