Yin Yang Shenanigans






How much fun can you have with four circles and about twenty lines of pure JavaScript? Let's find out!

The animation above has three phases:

1. spinning yin yang

2. spinning yin yang with color cycling

3. something weird-but-cool-looking

Click on the image to restart the animation.

We'll start with an HTML5 Canvas, two blue circles [lines 19-20] and two red ones [18,21]. The coordinates of the center of the big (radius=80) blue and small (radius=10) red are (x1,y1), and the other two (x2,y2):



In each animation frame we'll rotate them around the center of the canvas without erasing it (so that the new image is drawn over the previous one - the large circles leave a trace, which creates the Yin Yang symbol). If you're not a trigonometry enthusiast, you don't even have to worry about the sin/cos functions - all you need to know is that if the coordinates of an object are (sin(counter),cos(counter)), it will spin in a circle. I divided the counter by an arbitrary number (PI/4) to slow down the animation. x1 and y1 are 180 degrees 'behind' x2 and y2 so that the two sets of circles are always on the opposite sides of the center of the canvas.

Here's the code for phase 1:

<html>
<body style='background-color:black'>
<canvas id="myCanvaswidth="400height="400"></canvas>
<script>
function circle(x,y,radius,color) {
        context.fillStyle = color;
        context.beginPath();
        context.arc(xyradius02 * Math.PI);
        context.fill();        
}
 
function animate() {
        let x1=canvas.width/2+80*Math.sin(counter/Math.PI/4);
        let y1=canvas.height/2+80*Math.cos(counter/Math.PI/4);
        let x2=canvas.width/2+80*Math.sin(counter/Math.PI/4+Math.PI);
        let y2=canvas.height/2+80*Math.cos(counter/Math.PI/4+Math.PI);
 
        circle(x1,y1,80,'rgb(0,0,255)');
        circle(x2,y2,80, 'rgb(255,0,0)');
        circle(x1,y1,10,'rgb(255,0,0)');
        circle(x2,y2,10,'rgb(0,0,255)');
        counter++;
        window.requestAnimationFrame(animate);
}
 
let canvas = document.getElementById('myCanvas');
let context = canvas.getContext('2d');
let counter=0;
window.requestAnimationFrame(animate);
</script>
</body>
</html>


And the result:

Now let's add color cycling. To do this, we introduce a slider [17] that will be added to blue component and subtracted from the red component for two circles and the other way round for the other two.

Here's the breakdown of the formula: first, we take the remainder of the division of the counter by 255 (%255):



Subtract the result from 127 to bring half of it to the negative territory:



Finally, take the absolute value and multiply by two:



Now it oscillates nicely and smoothly between 0 and 255, which are the min/max values for the RGB color components. When two circles are red, the other two are blue and they periodically exchange colors. My uncurable obsession with minimizing the number of lines of code (I want to be on Dwitter when I grow up) forced me to cram these values into lines [18-20] which makes them very cumbersome... A sane person would declare a new variable for 255-slider.

<html>
<body bgcolor="black">
<canvas id="myCanvaswidth="400height="400"></canvas>
<script>
function circle(x,y,radius,color) {
        context.fillStyle = color;
        context.beginPath();
        context.arc(xyradius02 * Math.PI);
        context.fill();        
}
 
function animate() {
        let x1=canvas.width/2+80*Math.sin(counter/Math.PI/4);
        let y1=canvas.height/2+80*Math.cos(counter/Math.PI/4);
        let x2=canvas.width/2+80*Math.sin(counter/Math.PI/4+Math.PI);
        let y2=canvas.height/2+80*Math.cos(counter/Math.PI/4+Math.PI);
        let slider=2*Math.abs(128-counter%255);
        circle(x1,y1,80,'rgb('+(255-slider)+',0,'+slider+')');
        circle(x2,y2,80, 'rgb('+slider+',0,'+(255-slider)+')');
        circle(x1,y1,10,'rgb('+slider+',0,'+(255-slider)+')');
        circle(x2,y2,10,'rgb('+(255-slider)+',0,'+slider+')');
        counter++;
        window.requestAnimationFrame(animate);
}
 
let canvas = document.getElementById('myCanvas');
let context = canvas.getContext('2d');
let counter=0;
window.requestAnimationFrame(animate);
</script>
</body>
</html>




What if the radius of the large circles was the sine of time (represented by the counter)?

By the way, let's get rid of the two small circles, those stinkers weren't doing anything interesting anyway.

Let's make small changes in lines [18-19] and get rid of [20-21]

<html>
<body bgcolor="black">
<canvas id="myCanvaswidth="400height="400"></canvas>
<script>
function circle(x,y,radius,color) {
        context.fillStyle = color;
        context.beginPath();
        context.arc(xyradius02 * Math.PI);
        context.fill();        
}
 
function animate() {
        let x1=canvas.width/2+80*Math.sin(counter/Math.PI/4);
        let y1=canvas.height/2+80*Math.cos(counter/Math.PI/4);
        let x2=canvas.width/2+80*Math.sin(counter/Math.PI/4+Math.PI);
        let y2=canvas.height/2+80*Math.cos(counter/Math.PI/4+Math.PI);
        let slider=2*Math.abs(128-counter%255);
        circle(x1,y1,80-30*Math.sin(counter/8),'rgb('+(255-slider)+',0,'+slider+')');
        circle(x2,y2,80-30*Math.sin(counter/8), 'rgb('+slider+',0,'+(255-slider)+')');
 
        
        counter++;
        window.requestAnimationFrame(animate);
}
 
let canvas = document.getElementById('myCanvas');
let context = canvas.getContext('2d');
let counter=0;
window.requestAnimationFrame(animate);
</script>
</body>
</html>


Which leads to this cool effect. Enjoy experimenting with different parameters!





Check out these programming tutorials:

Python in Blender 3d:

Domino effect (10 lines)


Wrecking ball effect (14 lines)

3d fractal in Blender Python


JavaScript:

Oldschool fire effect in (20 lines)

Animated fractal (32 lines)

Starfield (21 lines)

Tutorial - interactive, animated sprites

Your first program in JavaScript: you need 5 minutes and a notepad


Fractals in Excel