Free Webflow components / Effect: animated shader glow button
Webflow button with an animated WebGL shader
A free WebGL shader button for Webflow: a live shader flows inside the button and speeds up on hover. Copy it, press ⌘V, and change the label.
- Responsive
- Hover states
What you receive in Webflow
- 10 native elements and 7 classes.
- Breakpoints: mobile landscape (767px).
- States: hover.
- 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
- Click Copy for Webflow.
- Open your page in the Webflow Designer.
- Press ⌘V or Ctrl+V.
HTML
<section class="sh">
<div class="sh-inner">
<h2 class="sh-title">Ready when you are</h2>
<p class="sh-text">A live WebGL shader flows inside the button and speeds up on hover. Browsers without WebGL see the gradient behind it.</p>
<a class="sh-btn" href="#"><span class="sh-glow" aria-hidden="true"></span><span class="sh-label">Start building</span></a>
</div>
</section>
<style>
.sh { font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; background: #050510; color: #f5f5ff; padding: 128px 24px; }
.sh-inner { max-width: 640px; margin: 0 auto; text-align: center; }
.sh-title { font-size: 48px; line-height: 1.08; letter-spacing: -0.03em; margin: 0 0 14px; }
.sh-text { font-size: 17px; line-height: 1.6; color: #a5a6c8; margin: 0 0 48px; }
.sh-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; height: 64px; padding: 0 40px; border-radius: 999px; overflow: hidden; color: #ffffff; text-decoration: none; font-size: 18px; font-weight: 700; background-image: linear-gradient(110deg, #4f46e5 0%, #c026d3 45%, #f97316 100%); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18) inset, 0 20px 60px -20px rgba(192, 38, 211, 0.7); transition: transform 0.3s ease, box-shadow 0.3s ease; }
.sh-btn:hover { transform: translateY(-2px); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.3) inset, 0 26px 70px -18px rgba(192, 38, 211, 0.9); }
.sh-btn:focus-visible { outline: 3px solid #f0abfc; outline-offset: 4px; }
.sh-glow { position: absolute; top: 0; right: 0; bottom: 0; left: 0; }
.sh-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.sh-label { position: relative; z-index: 1; text-shadow: 0 1px 12px rgba(0, 0, 0, 0.35); }
@media (max-width: 767px) { .sh { padding: 88px 20px; } .sh-title { font-size: 34px; } }
</style>
<script>
(function () {
var VS = "attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}";
var FS = "precision mediump float;uniform vec2 r;uniform float t;uniform float h;" +
"float n(vec2 p){return sin(p.x)*sin(p.y);}" +
"void main(){vec2 uv=gl_FragCoord.xy/r;vec2 q=uv*vec2(r.x/r.y,1.)*3.;float s=t*(.35+h*.9);" +
"for(int i=1;i<5;i++){float f=float(i);q+=vec2(sin(q.y*1.3+s*.7+f),cos(q.x*1.1-s*.6+f*1.7))*.45/f;}" +
"float v=.5+.5*sin(q.x+q.y+s);vec3 a=vec3(.31,.27,.9),b=vec3(.75,.15,.83),c=vec3(.98,.45,.09);" +
"vec3 col=mix(mix(a,b,smoothstep(0.,.6,v)),c,smoothstep(.55,1.,v));col+=h*.12;gl_FragColor=vec4(col,1.);}";
var reduce = window.matchMedia && matchMedia("(prefers-reduced-motion: reduce)").matches;
document.querySelectorAll(".sh-btn").forEach(function (btn) {
var holder = btn.querySelector(".sh-glow");
var cv = document.createElement("canvas"); cv.className = "sh-canvas";
var gl = cv.getContext("webgl", { antialias: false, premultipliedAlpha: false });
if (!gl) return;
function sh(type, src) { var s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s; }
var pr = gl.createProgram(); gl.attachShader(pr, sh(gl.VERTEX_SHADER, VS)); gl.attachShader(pr, sh(gl.FRAGMENT_SHADER, FS)); gl.linkProgram(pr);
if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) return;
gl.useProgram(pr);
var buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(pr, "p"); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
var uR = gl.getUniformLocation(pr, "r"), uT = gl.getUniformLocation(pr, "t"), uH = gl.getUniformLocation(pr, "h");
holder.appendChild(cv);
var hover = 0, target = 0, start = performance.now(), visible = true;
btn.addEventListener("pointerenter", function () { target = 1; visible = true; });
btn.addEventListener("pointerleave", function () { target = 0; });
btn.addEventListener("focus", function () { target = 1; visible = true; });
btn.addEventListener("blur", function () { target = 0; });
var seen = false; // pause only after it has been seen and then scrolled away
if ("IntersectionObserver" in window) new IntersectionObserver(function (es) { if (es[0].isIntersecting) seen = true; visible = es[0].isIntersecting || !seen; }).observe(btn);
function frame(now) {
requestAnimationFrame(frame);
if (!visible) return;
var dpr = Math.min(2, window.devicePixelRatio || 1), w = btn.clientWidth * dpr, h = btn.clientHeight * dpr;
if (cv.width !== w || cv.height !== h) { cv.width = w; cv.height = h; gl.viewport(0, 0, w, h); }
hover += (target - hover) * 0.08;
gl.uniform2f(uR, w, h); gl.uniform1f(uT, reduce ? 0 : (now - start) / 1000); gl.uniform1f(uH, hover);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
requestAnimationFrame(frame);
});
})();
</script>
More examples
Questions
What if a browser doesn't support WebGL?
The button shows a soft gradient glow instead, so it always looks finished.
Does it slow the page down?
The shader is small and only draws inside the button. It pauses when the button is off screen.
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.