Learn how to generate abstract and geometric wallpapers
Dive into the fascinating world of generative design to create animated wallpapers with abstract and geometric shapes. Discover how to master neon effects on a dark background to achieve captivating, endless animations with 5 step-by-step tutorials perfect for beginners.
Educational objectives
- Understanding the basic principles of CSS and JavaScript animations to create visual effects
- Mastering geometric shape generation techniques with p5.js
- Apply neon and glow effects to a dark background for a modern look.
- Create smooth and optimized infinite animation loops
- Develop harmonious abstract compositions with luminous colors
Understanding CSS properties for lighting effects
Neon effects rely primarily on the CSS property box-shadow combined with smooth transitions. To create a convincing lighting effect, we use several shadows stacked with different blur radii. The first shadow, smaller and more intense, simulates direct sunlight, while the larger shadows create the characteristic neon halo. The property filter: blur() can also be used to soften contours and enhance the illusion of diffused light.
Animation and keyframes for a dynamic rendering
CSS animations allow you to vary the light intensity over time thanks to @keyframes. By gradually modifying the opacity, shadow size, or even color, we achieve very realistic pulsing effects. The function ease-in-out ensures natural transitions, avoiding visual jolts. For infinite animations, we use animation-iteration-count: infinite coupled with animation-direction: alternate to create a hypnotic back-and-forth motion.
Key point The neon effect requires a dark background to reveal its full visual impact. Always use saturated colors (cyan, magenta, electric yellow) on black or very dark backgrounds.
Key points to remember
Neon effects in CSS rely on layering colored shadows of different sizes, animated in a loop to simulate the characteristic pulse of electric light.
🎯 Mini exercise: Create a pulsating neon circle
Create a pulsating circle with a cyan neon effect on a black background using the CSS box-shadow and animation properties.
Reference code:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cercle Néon Pulsant</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="neon-circle"></div>
</div>
</body>
</html>
body { margin: 0; padding: 0; background: #0a0a0a; min-height: 100vh; overflow:hidden; } .container { display: flex; justify-content: center; align-items: center; min-height: 100vh; } .neon-circle { width: 200px; height: 200px; border-radius: 50%; background: rgba(0, 255, 255, 0.1); border: 2px solid #00ffff; box-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff, 0 0 40px #00ffff, 0 0 80px #00ffff, inset 0 0 10px rgba(0, 255, 255, 0.2); animation: neonPulse 2s ease-in-out infinite alternate; } @keyframes neonPulse { 0% { box-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff, 0 0 40px #00ffff, 0 0 80px #00ffff, inset 0 0 10px rgba(0, 255, 255, 0.2); } 100% { box-shadow: 0 0 20px #00ffff, 0 0 40px #00ffff, 0 0 80px #00ffff, 0 0 160px #00ffff, inset 0 0 20px rgba(0, 255, 255, 0.4); transform: scale(1.05); } }
Monaco Editor v0.45
Introduction to p5.js for generative design
P5.js is a JavaScript library designed to make creative programming accessible to designers. Based on Processing, it allows you to create interactive animations and visualizations directly in the browser. To get started, we'll use two essential functions: setup() which runs only once at startup, and draw() which repeats in a loop to create the animation. The function createCanvas() defines the workspace, while background() It clears the screen at each frame to avoid unwanted overlays.
Creating dynamic geometric shapes
Basic geometric shapes like circles, rectangles, and triangles can be animated by changing their parameters over time. Using trigonometric functions such as sin() And cos() It allows for the creation of fluid and natural movements. By combining multiple shapes with phase shifts, we obtain complex and harmonious compositions. The function translate() shifts the coordinate system, allowing groups of elements to be easily rotated or moved around a central point.
Key point P5.js uses a coordinate system where (0,0) is located at the top left. To center elements, use width/2 and height/2 as reference coordinates.
Key points to remember
P5.js uses a simple structure with setup() for initialization and draw() for loop animation, allowing easy creation of animated geometric shapes.
🎯 Mini-exercise: Rotating geometric triangles
Create several colored triangles that rotate around the center of the screen at different speeds for a mesmerizing geometric effect.
Reference code:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Triangles Géométriques</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1/lib/p5.min.js"></script>
<style>
body { margin: 0; padding: 0; background: #0a0a0a; }
</style>
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
let angle = 0; let triangles = []; function setup() { createCanvas(windowWidth, windowHeight); // Create multiple triangles with different properties for (let i = 0; i <6; i++) { triangles.push({ radius: 50 + i * 30, speed: 0.01 + i * 0.005, size: 40 + i * 10, color: [ random(100, 255), // R random(100, 255), // G random(100, 255) // B ], offset: i * PI / 3 }); } } function draw() { background(10, 10, 15, 50); // Trail effect translate(width / 2, height / 2); for (let triangle of triangles) { push(); // Position of the rotating triangle let x = cos(angle * triangle.speed + triangle.offset) * triangle.radius; let y = sin(angle * triangle.speed + triangle.offset) * triangle.radius; translate(x, y); rotate(angle * triangle.speed * 2); // Triangle style with glow fill effect(triangle.color[0], triangle.color[1], triangle.color[2], 150); stroke(triangle.color[0], triangle.color[1], triangle.color[2]); strokeWeight(2); // Draw the triangle beginShape(); for (let i = 0; i <3; i++) { let triX = cos(i * TWO_PI / 3) * triangle.size; let triY = sin(i * TWO_PI / 3) * triangle.size; vertex(triX, triY); } endShape(CLOSE); pop(); } angle += 0.02; } function windowResized() { resizeCanvas(windowWidth, windowHeight); }
Monaco Editor v0.45
Understanding repetitive patterns
Abstract patterns rely on the repetition of simple elements according to precise mathematical rules. In creative programming, we use nested loops to generate grids of elements where each position influences visual properties: color, size, rotation, or transparency. The function map() p5.js is particularly useful for converting the position of an element into visual parameters, creating smooth gradients and transitions throughout the composition.
Introduction to geometric fractals
Fractals are shapes that repeat at different scales, creating complex patterns from simple rules. The fractal tree is a perfect example: each branch divides into smaller branches at a constant angle and size ratio. In programming, we use recursion (a function that calls itself) to generate these structures. Parameters such as the branching angle, the reduction factor, and the number of iterations control the final appearance of the fractal.
Key point Fractals can quickly become very complex and slow down the animation. Limit the number of iterations (generally 5-8 levels) to maintain smooth performance.
Key points to remember
Abstract patterns use loops and mathematical functions to create harmonious repetitions, while fractals employ recursion to generate self-similar shapes at different scales.
🎯 Mini-exercise: Luminous Fractal Tree
Create a fractal tree that grows from the bottom of the screen with luminous branches that change color depending on their depth level.
Reference code:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Arbre Fractal Lumineux</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1/lib/p5.min.js"></script>
<style>
body { margin: 0; padding: 0; background: #050510; }
</style>
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
let angle = 0; function setup() { createCanvas(windowWidth, windowHeight); } function draw() { background(5, 5, 16); // Position at the bottom center of the screen translate(width / 2, height); // Dynamic angle based on time let branchAngle = map(sin(angle), -1, 1, PI/6, PI/4); // Draw the fractal tree drawBranch(100, 0, branchAngle); angle += 0.01; } function drawBranch(len, depth, branchAngle) { // Limit the depth for performance if (depth > 7 || len < 4) { return; } // Color based on depth let hue = map(depth, 0, 7, 180, 300); // Cyan to magenta let brightness = map(len, 4, 100, 100, 255); stroke(brightness, brightness * 0.8, 255); strokeWeight(map(len, 4, 100, 1, 4)); // Glow effect for main branches if (depth < 3) { drawingContext.shadowColor = `rgb(${brightness}, ${brightness * 0.8}, 255)`; drawingContext.shadowBlur = map(len, 4, 100, 5, 15); } else { drawingContext.shadowBlur = 0; } // Draw the branch line(0, 0, 0, -len); // Move to the end of the branch translate(0, -len); // Right branch push(); rotate(branchAngle); drawBranch(len * 0.75, depth + 1, branchAngle); pop(); // Left branch push(); rotate(-branchAngle); drawBranch(len * 0.75, depth + 1, branchAngle); pop(); // Central branch (sometimes) if (depth < 4 && random() < 0.3) { push(); rotate(random(-0.2, 0.2)); drawBranch(len * 0.6, depth + 2, branchAngle); pop(); } } function windowResized() { resizeCanvas(windowWidth, windowHeight); }
Monaco Editor v0.45
Performance management and fluidity
To maintain smooth animation, it is crucial to control the number of elements drawn in each frame. The use of frameRate() This allows you to limit the number of frames per second if necessary, but it's better to optimize the code itself. Avoid complex calculations in the loop draw() by pre-calculating the values in setup() or by using lookup tables. The function requestAnimationFrame() integrated into p5.js synchronizes the animation with the screen refresh rate for optimal rendering.
Perfect curl techniques
A perfect animation loop restarts imperceptibly at the end of its cycle. To achieve this, use periodic functions like sin() And cos() with phases calculated so that the initial and final values coincide. The modulo (%) is also useful for creating repeating cycles on positions or colors. Plan your animation for a fixed duration (e.g., 10 seconds) and use millis() % duration to obtain a normalized position in the cycle.
Key point Always test your animations on different devices. What runs at 60 FPS on a powerful computer may be choppy on a smartphone. Implement adaptive detail levels if necessary.
Key points to remember
The optimization relies on limiting calculations per frame and using periodic mathematical functions to create perfect and fluid loops.
🎯 Mini-exercise: Optimized sinusoidal waves
Create a system of colored waves that move smoothly and repeat perfectly in a loop, with a performance optimization system.
Reference code:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vagues Sinusoïdales Optimisées</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1/lib/p5.min.js"></script>
<style>
body { margin: 0; padding: 0; background: #0a0a15; }
#fps { position: fixed; top: 10px; left: 10px; color: #fff; font-family: monospace; }
</style>
</head>
<body>
<div id="fps">FPS: --</div>
<script src="sketch.js"></script>
</body>
</html>
let waves = []; let time = 0; let cycleDuration = 8000; // 8 seconds for a complete cycle let stepSize = 4; // Optimization: reduce the number of points let lastFrameTime = 0; let fpsCounter = 0; let lastFpsUpdate = 0; function setup() { createCanvas(windowWidth, windowHeight); // Pre-calculate wave properties for (let i = 0; i < 5; i++) { waves.push({ amplitude: random(30, 80), frequency: random(0.01, 0.03), speed: random(0.001, 0.005), yOffset: height * (0.2 + i * 0.15), color: { r: random(50, 255), g: random(100, 255), b: random(150, 255) }, phase: random(TWO_PI) // Random initial phase }); } // Optimization: limit the framerate if necessary frameRate(60); } function draw() { background(10, 10, 21, 180); // Slight trail effect // Calculate the normalized time for a perfect loop let currentTime = millis(); let normalizedTime = (currentTime % cycleDuration) / cycleDuration; time = normalizedTime * TWO_PI; // Convert to radians // Draw each wave for (let wave of waves) { drawWave(wave); } // Performance monitoring updateFPS(); } function drawWave(wave) { stroke(wave.color.r, wave.color.g, wave.color.b, 180); strokeWeight(2); fill(wave.color.r, wave.color.g, wave.color.b, 30); // Glow effect for the main waves drawingContext.shadowColor = `rgba(${wave.color.r}, ${wave.color.g}, ${wave.color.b}, 0.5)`; drawingContext.shadowBlur = 10; beginShape(); vertex(0, height); // Starting point at the bottom of the screen // Generate wave points with optimization for (let x = 0; x <= width; x += stepSize) { let y = wave.yOffset + sin((x * wave.frequency) + (time * wave.speed * cycleDuration) + wave.phase) * wave.amplitude + cos((x * wave.frequency * 0.7) + (time * wave.speed * 1.3 * cycleDuration)) * wave.amplitude * 0.3; vertex(x,y); } vertex(width, height); // End point at the bottom of the screen endShape(CLOSE); // Reset glow effect drawingContext.shadowBlur = 0; } function updateFPS() { fpsCounter++; let currentTime = millis(); if (currentTime - lastFpsUpdate > 1000) { document.getElementById('fps').textContent = `FPS: ${fpsCounter}`; fpsCounter = 0; lastFpsUpdate = currentTime; } } function windowResized() { resizeCanvas(windowWidth, windowHeight); // Recalculate the Y offsets of the waves for (let i = 0; i <waves.length; i++) { waves[i].yOffset = height * (0.2 + i * 0.15); } }
Monaco Editor v0.45
Assembly of visual elements
The final composition of an abstract wallpaper requires a layered approach to create visual depth. Start with a subtle gradient background, then add the main geometric elements (circles, polygons), and finally details like fractals or patterns. Each layer should have its own animation speed to create a natural parallax effect. The use of blending modes such as blendMode(SCREEN) Or blendMode(ADD) allows for the creation of light interactions between the layers, enhancing the desired neon effect.
Responsive adaptation and export formats
A good screensaver should adapt to all screen sizes, from smartphones to ultrawide displays. Use windowResized() to recalculate relative positions and sizes when the window changes dimensions. For export, consider converting to MP4 video for maximum compatibility: use saveFrames() To capture a sequence of images, then assemble them with an external tool. Alternatively, your p5.js sketch can run directly in a web browser as an HTML5 screensaver.
Key point For a screensaver, opt for slow, hypnotic animations rather than sudden movements. The goal is to create a relaxing atmosphere, not to stimulate attention.
Key points to remember
A successful composition superimposes several layers of animated elements at different speeds, with responsive adaptation for all screen formats.
🎯 Mini-exercise: Full screen saver
Create a complete screensaver combining geometric shapes, neon effects, and fluid animations with a layer system and responsive adaptation.
Reference code:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Économiseur d'écran Néon</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1/lib/p5.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
cursor: none;
}
canvas { display: block; }
.info {
position: fixed;
bottom: 20px;
right: 20px;
color: rgba(255,255,255,0.3);
font-family: 'Courier New', monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div class="info">Press Space to change mode</div>
<script src="sketch.js"></script>
</body>
</html>
let time = 0; let particles = []; let geometricShapes = []; let backgroundWaves = []; let currentMode = 0; let modeNames = ['Geometric Neon', 'Cosmic Fractals', 'Abstract Waves']; function setup() { createCanvas(windowWidth, windowHeight); colorMode(HSB, 360, 100, 100, 100); initializeElements(); } function initializeElements() { // Initialize background particles particles = []; for (let i = 0; i <50; i++) { particles.push({ x: random(width), y: random(height), speedX: random(-0.5, 0.5), speedY: random(-0.5, 0.5), size: random(1, 3), hue: random(180, 300), alpha: random(20, 60) }); } // Main geometric shapes geometricShapes = []; for (let i = 0; i <8; i++) { geometricShapes.push({ x: width * (0.2 + i * 0.1), y: height * 0.5, rotation: 0, rotationSpeed: random(-0.01, 0.01), size: random(40, 120), sides: floor(random(3, 8)), hue: (i * 45) % 360, orbitRadius: random(50, 150), orbitSpeed: random(0.005, 0.015), phase: random(TWO_PI) }); } // Background Waves backgroundWaves = []; for (let i = 0; i < 6; i++) { backgroundWaves.push({ yOffset: height * (0.1 + i * 0.15), amplitude: random(20, 60), frequency: random(0.005, 0.02), speed: random(0.001, 0.003), hue: (i * 60 + 180) % 360, alpha: random(10, 30) }); } } function draw() { // Dynamic background gradient drawBackground(); // Layer 1: Background waves drawBackgroundWaves(); // Layer 2: Floating particles drawParticles(); // Layer 3: Main geometric shapes drawGeometricShapes(); // Layer 4: Surface effects drawSurfaceEffects(); // Interface drawModeInfo(); time += 0.01; } function drawBackground() { // Dynamic gradient for (let y = 0; y <height; y += 2) { let inter = map(y, 0, height, 0, 1); let c1 = color(220 + sin(time * 0.5) * 20, 80, 5); let c2 = color(280 + cos(time * 0.3) * 30, 60, 15); let gradientColor = lerpColor(c1, c2, inter); stroke(gradientColor); line(0, y, width, y); } } function drawBackgroundWaves() { for (let wave of backgroundWaves) { stroke(wave.hue, 70, 80, wave.alpha); strokeWeight(1); noFill(); beginShape(); for (let x = 0; x <= width; x += 5) { let y = wave.yOffset + sin(x * wave.frequency + time * wave.speed) * wave.amplitude + cos(x * wave.frequency * 0.7 + time * wave.speed * 1.3) * wave.amplitude * 0.5; vertex(x, y); } endShape(); } } function drawParticles() { for (let particle of particles) { // Update position particle.x += particle.speedX; particle.y += particle.speedY; // Bounce on edges if (particle.x < 0 || particle.x > width) particle.speedX *= -1; if (particle.y < 0 || particle.y > height) particle.speedY *= -1; // Display fill(particle.hue, 60, 90, particle.alpha); noStroke(); ellipse(particle.x, particle.y, particle.size); // Effect glow drawingContext.shadowColor = `hsla(${particle.hue}, 70%, 70%, 0.3)`; drawingContext.shadowBlur = particle.size * 2; ellipse(particle.x, particle.y, particle.size * 0.5); drawingContext.shadowBlur = 0; } } function drawGeometricShapes() { for (let shape of geometricShapes) { push(); // Orbital position let orbitX = shape.x + cos(time * shape.orbitSpeed + shape.phase) * shape.orbitRadius; let orbitY = shape.y + sin(time * shape.orbitSpeed + shape.phase) * shape.orbitRadius * 0.6; translate(orbitX, orbitY); rotate(shape.rotation + time * shape.rotationSpeed); // Neon style stroke(shape.hue, 80, 100, 70); strokeWeight(2); fill(shape.hue, 60, 100, 15); // Multiple glow effect drawingContext.shadowColor = `hsla(${shape.hue}, 80%, 60%, 0.8)`; drawingContext.shadowBlur = 15; // Draw polygon beginShape(); for (let i = 0; i < shape.sides; i++) { let angle = map(i, 0, shape.sides, 0, TWO_PI); let x = cos(angle) * shape.size; let y = sin(angle) * shape.size; vertex(x,y); } endShape(CLOSE); drawingContext.shadowBlur = 0; pop(); } } function drawSurfaceEffects() { // Connection lines between nearby shapes stroke(200, 50, 80, 20); strokeWeight(1); for (let i = 0; i < geometricShapes.length - 1; i++) { for (let j = i + 1; j < geometricShapes.length; j++) { let shape1 = geometricShapes[i]; let shape2 = geometricShapes[j]; let x1 = shape1.x + cos(time * shape1.orbitSpeed + shape1.phase) * shape1.orbitRadius; let y1 = shape1.y + sin(time * shape1.orbitSpeed + shape1.phase) * shape1.orbitRadius * 0.6; let x2 = shape2.x + cos(time * shape2.orbitSpeed + shape2.phase) * shape2.orbitRadius; let y2 = shape2.y + sin(time * shape2.orbitSpeed + shape2.phase) * shape2.orbitRadius * 0.6; let distance = dist(x1, y1, x2, y2); if (distance < 200) { let alpha = map(distance, 0, 200, 30, 0); stroke(200, 50, 80, alpha); line(x1, y1, x2, y2); } } } } function drawModeInfo() { fill(0, 0, 100, 40); textAlign(LEFT, TOP); textSize(16); text(`Mode: ${modeNames[currentMode]}`, 20, 20); textSize(12); text(`${frameRate().toFixed(1)} FPS`, 20, 45); } function keyPressed() { if (key === ' ') { currentMode = (currentMode + 1) % modeNames.length; initializeElements(); } } function windowResized() { resizeCanvas(windowWidth, windowHeight); initializeElements(); }
Monaco Editor v0.45
Summary
Key points to remember
- CSS Neon Effects Use multiple stacked box shadows with different radii to create realistic light halos against a dark background.
- P5.js for the generative The setup()/draw() structure allows for the easy creation of animations with geometric shapes and trigonometric functions.
- Patterns and fractals Nested loops generate repetitive patterns, while recursion creates self-similar fractal structures.
- Performance optimization Limit calculations per frame, pre-calculate fixed values, and use periodic functions for perfect loops.
- Layered composition Layer animated elements at different speeds with responsive adaptation to create depth and fit all screens
Sources
To deepen your knowledge:
Validate your knowledge
Test your understanding with this 10-question quiz:

