Skip to content

Instantly share code, notes, and snippets.

@JonathanDn
Last active June 9, 2023 07:34
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save JonathanDn/09c3e0f037c4c52efcf56ee0878d5e4c to your computer and use it in GitHub Desktop.
Save JonathanDn/09c3e0f037c4c52efcf56ee0878d5e4c to your computer and use it in GitHub Desktop.
(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/
<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>
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})`;
}
$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);
}
}
@sastava007
Copy link

How to store the count of claps...I mean how to setup backend database for this?

@JonathanDn
Copy link
Author

Hey @sastava007, This implementation is purely client side.

You could expose the totalClickCounter in row 55 as a global in the file and then whenever you want to send it to an API in your backend to update the total claps.

@topshef
Copy link

topshef commented Sep 28, 2020

This looks great. How can I demo this standalone? This is probably a noddy question. i.e. how to get working code from js fiddle (which presumably links the files somehow but does not seem to show this) Thanks a million

@JonathanDn
Copy link
Author

JonathanDn commented Sep 28, 2020

@topshef What do you mean by demo standalone ?

You got the jsfiddle link which serves as a demo/playground to mess around with the code and microinteraction written in HTML, SCSS JS
https://jsfiddle.net/urft14zr/425/

@topshef
Copy link

topshef commented Sep 28, 2020

@JonathanDn thanks for your quick reply :-) I mean to create the files needed to run the demo on its own, without js fiddle. i.e update html with links to the js and css, and header etc. Sorry if it's a noddy question!

@JonathanDn
Copy link
Author

JonathanDn commented Sep 28, 2020

@topshef Define on its own.

  1. On your local environment? (i.e your computer only)
  2. Do you mean to have it available online in a website other than jsfiddle?
  3. Do you want to add it to an existing app you have?

@JonathanDn
Copy link
Author

@topshef are you working with React/Vue/Vanilla JS?

@topshef
Copy link

topshef commented Sep 28, 2020

@JonathanDn
Online, vanilla JS

  1. local + online (sync is via Webdrive)
  2. yes, demo it, not on js fiddle
  3. later, but now just a demo to understand the code and test some changes
    Thanks

@topshef
Copy link

topshef commented Sep 28, 2020

I'm checking this out https://stackoverflow.com/questions/9851878/is-there-a-download-function-in-jsfiddle

..Ok that was it.. just add /show to the js fiddle URL and then just save the page :-)
(not that clean though but it works)

@JonathanDn
Copy link
Author

I'm checking this out https://stackoverflow.com/questions/9851878/is-there-a-download-function-in-jsfiddle

..Ok that was it.. just add /show to the js fiddle URL and then just save the page :-)
(not that clean though but it works)

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?

  • Would you suggest adding a codesandbox example would be more inviting for new devs?

@JonathanDn
Copy link
Author

@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

@topshef
Copy link

topshef commented Sep 28, 2020

@JonathanDn thanks a lot, yes I'll share it back on github and DM you :-)

@JonathanDn
Copy link
Author

@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