Last active
June 9, 2023 07:34
(moved to a repo https://github.com/JonathanDn/mediumclap ) Medium Clap Reproduction - My take on it by looking, researching and trial & error. Demo available --> https://jsfiddle.net/urft14zr/425/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="canvas"> | |
<div id="totalCounter" class="total-counter"></div> | |
<div id="clap" class="clap-container"> | |
<i class="clap-icon fa fa-hand-paper-o"></i> | |
</div> | |
<div id="clicker" class="click-counter"> | |
<span class="counter"></span> | |
</div> | |
<div id="sonar-clap" class="clap-container-sonar"></div> | |
<div id="particles" class="particles-container"> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
</div> | |
<div id="particles-2" class="particles-container"> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
</div> | |
<div id="particles-3" class="particles-container"> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
<div class="triangle"> | |
<div class="square"></div> | |
</div> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let accCounter = 0; | |
let totalCount = 127; | |
const minDeg = 1; | |
const maxDeg = 72; | |
const particlesClasses = [ | |
{ | |
class: "pop-top" | |
}, | |
{ | |
class: "pop-top-left" | |
}, | |
{ | |
class: "pop-top-right" | |
}, | |
{ | |
class: "pop-bottom-right" | |
}, | |
{ | |
class: "pop-bottom-left" | |
}, | |
]; | |
document.getElementById('totalCounter').innerText = totalCount; | |
document.getElementById('clap').onmouseover = function() { | |
let sonarClap = document.getElementById('sonar-clap'); | |
sonarClap.classList.add('hover-active'); | |
setTimeout(() => { | |
sonarClap.classList.remove('hover-active'); | |
}, 2000); | |
} | |
document.getElementById('clap').onclick = function() { | |
const clap = document.getElementById('clap'); | |
const clickCounter = document.getElementById("clicker"); | |
const particles = document.getElementById('particles'); | |
const particles2 = document.getElementById('particles-2'); | |
const particles3 = document.getElementById('particles-3'); | |
clap.classList.add('clicked'); | |
upClickCounter(); | |
runAnimationCycle(clap, 'scale'); | |
if (!particles.classList.contains('animating')) { | |
animateParticles(particles, 700); | |
} else if(!particles2.classList.contains('animating')){ | |
animateParticles(particles2, 700); | |
} else if(!particles3.classList.contains('animating')) { | |
animateParticles(particles3, 700); | |
} | |
} | |
function upClickCounter() { | |
const clickCounter = document.getElementById("clicker"); | |
const totalClickCounter = document.getElementById('totalCounter'); | |
accCounter ++; | |
clickCounter.children[0].innerText = '+' + accCounter; | |
totalClickCounter.innerText = totalCount + accCounter; | |
if (clickCounter.classList.contains('first-active')) { | |
runAnimationCycle(clickCounter, 'active'); | |
} else { | |
runAnimationCycle(clickCounter, 'first-active'); | |
} | |
runAnimationCycle(totalClickCounter, 'fader'); | |
} | |
function runAnimationCycle(el, className, duration) { | |
if (el && !el.classList.contains(className)) { | |
el.classList.add(className); | |
} else { | |
el.classList.remove(className); | |
void el.offsetWidth; // Trigger a reflow in between removing and adding the class name | |
el.classList.add(className); | |
} | |
} | |
function runParticleAnimationCycle(el, className, duration) { | |
if (el && !el.classList.contains(className)) { | |
el.classList.add(className); | |
setTimeout(() => { | |
el.classList.remove(className); | |
}, duration); | |
} | |
} | |
function animateParticles(particles, dur) { | |
addRandomParticlesRotation(particles.id, minDeg, maxDeg); | |
for(let i = 0; i < particlesClasses.length; i++) { | |
runParticleAnimationCycle(particles.children[i], particlesClasses[i].class, dur); | |
} | |
// Boolean functionality only to activate particles2, particles3 when needed | |
particles.classList.add('animating'); | |
setTimeout(() => { | |
particles.classList.remove('animating'); | |
}, dur); | |
} | |
function getRandomInt(min, max) { | |
return Math.floor(Math.random() * (max - min + 1)) + min; | |
} | |
function addRandomParticlesRotation(particlesName, minDeg, maxDeg) { | |
const particles = document.getElementById(particlesName); | |
const randomRotationAngle = getRandomInt(minDeg, maxDeg) + 'deg'; | |
particles.style.transform = `rotate(${randomRotationAngle})`; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$default-clap-color: #03a87c; | |
.canvas { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 300px; | |
height: 300px; | |
position: relative; | |
.total-counter { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 100%; | |
position: absolute; | |
margin-top: -45px; | |
color: gray; | |
font-family: sans-serif; | |
font-size: 16px; | |
} | |
.total-counter.fader { | |
animation: fade-in 1400ms forwards; | |
} | |
.clap-container { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
position: absolute; | |
width: 60px; | |
height: 60px; | |
border: 1px solid rgba(0,0,0,.15); | |
border-radius: 50%; | |
z-index: 2; | |
background: #fff; | |
cursor: pointer; | |
.clap-icon { | |
font-size: 30px; | |
color: $default-clap-color; | |
width: 30px; | |
height: 30px; | |
} | |
} | |
.clap-container:hover { | |
border: 1px solid $default-clap-color; | |
} | |
.clap-container.scale { | |
animation: scaleAndBack 700ms forwards; | |
} | |
.click-counter { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 35px; | |
height: 35px; | |
position: absolute; | |
top: 132px; | |
background-color: $default-clap-color; | |
border-radius: 50%; | |
z-index: 1; | |
.counter { | |
font-family: sans-serif; | |
font-size: 14px; | |
color: #fff; | |
} | |
} | |
.click-counter.first-active { | |
animation: first-bump-in 1s forwards; | |
} | |
.click-counter.active { | |
animation: bump-in 1s forwards; | |
} | |
.clap-container-sonar { | |
width: 60px; | |
height: 60px; | |
background: $default-clap-color; | |
border-radius: 50%; | |
position: absolute; | |
opacity: 0; | |
z-index: 0; | |
} | |
.hover-active { | |
animation: sonar-wave 2s forwards; | |
} | |
.particles-container { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 60px; | |
height: 60px; | |
position: absolute; | |
/* border: 1px solid gray; */ | |
/* z-index: 3; */ | |
.triangle { | |
border-left: 4px solid transparent; | |
border-right: 4px solid transparent; | |
border-top: 10px solid red; | |
border-bottom: 4px solid transparent; | |
position: absolute; | |
.square { | |
width: 5px; | |
height: 5px; | |
background: $default-clap-color; | |
position: absolute; | |
left: -15px; | |
top: 0; | |
} | |
} | |
.pop-top { | |
animation: pop-top 1s forwards; | |
} | |
.pop-top-left { | |
animation: pop-top-left 1s forwards; | |
} | |
.pop-top-right { | |
animation: pop-top-right 1s forwards; | |
} | |
.pop-bottom-right { | |
animation: pop-bottom-right 1s forwards; | |
} | |
.pop-bottom-left { | |
animation: pop-bottom-left 1s forwards; | |
} | |
} | |
} | |
// * * * Animations * * * // | |
@keyframes sonar-wave { | |
0% { | |
opacity: 0.7; | |
} | |
100% { | |
transform: scale(1.4); | |
opacity: 0; | |
} | |
} | |
@keyframes fade-in { | |
0% { | |
opacity: 0; | |
} | |
50% { | |
opacity: 0; | |
} | |
100% { | |
opacity: 1; | |
} | |
} | |
// * * * Pop Animations * * * // | |
@keyframes pop-top { | |
0% { | |
transform: translate(0, 0) rotate(0); | |
opacity: 0.4; | |
} | |
100% { | |
transform: translate(0, -100px) rotate(0); | |
opacity: 0; | |
} | |
} | |
@keyframes pop-top-left { | |
0% { | |
transform: translate(0, 0) rotate(-55deg); | |
opacity: 0.4; | |
} | |
100% { | |
transform: translate(-100px, -50px) rotate(-55deg); | |
opacity: 0; | |
} | |
} | |
@keyframes pop-top-right { | |
0% { | |
transform: translate(0, 0) rotate(55deg); | |
opacity: 0.4; | |
} | |
100% { | |
transform: translate(100px, -50px) rotate(55deg); | |
opacity: 0; | |
} | |
} | |
@keyframes pop-bottom-right { | |
0% { | |
transform: translate(0, 0) rotate(135deg); | |
opacity: 0.4; | |
} | |
100% { | |
transform: translate(70px, 80px) rotate(135deg); | |
opacity: 0; | |
} | |
} | |
@keyframes pop-bottom-left { | |
0% { | |
transform: translate(0, 0) rotate(-135deg); | |
opacity: 0.4; | |
} | |
100% { | |
transform: translate(-70px, 80px) rotate(-135deg); | |
opacity: 0; | |
} | |
} | |
@keyframes first-bump-in { | |
0% { | |
transform: translateY(-65px); | |
opacity: 1; | |
} | |
50% { | |
transform: translateY(-80px); | |
opacity: 1; | |
} | |
100% { | |
transform: translateY(-100px); | |
opacity: 0; | |
} | |
} | |
@keyframes bump-in { | |
0% { | |
transform: translateY(-80px) scale(0.9); | |
opacity: 1; | |
} | |
50% { | |
transform: translateY(-80px) scale(1); | |
opacity: 1; | |
} | |
100% { | |
transform: translateY(-100px) scale(1); | |
opacity: 0; | |
} | |
} | |
@keyframes scaleAndBack { | |
0% { | |
transform: scale(1); | |
} | |
50% { | |
transform: scale(1.15); | |
} | |
100% { | |
transform: scale(1); | |
} | |
} |
@topshef By the way I would love to see what you are doing with it if you would mind sharing with me :) You can also DM me that to my twitter profile: https://twitter.com/jodoron
@JonathanDn thanks a lot, yes I'll share it back on github and DM you :-)
@JonathanDn thanks a lot, yes I'll share it back on github and DM you :-)
Awesome!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome, I actually didn't know that ! I thought of uploading it all to codesandbox.io which shows the
index.html
boilerplate with all the script and style tags. But I guess that helps resolved your issue as well?