Welcome
M1 | The Technical Fundamentals of the Web M2 | Advanced CMS M3 | UX/UI Products M4 | Advanced Prototyping M5 | Advanced Integration & Core Project Training Bonus
About Log In Create Account
LMS Pro LMS Pro
  • Welcome
  • Web Production & Interface Training
    • Module 1 — Technical Fundamentals of the Web
    • Module 2 — Advanced CMS
    • Module 3 — Product UX & UI
    • Module 4 — Advanced Prototyping
    • Module 5 — Advanced Integration & Capstone Project
    • Bonus Module
  • About
  • Log In
  • Sign Up
Skip to content

LMS Pro

LMS Pro — Your platform to learn, grow, succeed

Professional Training

UX/UI Designer

Learn how to generate abstract and geometric wallpapers

Posted on 14 February 202615 February 2026 By LMS Pro No Comments on Apprendre à générer des fonds d’écran abstrait et géométrique
Bonus Module
YouTubeVJ Loop NeonAbstract background videoAbstract Background Video loopAbstract Background

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.

Module: Module 6: Bonus Module

Level : Beginner

Duration : 20 minutes

Prerequisites: None

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); } }


Result
● Ready
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); }


Result
● Ready
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); }


Result
● Ready
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); } }


Result
● Ready
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(); }


Result
● Ready
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:


  • 🔗
    Official P5.js documentation - Animation guide
    →

  • 🔗
    CSS-Tricks - Complete Guide to Neon and Glow Effects
    →

Validate your knowledge

Test your understanding with this 10-question quiz:

Loading quiz...
Categories: Bonus Design

Post navigation

❮ Previous Post: Introduction to Arcade Game Development
Next Post: Advanced CSS Animations Part 1 ❯

See also

Bonus Module
Create your own UX/UI design tools using AI
January 27, 2026
Bonus Module
Introduction to Arcade Game Development
February 14, 2026

LMS Pro LMS Pro is an educational platform dedicated to Professional Training. LMS Pro 2026 All rights reserved

You must log in

Forgot password?
No account yet., click here
LMS Pro

Create an account

Fill in this information

Already have an account? Log in
LMS Pro
English
French