Four rotating circles with play and pause button
Introduction
I wanted to create four rotating elements around a tight circumference where the text contained within remained static i.e. not rotating like its parent. Seemed easy enough, took a bit of CSS
wrangling, then I wanted to add a Play/Pause button. The example below is what I came up with.
Accessibility, prefers Reduced Motion
What if the user has set a preference for Reduced Motion? In this case the animation and play button are removed. There is also some warning text indicating that the animation will not work. Give it a try, set prefers-reduced-motion
in your System Settings (Accessibility
> Display
> Reduce Motion
) and see what happens here…
Read through code or download
You can read through the various code sections inline here or skip to Download Link to play with the code locally.
Code: HTML
<div class="circles-container">
<div class="outer-circle">
<div class="animation1 rotate" data-animation>
<div class="counter-rotate1" data-animation>
<div class="inner">Circle 1</div>
</div>
</div>
<div class="animation2 rotate" data-animation>
<div class="counter-rotate2" data-animation>
<div class="inner">Circle 2</div>
</div>
</div>
<div class="animation3 rotate" data-animation>
<div class="counter-rotate3" data-animation>
<div class="inner">Circle 3</div>
</div>
</div>
<div class="animation4 rotate" data-animation>
<div class="counter-rotate4" data-animation>
<div class="inner">Circle 4</div>
</div>
</div>
</div>
<div id="buttons">
<button id="pause-toggle" class="">Pause</button>
</div>
<div id="prefers-reduced-motion">
You have set a preference for reduced motion in your System Settings so no animation will be shown.
</div>
</div>
Code: Cascading Style Sheets (CSS
)
body {
background-color: #eaeaea;
}
#buttons {
position: absolute;
top: 240px;
left: 50%;
transform: translate(-50%, 0);
}
#pause-toggle:hover {
cursor: pointer;
}
#pause-toggle {
background-color: #333;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 1.25rem;
width: 150px;
padding: 10px 20px;
border: 0;
border-radius: 6px;
}
#prefers-reduced-motion {
display: none;
}
.circles-container {
position: relative;
top: 40px;
margin: 0 auto;
}
.outer-circle {
background-color: #add8e6;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
width: 200px;
height: 200px;
border-radius: 50%;
}
.rotate {
width: 100%;
height: 100%;
position: absolute;
}
.counter-rotate1,
.counter-rotate2,
.counter-rotate3,
.counter-rotate4 {
width: 100px;
height: 100px;
}
.inner {
background-color: #ed7883;
display: flex;
align-items: center;
justify-content: center;
color: #000;
width: 100px;
height: 100px;
border-radius: 50%;
}
.animation1 {
animation-name: circle1;
}
.animation1 .counter-rotate1 {
animation-name: counter-rotate-circle1;
}
.animation2 {
animation-name: circle2;
}
.animation2 .counter-rotate2 {
animation-name: counter-rotate-circle2;
}
.animation3 {
animation-name: circle3;
}
.animation3 .counter-rotate3 {
animation-name: counter-rotate-circle3;
}
.animation4 {
animation-name: circle4;
}
.animation4 .counter-rotate4 {
animation-name: counter-rotate-circle4;
}
.animation1,
.animation1 .counter-rotate1,
.animation2,
.animation2 .counter-rotate2,
.animation3,
.animation3 .counter-rotate3,
.animation4,
.animation4 .counter-rotate4 {
animation-duration: 45s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes circle1 {
from {
transform: rotateZ(0deg);
}
to {
transform: rotateZ(360deg);
}
}
@keyframes counter-rotate-circle1 {
from {
transform: rotateZ(0deg);
}
to {
transform: rotateZ(-360deg);
}
}
@keyframes circle2 {
from {
transform: rotateZ(90deg);
}
to {
transform: rotateZ(450deg);
}
}
@keyframes counter-rotate-circle2 {
from {
transform: rotateZ(-90deg);
}
to {
transform: rotateZ(-450deg);
}
}
@keyframes circle3 {
from {
transform: rotateZ(180deg);
}
to {
transform: rotateZ(540deg);
}
}
@keyframes counter-rotate-circle3 {
from {
transform: rotateZ(-180deg);
}
to {
transform: rotateZ(-540deg);
}
}
@keyframes circle4 {
from {
transform: rotateZ(270deg);
}
to {
transform: rotateZ(630deg);
}
}
@keyframes counter-rotate-circle4 {
from {
transform: rotateZ(-270deg);
}
to {
transform: rotateZ(-630deg);
}
}
@media (prefers-reduced-motion: reduce) {
#buttons {
display: none;
}
#prefers-reduced-motion {
display: block;
position: absolute;
top: 240px;
left: 50%;
text-align: center;
max-width: 280px;
transform: translate(-50%, 0);
}
.animation1,
.animation1 .counter-rotate1,
.animation2,
.animation2 .counter-rotate2,
.animation3,
.animation3 .counter-rotate3,
.animation4,
.animation4 .counter-rotate4 {
position: relative;
animation: none;
}
.animation1 {
left: 50px;
top: -20px;
}
.animation2 {
left: 120px;
top: -150px;
}
.animation3 {
top: -280px;
left: 50px;
}
.animation4 {
top: -550px;
left: -20px;
}
}
Code: JavaScript
const pauseToggle = document.getElementById('pause-toggle');
pauseToggle.addEventListener('click', () => {
const animations = document.querySelectorAll('[data-animation]');
animations.forEach(animation => {
const running = animation.style.animationPlayState || 'running';
animation.style.animationPlayState = running === 'running' ? 'paused' : 'running';
pauseToggle.innerHTML = running === 'running' ? 'Play' : 'Pause';
});
});
Download sample code
I’ve assembled all the parts so that you can play with them. The standard – software is provided “as is”, without warranty of any kind – applies here.
That’s all folks
Have fun playing with the code, if you improve upon it or have any other ideas please get in touch.
// End of Project