How Shaders Can Shrink, Beautify, and Speed Up Your Web Pages – A WebGL Tutorial
This article explains how programmable shaders running on the GPU form the foundation of modern graphics rendering, and demonstrates step‑by‑step how to replace a large transparent PNG background, create animated particle effects, and draw complex patterns directly with GLSL in WebGL, resulting in smaller, more dynamic, and faster web pages.
Programmable Shaders
Shader programs run on the GPU and are the core of modern graphics rendering, giving developers per‑pixel coloring capabilities. APIs such as OpenGL, OpenGL ES, DirectX, and the newer Vulkan all rely heavily on shaders.
WebGL brings 3D rendering to the browser, but shaders can also be used for other cool effects. Recently I applied shader techniques to a business page and documented the process.
Large Transparent Background
The original page used a 501 KB transparent PNG as a background, which is hard to compress because of the alpha channel. By splitting the PNG into two non‑transparent JPEGs—one storing the RGB channels and the other storing the alpha channel in the red channel—we reduced the size to 41.5 KB (about 8 % of the original).
The shader samples the two textures, combines them, and reconstructs the original image.
precision mediump float;
uniform vec2 uResolution;
uniform sampler2D uImage;
void main(){
vec2 st = gl_FragCoord.xy / uResolution;
vec4 c2 = texture2D(uImage, vec2(st.x, st.y * 0.5)); // Alpha channel
vec4 c1 = texture2D(uImage, vec2(st.x, st.y * 0.5 + 0.5)); // RGB channel
gl_FragColor = vec4(c1.xyz, c2.r > 0.6 ? c2.r : 0.0);
}Moving Background
The original background consisted of a static red gradient with scattered yellow particles. By generating the particles procedurally with shaders, we avoid loading any image assets and gain smooth performance even with many particles.
We first create a gradient background:
vec3 bgColor(){
return mix(vec3(0.96,0.02,0.16), vec3(0.96,0.25,0.21), y);
}Then we generate a random noise function and use it to place particles:
vec2 random2(vec2 st){
return fract(sin(dot(st, vec2(127.1,311.7))) * 43758.5453123);
}Particles are drawn as lines using a custom line function that smooths the edges with smoothstep. Multiple lines are combined with a maxList helper to keep the brightest value.
float line(float e, float w, float d, float p){
float e1 = e - w/2.0;
float e2 = e + w/2.0;
return smoothstep(e1 - d/2.0, e1 + d/2.0, p) *
smoothstep(e2 + d/2.0, e2 - d/2.0, p);
}To animate the particles we add a time‑dependent offset to the fragment coordinates before sampling the noise.
float inFrag(){
vec2 st = gl_FragCoord.xy / uResolution;
st *= 60.0;
st.y += uTime * 2.0;
float res = noise2(floor(st), fract(st));
return smoothstep(0.35, 0.5, res);
}Hand‑drawn Patterns
Complex window patterns can also be generated with shaders. A basic line function draws straight lines, while rayV, rayH, and box functions create rays and rectangles.
float line(float e, float w, float d, float p){
float e1 = e - w/2.0;
float e2 = e + w/2.0;
return smoothstep(e1 - d/2.0, e1 + d/2.0, p) *
smoothstep(e2 + d/2.0, e2 - d/2.0, p);
}
float rayV(vec2 ep, float w, float d, float dir, vec2 st){
float pct = line(ep.x, w, d, st.x);
if((st.y - ep.y) * dir < 0.0) pct = 0.0;
return pct;
}
float box(vec2 center, float width, float height, float w, float d, vec2 st){
float l1 = line(center.x, width + w, d, st.x);
float l2 = line(center.y, height + w, d, st.y);
return l1 * l2;
}By combining several lines, rays, and boxes we recreate the original window pattern without any image assets.
Final Composition
We blend the pattern with a shadow function to give depth, then clip the result with a circular mask that mimics the original rounded window.
float shadow(){
vec2 st = (gl_FragCoord.xy - center) / uResolution.x * 0.5;
return smoothstep(0.9, 0.3, st.y + 0.5 * st.x * st.x - 0.1);
}
vec3 veins(){
return mix(veinsBgColor, veinsFgColor, p) * shadow();
}
vec4 clip(vec3 color){
float r = uResolution.x * 0.4;
vec2 dxy = gl_FragCoord.xy - center;
float dist = sqrt(dxy.x*dxy.x + dxy.y*dxy.y);
float p = smoothstep(0.95, 0.96, dist/r);
return vec4(mix(color, borderColor, p), 1.0 - p);
}The final main function renders the veins, applies the circular mask, and outputs the final color.
void main(){
vec3 res = veins();
res = circle(res);
gl_FragColor = clip(res);
}Conclusion
These three examples show that thoughtful use of WebGL and GLSL can dramatically reduce image dependencies, eliminate large transparent layers, and enable global or background animations. Because shaders operate per‑pixel, they give developers great flexibility to create lightweight, visually striking effects.
Other small shader tricks used in the original page—such as an animated loading bar and a moving highlight over the text—are omitted for brevity.
Image credit: https://unsplash.com/photos/NFs6dRTBgaM by @Ferdinand Stöhr
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
