Free Webflow components / Effect: pixelated image load

Webflow pixelated image reveal

A free pixel image reveal for Webflow: the picture starts blocky and sharpens in steps each time it scrolls into view. Copy it, press ⌘V, and swap the image.

  • Responsive

What you receive in Webflow

  • 11 native elements and 8 classes.
  • Breakpoints: mobile landscape (767px), tablet (991px).
  • 2 Embed elements keep the CSS or script that Webflow cannot express. To edit an Embed, you must have a paid Webflow site plan.

How to paste it

  1. Click Copy for Webflow.
  2. Open your page in the Webflow Designer.
  3. Press ⌘V or Ctrl+V.

HTML

<section class="px">
  <div class="px-inner">
    <div class="px-copy">
      <p class="px-eyebrow">Case study</p>
      <h2 class="px-title">Numbers that tell the story</h2>
      <p class="px-text">The image starts as big pixels and sharpens step by step each time it scrolls into view. Replace it with any image.</p>
    </div>
    <div class="px-frame">
      <img class="px-img" src="https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1000&q=80" alt="Charts on a laptop" crossorigin="anonymous" loading="lazy">
    </div>
  </div>
</section>
<style>
.px { font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; background: #ecfdf5; color: #052e16; padding: 112px 24px; }
.px-inner { max-width: 1080px; margin: 0 auto; display: flex; align-items: center; gap: 56px; }
.px-copy { flex: 1; }
.px-eyebrow { font-size: 13px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: #059669; margin: 0 0 16px; }
.px-title { font-size: 46px; line-height: 1.08; letter-spacing: -0.03em; margin: 0 0 16px; }
.px-text { font-size: 17px; line-height: 1.6; color: #166534; margin: 0; max-width: 420px; }
.px-frame { position: relative; flex: 0 0 520px; aspect-ratio: 4 / 3; border-radius: 24px; overflow: hidden; background: #a7f3d0; }
.px-img { display: block; width: 100%; height: 100%; object-fit: cover; }
.px-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; image-rendering: pixelated; }
@media (max-width: 991px) { .px-inner { flex-direction: column; align-items: stretch; } .px-frame { flex-basis: auto; width: 100%; } }
@media (max-width: 767px) { .px { padding: 80px 20px; } .px-title { font-size: 34px; } }
</style>
<script>
(function () {
  if (window.matchMedia && matchMedia("(prefers-reduced-motion: reduce)").matches) return;
  document.querySelectorAll(".px-frame").forEach(function (frame) {
    var img = frame.querySelector(".px-img");
    if (!img || !("IntersectionObserver" in window)) return;
    var cv = document.createElement("canvas"), ctx = cv.getContext("2d");
    cv.className = "px-canvas"; frame.appendChild(cv);
    var steps = [64, 32, 16, 8, 4, 2], STEP = 220, timers = [], inView = false;
    function draw(size) {
      var w = frame.clientWidth, h = frame.clientHeight;
      if (!w || !h || !img.naturalWidth) return;
      cv.width = w; cv.height = h;
      var sw = Math.max(1, Math.round(w / size)), sh = Math.max(1, Math.round(h / size));
      var s = Math.max(w / img.naturalWidth, h / img.naturalHeight), iw = img.naturalWidth * s, ih = img.naturalHeight * s;
      ctx.imageSmoothingEnabled = false;
      ctx.clearRect(0, 0, w, h);
      ctx.drawImage(img, (w - iw) / 2 / size, (h - ih) / 2 / size, iw / size, ih / size);
      ctx.drawImage(cv, 0, 0, sw, sh, 0, 0, w, h);
    }
    function stop() { timers.forEach(clearTimeout); timers = []; }
    // pixelated again, ready to play the next time the image comes into view
    function reset() { stop(); draw(steps[0]); cv.style.transition = "none"; cv.style.opacity = "1"; }
    function play() {
      if (!img.naturalWidth) return;
      reset();
      steps.forEach(function (s, i) { timers.push(setTimeout(function () { draw(s); }, i * STEP)); });
      timers.push(setTimeout(function () { cv.style.transition = "opacity 0.35s ease"; cv.style.opacity = "0"; }, steps.length * STEP));
    }
    reset();
    // the image can finish loading after the frame scrolled into view: play then
    if (!img.complete) img.addEventListener("load", function () { if (inView) play(); else reset(); });
    new IntersectionObserver(function (es) {
      var e = es[0];
      if (e.intersectionRatio >= 0.4 && !inView) { inView = true; play(); }
      else if (!e.isIntersecting && inView) { inView = false; reset(); } // fully out of view
    }, { threshold: [0, 0.4] }).observe(frame);
    window.addEventListener("resize", function () { if (!inView) reset(); });
    img.addEventListener("error", function () { stop(); cv.remove(); });
  });
})();
</script>

Questions

Can I use my own image?

Yes. Replace the image like any Webflow image. The script redraws whatever image is there, in coarse steps, once it has loaded.

What does the Designer show?

The sharp image. The pixel steps only play on your published site.

Is this effect component free?

Yes. Copy it with no account and use it on client sites and your own sites.

Does it work on the free Webflow plan?

Yes. It pastes on every Webflow plan, including Starter. If it includes an Embed element, editing that Embed's code needs a paid site plan.