Sunday, June 29, 2025
Home BlogDesign a kaleidoscope that generates symmetrical patterns based on mouse movements. Use pj5s

Design a kaleidoscope that generates symmetrical patterns based on mouse movements. Use pj5s

by vasudigital0351

In this article i have established the main kaleidoscope logic and prepared several convenient controls. You will be constantly drawing colours which will change and they will give the effect of a constantly changing rainbow and the symmetrical balanced lines will make interesting patterns in the mirror.

Now you are able:

  • Using the slider of the “Symmetry” adjust the number of reflections.
  • With the slider marked “Brush Size” you can adjust the thickness of your lines.
  • Clear with the button and start a new canvas.
  • With the button “Save”,” save your favorite creations as PNG image.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive Kaleidoscope</title>
    <!-- Tailwind CSS for styling the controls -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- p5.js library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
    <style>
        /* Basic styles for the body and canvas */
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            background-color: #111827; /* Dark background for the page */
            font-family: 'Inter', sans-serif;
        }
        main {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }
        /* Style to ensure the canvas doesn't have a default block display margin */
        canvas {
            display: block;
        }
    </style>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body>

    <!-- Header with controls for the kaleidoscope -->
    <div id="controls" class="fixed top-0 left-0 right-0 bg-gray-800/50 backdrop-blur-md p-4 z-10 shadow-lg">
        <div class="container mx-auto flex flex-wrap items-center justify-center gap-4 md:gap-6">
            <!-- Symmetry Control -->
            <div class="flex items-center space-x-2 text-white">
                <label for="symmetrySlider" class="text-sm font-medium">Symmetry:</label>
                <input id="symmetrySlider" type="range" min="2" max="30" value="8" class="w-32 md:w-40 cursor-pointer">
                <span id="symmetryValue" class="text-sm font-bold w-6 text-center">8</span>
            </div>
            
            <!-- Stroke Weight Control -->
            <div class="flex items-center space-x-2 text-white">
                <label for="strokeSlider" class="text-sm font-medium">Brush Size:</label>
                <input id="strokeSlider" type="range" min="1" max="40" value="4" class="w-32 md:w-40 cursor-pointer">
                <span id="strokeValue" class="text-sm font-bold w-6 text-center">4</span>
            </div>

            <!-- Buttons -->
            <div class="flex items-center space-x-3">
                <button id="clearButton" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200 text-sm font-semibold shadow-md">Clear</button>
                <button id="saveButton" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors duration-200 text-sm font-semibold shadow-md">Save</button>
            </div>
        </div>
    </div>

    <!-- The p5.js canvas will be injected here -->
    <main id="canvas-container"></main>

    <script>
        // This is the main p5.js sketch for the kaleidoscope
        const sketch = (p) => {
            // Global variables for the sketch
            let symmetry = 8;
            let angle;
            let strokeWeightValue = 4;
            
            // The setup function runs once when the sketch starts
            p.setup = () => {
                // Create canvas and attach it to the container element
                const canvasContainer = document.getElementById('canvas-container');
                const canvas = p.createCanvas(p.windowWidth, p.windowHeight);
                canvas.parent(canvasContainer);

                // Set initial drawing properties
                p.angleMode(p.DEGREES); // Use degrees for rotation
                p.colorMode(p.HSB, 360, 100, 100, 100); // HSB color mode is great for smooth color transitions
                p.background(15, 15, 15); // Dark background for the drawing canvas
                
                // Calculate the angle for each symmetrical slice
                angle = 360 / symmetry;
            };

            // The draw function runs continuously in a loop
            p.draw = () => {
                // Move the origin to the center of the canvas
                // This makes rotations and symmetrical drawing much easier
                p.translate(p.width / 2, p.height / 2);

                // Only draw if the mouse is pressed and inside the canvas
                if (p.mouseIsPressed && 
                    p.mouseX > 0 && p.mouseX < p.width && 
                    p.mouseY > 0 && p.mouseY < p.height) {
                    
                    // Get mouse coordinates relative to the new (center) origin
                    const mx = p.mouseX - p.width / 2;
                    const my = p.mouseY - p.height / 2;
                    const pmx = p.pmouseX - p.width / 2;
                    const pmy = p.pmouseY - p.height / 2;
                    
                    // Set the drawing style
                    // The hue cycles through all colors based on frameCount, creating a rainbow effect
                    const hue = p.frameCount % 360;
                    p.stroke(hue, 80, 100, 50); // Rainbow color with some transparency
                    p.strokeWeight(strokeWeightValue);

                    // Loop through each symmetry slice to draw the pattern
                    for (let i = 0; i < symmetry; i++) {
                        p.rotate(angle);
                        
                        // Draw a line from the previous mouse position to the current one
                        // This creates smooth, continuous strokes
                        p.line(mx, my, pmx, pmy);
                        
                        // To create the mirror effect within each slice, we flip the
                        // coordinate system horizontally and draw the line again.
                        p.push(); // Save the current drawing state
                        p.scale(-1, 1); // Flip horizontally
                        p.line(mx, my, pmx, pmy);
                        p.pop(); // Restore the original drawing state
                    }
                }
            };
            
            // This function is called whenever the window is resized
            p.windowResized = () => {
                p.resizeCanvas(p.windowWidth, p.windowHeight);
                p.background(15, 15, 15); // Clear background on resize
            };

            // ---- INTERACTION HANDLERS ----
            
            // Function to clear the canvas
            const clearCanvas = () => {
                p.background(15, 15, 15);
            };

            // Function to update symmetry
            const updateSymmetry = (value) => {
                symmetry = value;
                angle = 360 / symmetry;
            };
            
            // Function to update stroke weight
            const updateStrokeWeight = (value) => {
                strokeWeightValue = value;
            };

            // Attach functions to the window object to make them accessible by HTML event listeners
            window.clearCanvas = clearCanvas;
            window.updateSymmetry = updateSymmetry;
            window.updateStrokeWeight = updateStrokeWeight;
            window.saveCanvas = () => p.saveCanvas('kaleidoscope', 'png');
        };

        // Create a new p5 instance and run the sketch
        new p5(sketch);
        
        // ---- DOM EVENT LISTENERS ----
        
        document.addEventListener('DOMContentLoaded', () => {
            const symmetrySlider = document.getElementById('symmetrySlider');
            const symmetryValue = document.getElementById('symmetryValue');
            const strokeSlider = document.getElementById('strokeSlider');
            const strokeValue = document.getElementById('strokeValue');
            const clearButton = document.getElementById('clearButton');
            const saveButton = document.getElementById('saveButton');
            
            // Update symmetry from slider
            symmetrySlider.addEventListener('input', (e) => {
                const val = parseInt(e.target.value, 10);
                symmetryValue.textContent = val;
                window.updateSymmetry(val);
            });
            
            // Update stroke weight from slider
            strokeSlider.addEventListener('input', (e) => {
                const val = parseInt(e.target.value, 10);
                strokeValue.textContent = val;
                window.updateStrokeWeight(val);
            });

            // Clear button functionality
            clearButton.addEventListener('click', () => {
                window.clearCanvas();
            });

            // Save button functionality
            saveButton.addEventListener('click', () => {
                window.saveCanvas();
            });
        });

    </script>
</body>
</html>

Output:

Video Preview

You may also like

Leave a Comment

About Us

Lorem ipsum dolor sit amet, consect etur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis..

Feature Posts

Newsletter