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>