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>InteractiveKaleidoscope</title> <!-- TailwindCSSfor 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> <!-- StrokeWeight Control --> <div class="flex items-center space-x-2 text-white"> <label for="strokeSlider" class="text-sm font-medium">BrushSize:</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> <!-- Thep5.js canvas will be injected here --> <main id="canvas-container"></main> <script>// This is the main p5.js sketch for the kaleidoscopeconst sketch = (p) => {// Global variables for the sketch let symmetry = 8; let angle; let strokeWeightValue = 4;// The setup function runs once when the sketch startsp.setup = () => {// Create canvas and attach it to the container elementconst canvasContainer = document.getElementById('canvas-container');const canvas = p.createCanvas(p.windowWidth, p.windowHeight);canvas.parent(canvasContainer);// Set initial drawing propertiesp.angleMode(p.DEGREES); // Use degrees for rotationp.colorMode(p.HSB, 360, 100, 100, 100); // HSB color mode is great for smooth color transitionsp.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 loopp.draw = () => {// Move the origin to the center of the canvas// This makes rotations and symmetrical drawing much easierp.translate(p.width / 2, p.height / 2);// Only draw if the mouse is pressed and inside the canvasif (p.mouseIsPressed && p.mouseX > 0 && p.mouseX < p.width && p.mouseY > 0 && p.mouseY < p.height) {// Get mouse coordinates relative to the new (center) originconst 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 effectconst hue = p.frameCount % 360;p.stroke(hue, 80, 100, 50); // Rainbow color with some transparencyp.strokeWeight(strokeWeightValue);// Loop through each symmetry slice to draw the patternfor (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 strokesp.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 statep.scale(-1, 1); // Flip horizontallyp.line(mx, my, pmx, pmy);p.pop(); // Restore the original drawing state } } };// This function is called whenever the window is resizedp.windowResized = () => {p.resizeCanvas(p.windowWidth, p.windowHeight);p.background(15, 15, 15); // Clear background on resize };// ---- INTERACTION HANDLERS ----// Function to clear the canvasconst clearCanvas = () => {p.background(15, 15, 15); };// Function to update symmetryconst updateSymmetry = (value) => { symmetry = value; angle = 360 / symmetry; };// Function to update stroke weightconst updateStrokeWeight = (value) => { strokeWeightValue = value; };// Attach functions to the window object to make them accessible by HTML event listenerswindow.clearCanvas = clearCanvas;window.updateSymmetry = updateSymmetry;window.updateStrokeWeight = updateStrokeWeight;window.saveCanvas = () => p.saveCanvas('kaleidoscope', 'png'); };// Create a new p5 instance and run the sketchnewp5(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 slidersymmetrySlider.addEventListener('input', (e) => {const val = parseInt(e.target.value, 10);symmetryValue.textContent = val;window.updateSymmetry(val); });// Update stroke weight from sliderstrokeSlider.addEventListener('input', (e) => {const val = parseInt(e.target.value, 10);strokeValue.textContent = val;window.updateStrokeWeight(val); });// Clear button functionalityclearButton.addEventListener('click', () => {window.clearCanvas(); });// Save button functionalitysaveButton.addEventListener('click', () => {window.saveCanvas(); }); }); </script></body></html>