<form class="gravity-form">
<span class="screen-reader">Set Pull</span>
<button type="button" id="decrease-pull" class="decrease-pull"><span class="screen-reader">Decrease Pull</span>-</button>
<button type="button" id="increase-pull" class="increase-pull"><span class="screen-reader">Increase Pull</span>+</button>
</form>
<div id="field">
<div id="balls"></div>
<div class="field-info-container" aria-live="polite">
<div class="field-info">
<h3>Current Pull</h3>
<p class="current-pull">
<span id="current-gravity"></span>
<span class="percentage-sign">%</span>
</p>
</div>
</div>
</div>
body {
background: #d8e8e3;
font-family: Trebuchet MS,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Tahoma, sans-serif;
padding: 200px 20px 800px;
max-width: 600px;
margin: auto;
}
.screen-reader {
clip: rect(1px, 1px, 1px, 1px);
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
&:focus {
background-color: #ddd;
border-radius: 3px;
box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6);
clip: auto !important;
color: #444;
display: block;
font-size: 1.2rem;
font-weight: bold;
height: auto;
left: 5px;
line-height: normal;
padding: 15px 23px 14px;
text-decoration: none;
top: 5px;
width: auto;
z-index: 100000; /* Above WP toolbar. */
}
}
#field,
.field-info-container {
background: #eee;
position: absolute;
width: 200px;
height: 200px;
// border: 1px solid #fff;
border-radius: 50%;
margin: auto;
top: 250px;
left: 40%;
}
.field-info-container {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
flex-direction: column;
background: #fff;
box-shadow: 0 1px 15px rgba(255,255,255,.8);
z-index: 5;
top: 0;
left: 0;
&:after,
&:before {
display: block;
content: '';
position: absolute;
width: 200%;
height: 200%;
border: 2px solid #fff;
border-radius: 50%;
}
&:before {
width: 350%;
height: 350%;
border: 2px solid rgba(255,255,255,0.5);
}
}
.ball {
position: absolute;
border-radius: 50%;
width: 10px;
height: 10px;
background: #0ebeff;
}
button {
float: left;
border: none;
background: #fff;
width: 50px;
height: 50px;
font-size: 20px;
font-weight: bold;
border-radius: 50%;
cursor: pointer;
color: #0ebeff;
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
margin-right: 10px;
transition: all .15s ease-out;
&:hover {
transform: scale(1.15);
}
}
.decrease-pull {
margin-top: 4px;
width: 42px;
height: 42px;
&:hover {
transform: scale(0.9);
}
}
button:hover,
button:focus {
outline: none;
box-shadow: 0 0 2px #0ebeff;
}
.current-pull {
font-size: 40px;
position: relative;
}
.percentage-sign {
font-size: 15px;
position: absolute;
top: 5px;
}
.gravity-form {
position: relative;
z-index: 99;
}
button:disabled {
color: #ccc;
opacity: 0.7;
}
View Compiled
// Original JavaScript code by Chirp Internet: www.chirp.com.au
// Please acknowledge use of this code by including this header.
// Modified very, very heavily by Jeremy Jones: https://jeremyjon.es
const numberOfBalls = 80;
const minSize = 4;
const maxSize = 18;
const colors = [
'#0ebeff',
'#0ebeff', // I want more of the blue ones
'#59C9A5',
'#EDCA04'
];
class Orbit {
constructor() {
this.field = document.getElementById('field')
this.ballsEl = document.getElementById('balls')
this.gravitationalPull = 50
this.gravityText = document.getElementById('current-gravity')
this.increasePullBtn = document.getElementById('increase-pull')
this.decreasePullBtn = document.getElementById('decrease-pull')
this.balls = null
this.ballSettings = {
num: numberOfBalls,
minSize: minSize,
maxSize: maxSize,
}
this.start = 0
this.init(40)
}
init(ballsNum) {
this.balls = this.createBalls(ballsNum)
window.requestAnimationFrame(this.step.bind(this))
this.setPullButtons(this.gravitationalPull)
this.increasePullBtn.addEventListener('click', this.increaseGravitationalPull.bind(this))
this.decreasePullBtn.addEventListener('click', this.decreaseGravitationalPull.bind(this))
// uncomment to have planet track cursor
// document.onmousemove = getCursorXY;
}
createBalls() {
let size;
for(let i = 0; i < this.ballSettings.num; i++) {
// get random size between setting sizes
size = Math.ceil(this.ballSettings.minSize + (Math.random() * (this.ballSettings.maxSize - this.ballSettings.minSize)))
this.createBall(size)
}
// return all the balls
return document.querySelectorAll('.ball');
}
createBall(size) {
let newBall, stretchDir
newBall = document.createElement("div")
stretchDir = (Math.round((Math.random() * 1)) ? 'x' : 'y')
newBall.classList.add('ball')
newBall.style.width = size + 'px'
newBall.style.height = size + 'px'
newBall.style.background = this.getRandomColor();
newBall.setAttribute('data-stretch-dir', stretchDir) // either x or y
newBall.setAttribute('data-stretch-val', 1 + (Math.random() * 5))
newBall.setAttribute('data-grid', field.offsetWidth + Math.round((Math.random() * 100))) // min orbit = 30px, max 130
newBall.setAttribute('data-duration', 3.5 + Math.round((Math.random() * 8))) // min duration = 3.5s, max 8s
newBall.setAttribute('data-start', 0)
this.ballsEl.appendChild(newBall)
}
callStep(timestamp) {
return this.step(timestamp)
}
step(timestamp) {
let progress, x, xPos, yPos, y, stretch, gridSize, duration, start;
for(let i = 0; i < this.balls.length; i++) {
start = this.balls[i].getAttribute('data-start')
if(start == 0) {
start = timestamp
this.balls[i].setAttribute('data-start', start)
}
gridSize = this.balls[i].getAttribute('data-grid')
duration = this.balls[i].getAttribute('data-duration')
progress = (timestamp - start) / duration / 1000 // percent
stretch = this.balls[i].getAttribute('data-stretch-val')
if(this.balls[i].getAttribute('data-stretch-dir') === 'x') {
x = ((stretch * Math.sin(progress * 2 * Math.PI)) * (1.05 - (this.gravitationalPull/100)))// x = ƒ(t)
y = Math.cos(progress * 2 * Math.PI) // y = ƒ(t)
} else {
x = Math.sin(progress * 2 * Math.PI) // x = ƒ(t)
y = ((stretch * Math.cos(progress * 2 * Math.PI)) * (1.05 - (this.gravitationalPull/100))) // y = ƒ(t)
}
xPos = field.clientWidth/2 + (gridSize * x)
yPos = field.clientHeight/2 + (gridSize * y)
this.balls[i].style.transform = 'translate3d('+xPos + 'px, '+yPos + 'px, 0)'
// if these are true, then it's behind the planet
if(((this.balls[i].getAttribute('data-stretch-dir') === 'x') && (((field.offsetWidth/2) - this.balls[i].offsetWidth) * -1) < xPos && xPos < ((field.offsetWidth/2) + this.balls[i].offsetWidth)) || ((this.balls[i].getAttribute('data-stretch-dir') === 'y') && (((field.offsetWidth/2) - this.balls[i].offsetWidth) * -1) < yPos && yPos < ((field.offsetWidth/2) + this.balls[i].offsetWidth))) {
// backside of the moon
this.balls[i].style.zIndex = '-1'
} else {
// ...front side of the moon
this.balls[i].style.zIndex = '9'
}
if(progress >= 1) {
this.balls[i].setAttribute('data-start', 0) // reset to start position
}
}
window.requestAnimationFrame(this.step.bind(this))
}
// since I don't know physics, this is an approriximation
setGravitationalPull(percent) {
let step, steps, time, direction
if(percent < 0) {
return
}
if(100 < percent) {
return
}
if(percent === this.gravitationalPull) {
return
}
steps = 20
step = Math.abs(percent - this.gravitationalPull)/steps
direction = (percent < this.gravitationalPull ? '-' : '+')
// get the current pull and step it down over 20 steps so it's smoother than jumping straight there
for(let i = 0; i < steps; i++) {
// set the time this will fire
time = i * (i/Math.PI)
// minimum time span
if(time < 4) {
time = 4
}
// set the function
setTimeout(()=>{
if(direction === '-') {
this.gravitationalPull -= step
} else {
this.gravitationalPull += step
}
}, time);
// on our last one, set the gravitationalPull to its final, nicely rounded number
if(i === steps - 1) {
setTimeout(()=>{
this.gravitationalPull = Math.round(this.gravitationalPull)
this.setPullButtons()
}, time + 20)
}
}
}
setPullButtons() {
if(this.gravitationalPull <= 0) {
this.decreasePullBtn.disabled = true
this.increasePullBtn.disabled = false
}
else if(100 <= this.gravitationalPull) {
this.decreasePullBtn.disabled = false
this.increasePullBtn.disabled = true
}
else {
this.decreasePullBtn.disabled = false
this.increasePullBtn.disabled = false
}
this.gravityText.innerHTML = this.gravitationalPull
}
increaseGravitationalPull() {
this.setGravitationalPull(this.gravitationalPull + 10)
}
decreaseGravitationalPull() {
this.setGravitationalPull(this.gravitationalPull - 10)
}
// if you want the planet to track the cursor
getCursorXY(e) {
let cursorPos = {
x: (window.Event) ? e.pageX : event.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft),
y: (window.Event) ? e.pageY : event.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop)
}
field.style.left = cursorPos.x - (field.offsetWidth/2) + "px"
field.style.top = cursorPos.y - (field.offsetHeight/2) + "px"
}
getRandomColor() {
return colors[Math.floor((Math.random() * colors.length))]
}
}
new Orbit()
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.