惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Fox-IT International blog
Recorded Future
Recorded Future
T
ThreatConnect
T
The Exploit Database - CXSecurity.com
SecWiki News
SecWiki News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
人人都是产品经理
人人都是产品经理
T
Tenable Blog
L
LINUX DO - 最新话题
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
罗磊的独立博客
博客园 - 司徒正美
The Hacker News
The Hacker News
博客园 - 聂微东
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Scott Helme
Scott Helme
博客园 - 【当耐特】
O
OpenAI News
Schneier on Security
Schneier on Security
Latest news
Latest news
S
Security @ Cisco Blogs
S
Secure Thoughts
F
Full Disclosure
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
T
Tor Project blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
小众软件
小众软件
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
N
News and Events Feed by Topic
E
Exploit-DB.com RSS Feed
J
Java Code Geeks
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Schneier on Security
Cisco Talos Blog
Cisco Talos Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Proofpoint News Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
雷峰网
雷峰网

DEV Community

Rugby Fundamentals as Software Concepts - Mapping the Pitch to your Code Base I Let Claude Code Run Unsupervised for 24 Hours. Here's What Happened. Why Zed Is Replacing VS Code in My AI-Augmented Workflow Karpathy's LLM Wiki? No Code with Claude or Github Copilot! Why Platform Governance and Transparency Matter for Developers and Freelancers I built a Flutter CLI that generates Clean Architecture in seconds Using an LLM to automate a task that used to take hours by hand CyberArena – Interactive Cyber Security Simulation & Threat Analysis Platform Tile Extractor Mathematical Functions in CSS: clamp, min, max and How They Simplify Responsiveness Polyglot Persistence in Microservices: Let the Domain Choose the Database 190 Countries, Zero API Calls: Shipping Static Data in a Chrome Extension Your AI Writes Code Fast. Here’s How to Check It Before Shipping qwen2.5-coder is too slow for Claude Code on a Mac. Here's the fix. Building Automated Text-to-Video Pipelines with AI Can Gemini Become an Offline AI Tutor? Lessons from Building Educational AI OPRIX : From a simple messaging web app to a well structured and enhanced UI messaging web app Why React + TypeScript Nullability Slowly Becomes Exhausting Why AI Agents Need a Project Layer - Part 1 Stop Hand-Editing MCP Configs: A Zero-Dependency Go CLI What I Learned Working With Microsoft, SQUAD(GTCO), and Different Tech Communities 🧠 Hermes Agent Assistant — A Modular AI Agent System with Planner, Executor & Memory Spring Boot Auto-Configuration Source Code: Nail This Interview Question The Ultimate Guide to Free AI API Keys: 6 Platforms You Need to Know Why 91% of AI Agents Fail in Production (And What the 9% Do Differently) TryHackMe | Battery | WALKTHROUGH Stop Guessing Your Regex — Test It Live in the Browser I Built FreelancEye, an Open-Source Mobile PWA for Finding Clients Beyond the Hype: My Production Playbook for Docker Swarm Top AI App Builder Platforms with Integrated Backend, Hosting & Database ECS vs EKS in 2026: An Honest Comparison from Someone Who Has Run Both in Production Hardening Your Node.js App Against Supply Chain & Remote Code Execution Attacks linux commands A Practical GEO Case: How an AI System Started Recommending Our Blog Your AI Agent Works 24/7 and Earns $0. I Built the Fix. Your AI Trading Agent Will Lose All Your Money — Here's How To Stop It Google I/O 2026: What Happens When Everything Connects? Why AI writes software but doesn’t build a good product Beyond the Hype: How Google I/O 2026 Secretly Democratized Production-Ready AI Agents with Managed Sandboxes. The Killer Assumption Test: How to Spot Doomed Product Decisions Before You Ship Stop Describing Your Bugs — Just Screenshot Them # I Built an AI Website Builder and Here's What Actually Happened Cooking an AI Campaign in 5 Minutes with Google Cloud AI APIs Your PM Retrospectives Are Lying to You How I Built a Free, Self-Hosted Pipeline That Auto-Generates Faceless YouTube Shorts TypeScript 54 to 58: The Features That Actually Matter in 2026 How to Tailor Your CV to Any Job Posting in 2026 The 7-day SaaS MVP loop: ship fast, then validate with people who actually show up 95. Fine-Tuning LLMs: Make a General Model Do Your Specific Job What Is a Frontend Developer Roadmap and Why You Need One Google shipped three Gemini "Flash" models. Picking the wrong one could 6 your AI bill Building an MCP server so Claude can query my SaaS analytics directly Google I/O 2026 and the Rise of the AI Ecosystem Your Docker Builds Are Slow Because You're Doing It Wrong (And I Built a Tool to Prove It) How do you verify GitHub contributions without trusting self-reported skills? CV vs Resume: What's the Difference and Which Do You Need? student Devs: Build AI Agents & Compete for $55K in Prizes 🚀 How to Write a Cover Letter That Actually Gets You Interviews Battle-Tested: What Getting Hacked Taught Me About Web & Cyber Security Unda folders za kuandika code >> mkdir src >> cd src >> mkdir controllers database routes services utils >> cd .. Directory: C:\Users\mwaki\microfinance-system Mode LastWriteTime Length Name Code Coverage .NET AI slop debt" is technical debt on fast forward. Nobody's ready. Multi-Head Latent Attention (MLA) Memoria - A Local AI Reading Companion Powered by Gemma 4 Stop Trusting Your Accuracy Score: A Practical Guide to Evaluating Logistic Regression Models Serious Question: Is the Developer Job Actually in Risk Due to AI? published: true tags: #discuss #career #ai #help rav2d: We ported an AV2 video decoder from C to Rust — here's why Your New Domain's First Week of GA4 Is a Lie: 4 Days of Raw Data from a Launch Gemma Guide - Real-Time Spatial Awareness for Blind Users From YAML to AI Agents: Building Smarter DevOps Pipelines with MCP A Field Guide to Human–AI Relations (For the Newly Bewildered Mortal) The AI Agent That Learns While It Works — A Complete Guide to Hermes Agent Inviting collaborators to work on ArchScope ArchScope is an interactive web-based tool that lets you design, visualize, and test system architectures with real-time performance simulations. Github - ArchScope is an interactive web-based tool that lets you Gemma 4: Google's Open-Weight AI Is a Game Changer for Developers Confessions of a Git Beginner: Why the Terminal Stopped Scaring Me Docker 容器化实战:从零到生产部署 🚀 I Built a Full Stack Miro Clone with Real-Time Collaboration using Next.js Building an African Economic Data Pipeline with Python, DuckDB & World Bank API llms.txt vs robots.txt vs ai.txt: The Developer's Cheat Sheet Intigriti Challenge 0526 Writeup Business Logic Flaws: How Attackers Skip Steps in Your App to Get What They Should Never Have Why Vibe Coders Need Boilerplates to Save Time, Tokens, and Build More Secure SaaS Projects Idle Cloud Cost Is the New Egress Cost Quark's Outlines: Python Traceback Objects Ghost in the Stack (Part 1): Why uninitialized variables remember old data Building a High-Performance Local Chess Assistant Extension with WebAssembly Stockfish and Manifest V3 Breaking the Trade-off Between Self-Custody and Intelligent Automation on the Stellar Network I Open-Sourced a Practical Fullstack Interview Preparation Repository (React + Node + System Design) 🚀 How I Started Coding as a Student (Beginner-Friendly Guide) WordPress vs. Ghost: Why Automated Bot Attacks Are Making us think much I tested 4 AI agent-governance tools against an open spec - here's the matrix zkML Inference Proof: What the Receipt Proves, and What the Model Still Does Not I Scored 1000/1000 on AWS Certified AI Practitioner (AIF-C01) Here's Every Resource I Used Go - Struct and Interface Handling JSON Requests in Go Storing Kamal secrets in AWS Secrets Manager and deploying to a cheap Hetzner VPS How I Caught and Fixed an N+1 Query in My Django REST API I got tired of paying $10/month to remove image backgrounds – so I built it for free How to Start Coding as a Student: A Complete Beginner’s Guide 🚀 Storing Kamal secrets in AWS Secrets Manager and deploying to a cheap Hetzner VPS
Build a scroll-driven WebGL hero in 30 lines
TommyDee · 2026-05-23 · via DEV Community

Hero sections that respond to scroll are one of those features that look complicated and actually aren't, once you have the right pieces. Two images, a shader that morphs between them, scroll position drives the morph. That's it. The rest is plumbing.

This tutorial builds exactly that — a scroll-driven WebGL hero — in about 30 lines of JavaScript. Plain HTML, no framework, no build step. Drop it into a CodePen or a static HTML file and it works.

What we're building

Three behaviors, tied together:

  1. As the user scrolls down into the hero section, image A morphs into image B.
  2. While the hero is centered in the viewport, the morph holds on image B.
  3. As the user scrolls down past the hero, image B morphs back to image A. That's the standard scroll-driven hero shape. Sites like Apple's product pages, Linear's marketing, and Vercel's case studies all use this pattern. The trick is the holding part — without it, the transition feels like it ends too fast and the user never sees the destination.

The dependencies

Two npm packages, both MIT, both tiny.

pnpm add @vysmo/scroll @vysmo/transitions

Enter fullscreen mode Exit fullscreen mode

Combined, that's about 6 KB gzipped. Less than a logo SVG.

The HTML

A section, a canvas, two <img> elements that we'll use as transition sources, plus some content above and below to give the page something to scroll through:

<section style="height: 200vh; padding-top: 50vh;">
  <h2>Above the hero</h2>
  <p>Scroll down to see the effect.</p>
</section>

<section id="hero" style="height: 100vh; position: relative;">
  <canvas id="hero-canvas" style="width: 100%; height: 100%;"></canvas>
  <img id="img-a" src="/from.jpg" style="display: none;" />
  <img id="img-b" src="/to.jpg" style="display: none;" />
</section>

<section style="height: 200vh;">
  <h2>Below the hero</h2>
  <p>Keep scrolling.</p>
</section>

Enter fullscreen mode Exit fullscreen mode

The two <img> elements are display: none because we're not rendering them as DOM — we're passing them to WebGL as texture sources. The browser still decodes them, which is what we need.

The JavaScript — 30 lines

import { Runner, crossZoom } from "@vysmo/transitions";
import { createScrollTransition, scrollPlateau } from "@vysmo/scroll";

const section = document.querySelector<HTMLElement>("#hero")!;
const canvas = document.querySelector<HTMLCanvasElement>("#hero-canvas")!;
const from = document.querySelector<HTMLImageElement>("#img-a")!;
const to = document.querySelector<HTMLImageElement>("#img-b")!;

// Wait for both images to decode before we use them as textures.
await Promise.all([from.decode(), to.decode()]);

// Match the canvas backing store to its CSS size so the shader renders sharp.
const dpr = Math.min(window.devicePixelRatio, 2);
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;

// One WebGL2 runner per canvas. It owns the compiled programs and FBOs.
const runner = new Runner({ canvas });

// Bind scroll progress to the transition.
createScrollTransition({
  section,
  runner,
  transition: crossZoom,
  from,
  to,
  // Hold the "to" image while the section is 30%–70% through the viewport.
  ease: scrollPlateau(0.3, 0.7),
});

Enter fullscreen mode Exit fullscreen mode

That's the whole thing. Let me walk through the parts that aren't obvious.

What scrollPlateau does

This is the part of the API I'd most like to spend a paragraph on, because it's the small idea that makes the whole pattern work.

Raw scroll progress is a 0 → 1 line as the section moves through the viewport. If you feed that directly into a transition, you get a morph that starts the moment the section enters and ends the moment it exits — which means the user only briefly sees the destination image as it flies past. Unsatisfying.

scrollPlateau(0.3, 0.7) reshapes that linear progress into a bathtub curve:

  • 0.0 → 0.3 of scroll progress: transition plays 0 → 1
  • 0.3 → 0.7 of scroll progress: transition stays at 1 (the "hold")
  • 0.7 → 1.0 of scroll progress: transition plays 1 → 0 The result: the morph happens quickly as the section enters, the destination image gets to live on screen for the comfortable middle stretch, and the morph reverses on exit. Visually, the user feels like they "arrived" at something, instead of like something whisked past them.

You can change the plateau bounds to taste — scrollPlateau(0.1, 0.9) makes the entry and exit transitions very fast, scrollPlateau(0.4, 0.6) is slower and more deliberate.

Swap the transition for a totally different feel

The line transition: crossZoom is the one that defines the look. There are 60 built-in transitions, and switching is one identifier:

import { pageCurl } from "@vysmo/transitions";
// ...
createScrollTransition({ section, runner, transition: pageCurl, from, to, ease });

Enter fullscreen mode Exit fullscreen mode

Now image A peels back like a page turn to reveal image B as you scroll. Same 30 lines of code, completely different aesthetic.

A few transitions worth trying for a hero:

  • crossZoom — the cinematic default; image A zooms in as it fades into B
  • pageCurl — editorial, like turning a magazine page
  • paintBleed — a paint-pour reveal, looks great for product launches
  • warpZoom — a chromatic warp; perfect for tech aesthetics
  • glassShatter — image A shatters into B; high-drama, use sparingly Each is a one-line change to the import and the transition prop. The catalog has all of them with live previews.

What's happening under the hood

In case the magic feels suspicious, here's what's actually running:

  1. createScrollTransition registers your section with a shared IntersectionObserver and a single requestAnimationFrame loop (one rAF for all Vysmo scroll bindings on the page — that part matters for performance when you have multiple sections).
  2. On every animation frame where the section is intersecting the viewport, it computes a raw 0 → 1 progress value from the section's bounding-box position.
  3. It passes that value through the ease function (scrollPlateau in our case) to get a morphed progress.
  4. It calls runner.render(transition, { from, to, progress }). The runner has the compiled WebGL2 shader cached, so this is just uniform updates and a draw call — sub-millisecond on a modern phone. No internal timers, no playback state, no "play" / "pause" / "reverse" — the renderer is idempotent. You give it a progress value, it draws that frame. Scroll up and the same code path runs the transition backwards. That's why the same library works equally well for rAF-driven animations, GSAP timelines, video exports, and scroll bindings.

Common gotchas

Image must be decoded first. If you skip the await Promise.all([from.decode(), to.decode()]) line, the first render might happen with one or both textures empty, and you'll see a flash of black. Always decode before passing to the Runner.

Canvas DPR. Without the canvas.width = canvas.clientWidth * dpr lines, the canvas backing store is whatever the browser defaults to (usually 300×150 px), which makes everything look like a pixelated mess. Match the backing store to the CSS size times device pixel ratio.

Don't run on iOS Low Power Mode. WebGL2 still works there but performance is throttled — the rAF callback may only fire 30 times per second instead of 60. Your transition will look choppy. Test on a real phone, not just your laptop with throttling disabled.

When you'd want more

This 30-line version covers a single hero. As your page gets more complex — multiple scroll-driven sections, scroll-driven text reveals layered on top, scroll-driven effects (a bloom that ramps as you enter, identity in the middle, fades on exit) — the same @vysmo/scroll package has primitives for those too:

  • createScrollEffect — drives a @vysmo/effects filter through the same three-zone envelope
  • createScrollProgress — raw 0 → 1 emitter you can wire to anything (opacity, transform, your own state)
  • sharedScrollObserver — for when you're building your own scroll-driven renderer and want to plug into the same single-rAF batching But you don't need any of that today. 30 lines, two images, one transition, one envelope. That's a shippable hero.

Try it

Full example, copy-pasteable, including the HTML scaffolding:

<!doctype html>
<html>
  <head><title>Vysmo Hero Demo</title></head>
  <body style="margin:0;font-family:system-ui;">
    <section style="height:200vh;padding-top:50vh;">
      <h2 style="text-align:center;">Scroll down</h2>
    </section>

    <section id="hero" style="height:100vh;position:relative;">
      <canvas id="hero-canvas" style="width:100%;height:100%;display:block;"></canvas>
      <img id="img-a" src="/from.jpg" style="display:none;" />
      <img id="img-b" src="/to.jpg" style="display:none;" />
    </section>

    <section style="height:200vh;">
      <h2 style="text-align:center;">Keep scrolling</h2>
    </section>

    <script type="module">
      import { Runner, crossZoom } from "https://esm.sh/@vysmo/transitions";
      import { createScrollTransition, scrollPlateau } from "https://esm.sh/@vysmo/scroll";

      const section = document.querySelector("#hero");
      const canvas = document.querySelector("#hero-canvas");
      const from = document.querySelector("#img-a");
      const to = document.querySelector("#img-b");

      await Promise.all([from.decode(), to.decode()]);

      const dpr = Math.min(window.devicePixelRatio, 2);
      canvas.width = canvas.clientWidth * dpr;
      canvas.height = canvas.clientHeight * dpr;

      const runner = new Runner({ canvas });
      createScrollTransition({
        section, runner,
        transition: crossZoom,
        from, to,
        ease: scrollPlateau(0.3, 0.7),
      });
    </script>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

Drop two images named from.jpg and to.jpg next to that file, open it in a browser, and you have a working scroll-driven WebGL hero.

The full catalog of transitions with live previews is at vysmo.com/transitions, and the scroll package docs are at vysmo.com/scroll. Source: github.com/vysmodev/vysmo. All MIT.