Clock hand rotation reverting back to original position at 360deg - javascript

I was following this challenge which ended with fixing this clocks hands.
Whenever the second's hand reaches the 12 o'clock position, the entire animation restarts but pulls the hand backwards so the next tick at 1-second isnt seamless, and it looks ugly.
how can I achieve this?
const secondHand = document.querySelector('.second-hand');
function setDate(){
const now = new Date();
const seconds = now.getSeconds();
const secondsDegrees = ((seconds / 60) * 360) + 90;
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
}
setInterval(setDate, 1000);
setDate();
.clock {
width: 30rem;
height: 30rem;
border: 20px solid white;
border-radius: 50%;
margin: 50px auto;
position: relative;
padding: 2rem;
box-shadow:
0 0 0 4px rgba(0,0,0,0.1),
inset 0 0 0 3px #EFEFEF,
inset 0 0 10px black,
0 0 10px rgba(0,0,0,0.2);
}
.clock-face {
position: relative;
width: 100%;
height: 100%;
transform: translateY(-3px); /* account for the height of the clock hands */
}
.hand {
width: 50%;
height: 6px;
background: black;
position: absolute;
top: 50%;
transform-origin: 100%;
transform: rotate(90deg);
transition: all 0.05s;
transition-timing-function: cubic-bezier(0.38, 2.9, 0.58, 1);
}
<div class="clock">
<div class="clock-face">
<div class="hand hour-hand"></div>
<div class="hand min-hand"></div>
<div class="hand second-hand"></div>
</div>
</div>
<br>

You need to set your seconds hand to 360 degrees, disable its transition, set it to 0 degrees, then enable its transition. Note that you should only disable the transition after the previous transition is over:
function setDate() {
const now = new Date();
const seconds = now.getSeconds();
const secondsDegrees = ((seconds / 60) * 360) + 90;
if(secondsDegrees == 0) {
secondHand.style.transform = "rotate(360deg)";
setTimeout(function() {
secondHand.style.transition = "0s";
secondHand.style.transform = "rotate(0deg)";
secondHand.style.transition = "";
},50);
} else {
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
}
}
You will probably need to do this for the minutes and hours hands too.

Related

re-setting Interval after clearInterval

So I have an Interval that activates a function function Seifenblasen_blasen() every 100ms. I stop this Interval or function with clear Interval by running function function Bouny_Ball() when clicking the Button Bounce.
Now when I click the Button Bubble again it should start generating Bubbles every 100ms. But it doesn't, as it was cleared AND is not in the function Seifenblasen_blasen() therefore it still is cleared. But neither putting it in the function Seifenblasen_blasen() or making a seperate function and calling it with the same button press makes a difference. This is where I need your Help.
Thanks in advance.
This is not the only problem tho. I will make separate questions for these but if you have time feel free to ask and i'll provide them in a comment. I need to wait 90 minutes after all.
whole code
Function Seifenblasen_blasen() with Interval:
function Seifenblasen_blasen(){
document.getElementById("screen2").style.display = "none";
document.getElementById("screen").style.display = "block";
const section = document.querySelector('#screen')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
},8000)
}
const Blaseninterval = setInterval(Seifenblasen_blasen, 100)
Resulting CSS of function Seifenblasen_blasen()
#screen {
width: 100%;
height: 90vh;
overflow: hidden;
background-image: linear-gradient(#008CBA, #030a19);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#screen spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 4s linear infinite;
}
#screen spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
}
#screen span {
margin-top: 700px;
font-size: 1em;
color: #333;
margin: 0 auto;
font-family: consolas;
background-color: #1F69FA;
border: none;
position: absolute;
}
clearence of Interval:
function Bouncy_Ball() {
clearInterval(Blaseninterval);
document.getElementById("screen").style.display = "none";
document.getElementById("screen2").style.display = "block";
}

Restrict creation of new divs to a specific area

I have a small page. Divas in the form of circles are created here every certain time.
They spawn in random places.
As can be seen even on the buttons and slightly outside the page.
The question is. Is it possible to make a box that does not touch the buttons, and that the circles are created within this box?
This should be done as a border with a certain extension, but specifying everything in pixels is not an option, it will be bad for different screens.
I created such a frame, replaced document.body.appendChild(div);
on the document.getElementById("spawnRadius").appendChild(div);
It seems that they should appear within this frame, but no, all the same throughout the page.
I also tried instead of whole page height and width document.documentElement.clientWidth use the width and height of the desired border spawnRadius.width
But now all my circles do not appear randomly, but at the beginning of this block in one place.
I tried to see these values ​​through console.log
console.log(documentHeight);
console.log(documentWidth);
But getting there undefined
PS. Demo watch in full page
//timer
var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var totalSeconds = 0;
setInterval(setTime, 1000);
function setTime() {
++totalSeconds;
secondsLabel.innerHTML = pad(totalSeconds % 60);
minutesLabel.innerHTML = pad(parseInt(totalSeconds / 60));
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
//create circle
var widthHeight = 65;
var margin = 25;
var delta = widthHeight + margin;
var spawnRadius = document.getElementById("spawnRadius");
let clicks = 0;
function createDiv(id, color) {
let div = document.createElement('div');
var currentTop = 0;
var documentHeight = spawnRadius.height;
var documentWidth = spawnRadius.width;
div.setAttribute('class', id);
if (color === undefined) {
let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
}
else {
div.style.borderColor = color;
}
div.classList.add("circle");
div.classList.add("animation");
currentTop = Math.floor(Math.random() * documentHeight) - delta;
currentLeft = Math.floor(Math.random() * documentWidth) - delta;
var limitedTop = Math.max(margin * -1, currentTop);
var limitedLeft = Math.max(margin * -1, currentLeft);
div.style.top = limitedTop + "px";
div.style.left = limitedLeft + "px";
const nodes = document.querySelectorAll('.animation');
for(let i = 0; i < nodes.length; i++) {
nodes[i].addEventListener('click', (event) => {
event.target.style.animation = 'Animation 200ms linear';
setTimeout(() => {
event.target.style.animation = '';
}, 220); });
}
$(div).click(function() {
$('#clicks').text(++clicks);
$(this).fadeOut();
});
document.getElementById("spawnRadius").appendChild(div);
}
let i = 0;
const oneSecond = 600;
setInterval(() => {
i += 1;
createDiv(`circle${i}`);
}, oneSecond);
html, body {
width: 100%;
height: 100%;
margin: 0;
background: #0f0f0f;
}
.back {
font-family: "Comic Sans MS", cursive, sans-serif;
font-size: 25px;
letter-spacing: 2px;
word-spacing: 2px;
color: #ffffff;
text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
font-weight: 700;
text-decoration: none;
font-style: italic;
font-variant: normal;
text-transform: lowercase;
position: absolute;
top: 25%;
left: 2%;
user-select: none;
z-index: 999;
}
.panel {
color: #0f0f0f;
font-size: 40px;
z-index: 999;
position: absolute;
cursor: default;
user-select: none;
color: #0f0f0f;
}
.score {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.time {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.circle {
width: 60px;
height: 60px;
border-radius: 60px;
background-color: #0f0f0f;
border: 3px solid #000;
margin: 20px;
position: absolute;
}
#keyframes Animation {
0% {
transform: scale(1);
}
50% {
transform: scale(.8);
}
100% {
transform: scale(1);
}
}
#spawnRadius {
top: 55%;
height: 650px;
width: 1000px;
left: 50%;
white-space: nowrap;
position: absolute;
transform: translate(-50%, -50%);
background: #0f0f0f;
border: 2px solid #ebc6df;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="panel">
<span class="score">Score: <a id="clicks">0</a></span>
<span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
back
<div id="spawnRadius"></div>
To answer your main question, the getBoundingClientRect method can be used to retrieve the current bounding rectangle of an element, using which you can determine where the valid spawn areas are.
When choosing a valid placement, only consider the width and height of the container element, since the coordinates of child elements are relative to its parent. You also need to take into account the size of the elements being spawned, so the valid range of the x position for example is 0 to containerWidth - circleWidth.
The circles also had a CSS margin associated with them, which would offset them past their absolute coordinates.
There are a few other issues with the code though which you may run into later on:
There was an odd mix of jQuery and standard JavaScript calls, so if you're familiar with native JavaScript methods then it's likely simpler to stick with those and remove the dependency on jQuery.
For example, there were two click event handlers on each circle, one to add the CSS animation and another to increment the score. These can be combined into a single function.
The bounce out animation and the jQuery fade out can also be combined by adding opacity values into the animation start and end keyframes.
There was a loop in the createDiv function which added another click event handler to every circle element rather than just to the newly created element. This may have originally necessitated the jQuery click handler outside of that loop, since otherwise the score counter would have been incremented multiple times.
It was also possible to click the circles multiple times before the animation was complete (hence adding multiple points), which was likely not intended. Adding a simple Boolean clicked flag can avoid this.
Once the fade animation completed, the circle element itself was still on the page, it just had a display of none so wouldn't be visible. Over time, this would cause slowdowns on lower end hardware since there would be many DOM elements still sitting in memory that were no longer required. As such, it's best to remove elements from the DOM once they're no longer needed using removeChild. You had the right idea by removing the animation after the animation completed.
Here's the amended code:
var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var clickEl = document.getElementById("clicks");
var totalSeconds = 0;
let clicks = 0;
setInterval(setTime, 1000);
function setTime() {
++totalSeconds;
secondsLabel.innerText = pad(totalSeconds % 60);
minutesLabel.innerText = pad(parseInt(totalSeconds / 60));
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
var spawnRadius = document.getElementById("spawnRadius");
var spawnArea = spawnRadius.getBoundingClientRect();
const circleSize = 66; // Including borders
//create circle
function createDiv(id, color) {
let div = document.createElement('div');
div.setAttribute('class', id);
if (color === undefined) {
let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
}
else {
div.style.borderColor = color;
}
// Randomly position circle within spawn area
div.style.top = `${Math.floor(Math.random() * (spawnArea.height - circleSize))}px`;
div.style.left = `${Math.floor(Math.random() * (spawnArea.width - circleSize))}px`;
div.classList.add("circle", "animation");
// Add click handler
let clicked = false;
div.addEventListener('click', (event) => {
if (clicked) { return; } // Only allow one click per circle
clicked = true;
div.style.animation = 'Animation 200ms linear forwards';
setTimeout(() => { spawnRadius.removeChild(div); }, 220);
clickEl.innerText = ++clicks
});
spawnRadius.appendChild(div);
}
let i = 0;
const oneSecond = 1000;
setInterval(() => {
i += 1;
createDiv(`circle${i}`);
}, oneSecond);
html, body {
width: 100%;
height: 100%;
margin: 0;
background: #0f0f0f;
}
.back {
font-family: "Comic Sans MS", cursive, sans-serif;
font-size: 25px;
letter-spacing: 2px;
word-spacing: 2px;
color: #ffffff;
text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
font-weight: 700;
text-decoration: none;
font-style: italic;
font-variant: normal;
text-transform: lowercase;
position: absolute;
top: 25%;
left: 2%;
user-select: none;
z-index: 999;
}
.panel {
color: #0f0f0f;
font-size: 40px;
z-index: 999;
position: absolute;
cursor: default;
user-select: none;
color: #0f0f0f;
}
.score {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.time {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.circle {
width: 60px;
height: 60px;
border-radius: 60px;
background-color: #0f0f0f;
border: 3px solid #000;
position: absolute;
}
#keyframes Animation {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(.8);
}
100% {
transform: scale(1);
opacity: 0;
}
}
#spawnRadius {
top: 55%;
height: 650px;
width: 1000px;
left: 50%;
white-space: nowrap;
position: absolute;
transform: translate(-50%, -50%);
background: #0f0f0f;
border: 2px solid #ebc6df;
}
<span class="panel">
<span class="score">Score: <a id="clicks">0</a></span>
<span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
back
<div id="spawnRadius"></div>

How to conform top glare to all child elements

Goal
Make the glare layer on all visible children like a clip-path
––––––––––––
Heads Up
Child elements can be any shape with any animation
Child elements can be any svg shape with any kind of animation attached to it
So glare must be automatically dynamic and conforming
––––––––––––
What I've Done
I create an apple TV effect…
But the glare only works as a box on top of other boxes.
The glare does not conform to other shapes
Example Below
––––––––––––
What I Can't Use
Canvas - No Canvas Please - I'm not familiar with it
Clip-Path - Because child elements can be anything overflowing outside of the glare
––––––––––––
What I'm looking for
Some kind of magical CSS line of code that makes the glare layer conform to all elements under it… like a normal glare would work.
Is this possible?
Is there some way Javascript can glare it automatically?
Is there some kind of mix-blend-mode I can use to make the glare just work?
Or is this something that is just impossible?
Glare should not look like a box
––––––––––––
What I tried
I tried to scale the glare layer to scale(1.1) and use some mix-blend-mode
But I couldn't figure out how to make it work.
appleTV();
function appleTV(){
appleTVComponents = 0;
function rotateX(n) {return ' rotateX('+n+'deg)'}
function rotateY(n) {return ' rotateY('+n+'deg)'}
function translateX(n) {return ' translateX('+n+'px)'}
function translateY(n) {return ' translateY('+n+'px)'}
function perspective(n) {return 'perspective('+n+'px)'}
function scale(n) {return ' scale3d('+n+','+n+','+n+')'}
function section(s='',e) {e=document.createElement('section');e.className='appletv_'+s;return e;}
function getWidth(e) {return e.clientWidth || e.offsetWidth || e.scrollWidth}
function setPerspective(e) {e.style.transform = perspective(getWidth(e)*3);}
function preventScroll(state) {if(supportsTouch){win.preventScroll=state||false;}}
function preventDefault(e) {if (supportsTouch&&win.preventScroll){e.preventDefault();}}
function isTouchScreen() {return 'ontouchstart' in window || navigator.msMaxTouchPoints}
function child(e) {return e.firstChild;}
function children(e) {return [...e.children]}
let body = document.body,
win = window,
imgs = document.querySelectorAll('.appletv'),
totalImgs = imgs.length,
supportsTouch = isTouchScreen(),
move = 'mousemove',
start = 'mouseenter',
end = 'mouseleave';
if(supportsTouch){move='touchmove'; start='touchstart'; end='touchend';}
if(totalImgs <= 0){return;}
for(var l=0;l<totalImgs;l++){
var thisImg = imgs[l],
layerElems = [...thisImg.querySelectorAll('.appletv_layer')];
if(!layerElems.length){continue;}
while(thisImg.firstChild) {thisImg.removeChild(thisImg.firstChild);}
var containerHTML = section(''),
shineHTML = section('gloss'),
shadowHTML = section('shadow'),
layersHTML = section('layer'),
layers = [];
thisImg.id = 'appletv_'+(++appleTVComponents);
layerElems.forEach((e,i)=>{
let layer_ = section('rendered_layer')
layer = section(''),
img = e.getAttribute('data-img');
layer_.setAttribute('data-layer',i);
[...e.children].forEach(c=>{layer.appendChild(c)})
if (img) {layer.style.backgroundImage = 'url('+img+')';}
layer_.appendChild(layer);
layersHTML.appendChild(layer_);
layers.push(layer);
});
[shadowHTML,layersHTML,shineHTML].forEach(e=>{containerHTML.appendChild(e)});
thisImg.appendChild(containerHTML);
var w = getWidth(thisImg);
setPerspective(thisImg)
preventScroll();
(function enableMovements(_thisImg,_layers,_totalLayers,_shine) {
thisImg.addEventListener(move, e=>{processMovement(e,supportsTouch,_thisImg,_layers,_totalLayers,_shine);});
thisImg.addEventListener(start, e=>{processEnter(_thisImg);});
thisImg.addEventListener(end, e=>{processExit(_thisImg,_layers,_totalLayers,_shine);});
})(thisImg,layers,layerElems.length,shineHTML);
};
function processMovement(e, touchEnabled, elem, layers, totalLayers, shine){
preventDefault(e)
let bdst = body.scrollTop,
bdsl = body.scrollLeft,
pageX = (touchEnabled)? e.touches[0].pageX : e.pageX,
pageY = (touchEnabled)? e.touches[0].pageY : e.pageY,
offsets = elem.getBoundingClientRect(),
w = elem.clientWidth || elem.offsetWidth || elem.scrollWidth, // width
h = elem.clientHeight || elem.offsetHeight || elem.scrollHeight, // height
wMultiple = 320/w,
offsetX = 0.52 - (pageX - offsets.left - bdsl)/w, //cursor position X
offsetY = 0.52 - (pageY - offsets.top - bdst)/h, //cursor position Y
dy = (pageY - offsets.top - bdst) - h / 2, //#h/2 = center of container
dx = (pageX - offsets.left - bdsl) - w / 2, //#w/2 = center of container
yRotate = (offsetX - dx)*(0.07 * wMultiple), //rotation for container Y
xRotate = (dy - offsetY)*(0.1 * wMultiple), //rotation for container X
imgCSS = rotateX(xRotate)+rotateY(yRotate), //img transform
arad = Math.atan2(dy, dx), //angle between cursor and center of container in RAD
angle = arad * 180 / Math.PI - 90; //convert rad in degrees
if (angle < 0) {angle = angle + 360;}
if(elem.firstChild.className.indexOf(' over') != -1){imgCSS += scale(1.07);}
elem.firstChild.style.transform = imgCSS;
shine.style.background = 'linear-gradient(' + angle + 'deg, rgba(255,255,255,' + (pageY - offsets.top - bdst)/h * 0.4 + ') 0%,rgba(255,255,255,0) 80%)';
shine.style.transform = translateX((offsetX * totalLayers) - 0.1)+translateY((offsetY * totalLayers) - 0.1);
var revNum = totalLayers;
for(var ly=0;ly<totalLayers;ly++){
layers[ly].style.transform = translateX((offsetX * revNum) * ((ly * 2.5) / wMultiple))+translateX((offsetY * totalLayers) * ((ly * 2.5) / wMultiple));
revNum--;
}
}
function processEnter(e){preventScroll(true);setPerspective(e);child(e)&&child(e).classList.add('over');}
function processExit(elem, layers, totalLayers, shine){preventScroll();
child(elem).classList.remove('over')
child(elem).style.transform = '';
shine.style = '';
layers.forEach(e=>{e.style.transform = ''})
}
}
body,
html {
height: 100%;
min-height: 100%;
}
body {background: linear-gradient(to bottom, #f6f7fc 0%, #d5e1e8 40%);}
.center{
position: absolute;
left: 50%;
margin: 10px auto;
transform: translateX(-50%);
}
.appletv {
position: relative !important;
margin: 0 auto !important;
display: inline-block;
width: 300px;
height: 150px;
border-radius: 5px;
transform-style: preserve-3d;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
cursor: pointer;
backface-visibility: hidden;
}
.appletv.depressed {
margin-top: 25px;
box-shadow: 0 5px 30px rgba(0, 0, 0, 0.4);
}
.appletv_ {
position: relative;
width: 100%;
height: 100%;
border-radius: 5px;
transition: all 0.2s ease-out;
background: teal;
}
.appletv_container.over {z-index: 1;}
.appletv_container.over .appletv_shadow {box-shadow: 0 45px 100px rgba(14, 21, 47, 0.4), 0 16px 40px rgba(14, 21, 47, 0.4);}
.appletv_layer {
position: relative;
width: 100%;
height: 100%;
border-radius: 5px;
/*overflow: hidden;*/
transform-style: preserve-3d;
}
.appletv_rendered_layer {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
overflow: hidden;
border-radius: 5px;
transition: all 0.1s ease-out;
transform-style: preserve-3d;
}
.appletv_rendered_layer > :first-child {
position: absolute;
width: 104%;
height: 104%;
top: -2%;
left: -2%;
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
background-size: cover;
transition: all 0.1s ease-out;
}
.appletv_shadow {
position: absolute;
top: 5%;
left: 5%;
width: 90%;
height: 90%;
transition: all 0.2s ease-out;
box-shadow: 0 8px 30px rgba(14, 21, 47, 0.6);
}
.appletv_gloss {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 5px;
/*display: none !important;*/
background: linear-gradient(135deg, rgba(255, 255, 255, 0.25) 0%, rgba(255, 255, 255, 0) 40%);
}
[data-layer="1"] {overflow: visible !important;}
[data-layer="1"] > section > section {
position: absolute;
background: rgb(50, 141, 210);
width: 60px;
height: 60px;
border-radius: 10px;
}
[data-layer="1"] > section > section:first-child {
left: -30px;
top: -10px;
}
[data-layer="1"] > section > section:last-child {
right: -20px;
top: 50px;
}
#keyframes rotate {
0% {transform: rotate(0);}
100% {transform: rotate(359deg);}
}
.appletv_gloss {
/*display: none;*/
background-blend-mode: multiply;
}
.appletv [data-layer="1"] {
transform: scale(0.5);
transition: .3s ease-in-out 0s;
}
.appletv:hover [data-layer="1"] {
transform: scale(1);
}
.appletv:hover [data-layer="1"] > section > section {
animation: rotate 10s linear 0s infinite;
}
.appletv:hover [data-layer="1"] > section > section:last-child {
animation: rotate 25s linear 0s infinite;
}
#hover {
font-size: 30px;
position: absolute;
top: 37%;
text-align: center;
width: 100%;
color: white;
text-shadow: 0 2px 2px rgba(0,0,0,0.3) ;
}
<html>
<body>
<section class="center">
<section class="appletv">
<section class="appletv appletv_layer" data-img="https://source.unsplash.com/random">
<section id="hover">Hover Corners</section>
</section>
<section class="appletv appletv_layer">
<section></section>
<section></section>
</section>
</section>
</section>
</body>
</html>

Analog Clock - PHP with Javascript - date function

For my personal challenge I am trying to re-create a lo-fi house room. In the room I would like to display a analog clock with the current time, completely programmed with PHP.
I am currently able to "hardcode" it through if statements, but now I would like to move the minutes section every minute at the right time. I couldn't find it on internet, so I hope you can help me out here! Somehow I need to make this a for loop, but I don't know how to iterate through the css section.
Would appreciate any help!
<div class="clock">
<?php
$m = date("i");
echo date("h:i:s");
//Need to become a for/while loop
if ($m == 14){
echo '<div id="mins" style="transform: rotate(60deg);"</div>';
}
?>
<div id="mins"></div>
</div>
.clock{
height: 150px;
width: 150px;
border-radius: 50%;
background: white;
position: absolute;
left: 45%;
top: 20%;
}
#mins{
height: 60px;
width: 5px;
left: 50%;
position: relative;
transform: translateX(-50%);
background: black;
transform: rotate(0deg);
transform-origin: bottom center;
}
My solution with Javascript for an analog script. No PHP needed.
index.php
<div class="clock">
<div id="hours"></div>
<div id="minutes"></div>
<div id="seconds"></div>
</div>
javascript
const updateInMS = 1000;
// Get the HTML elements from the page.
const clock = document.getElementsByClassName('clock')[0];
const htmHours = document.getElementById('hours');
const htmMinutes = document.getElementById('minutes');
const htmSeconds = document.getElementById('seconds');
// Start the timer
startTimer();
function startTimer() {
// Trigger the tick function that loops the clock
tick();
}
function tick() {
setTimeout(function() {
// Retrieve the date
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
const secondsInDegrees = (360 * seconds) / 60;
const minutesInDegrees = (360 * minutes) / 60;
const hoursInDegrees = (360 * hours) / 12;
htmHours.style.transform = 'rotate(' + hoursInDegrees + 'deg)';
htmMinutes.style.transform = 'rotate(' + minutesInDegrees + 'deg)';
htmSeconds.style.transform = 'rotate(' + secondsInDegrees + 'deg)';
tick();
}, updateInMS);
}
css
.clock {
height: 150px;
width: 150px;
border-radius: 50%;
background: white;
position: absolute;
left: 45%;
top: 20%;
}
.clock div {
height: 60px;
width: 5px;
left: 50%;
/* UPDATED THIS TO FIXED: */
position: fixed;
transform: translateX(-50%);
background: black;
transform: rotate(0deg);
transform-origin: bottom center;
}
#hours {
background: black;
}
#minutes {
background: red;
}
#seconds {
background: blue;
}
Hope you like my solution.

Modify countdown script to allow for multiple countdowns per page

I am utilizing the following script from CodePen
// Create Countdown
var Countdown = {
// Backbone-like structure
$el: $('.countdown'),
// Params
countdown_interval: null,
total_seconds : 0,
// Initialize the countdown
init: function() {
// DOM
this.$ = {
hours : this.$el.find('.bloc-time.hours .figure'),
minutes: this.$el.find('.bloc-time.min .figure'),
seconds: this.$el.find('.bloc-time.sec .figure')
};
// Init countdown values
this.values = {
hours : this.$.hours.parent().attr('data-init-value'),
minutes: this.$.minutes.parent().attr('data-init-value'),
seconds: this.$.seconds.parent().attr('data-init-value'),
};
// Initialize total seconds
this.total_seconds = this.values.hours * 60 * 60 + (this.values.minutes * 60) + this.values.seconds;
// Animate countdown to the end
this.count();
},
count: function() {
var that = this,
$hour_1 = this.$.hours.eq(0),
$hour_2 = this.$.hours.eq(1),
$min_1 = this.$.minutes.eq(0),
$min_2 = this.$.minutes.eq(1),
$sec_1 = this.$.seconds.eq(0),
$sec_2 = this.$.seconds.eq(1);
this.countdown_interval = setInterval(function() {
if(that.total_seconds > 0) {
--that.values.seconds;
if(that.values.minutes >= 0 && that.values.seconds < 0) {
that.values.seconds = 59;
--that.values.minutes;
}
if(that.values.hours >= 0 && that.values.minutes < 0) {
that.values.minutes = 59;
--that.values.hours;
}
// Update DOM values
// Hours
that.checkHour(that.values.hours, $hour_1, $hour_2);
// Minutes
that.checkHour(that.values.minutes, $min_1, $min_2);
// Seconds
that.checkHour(that.values.seconds, $sec_1, $sec_2);
--that.total_seconds;
}
else {
clearInterval(that.countdown_interval);
}
}, 1000);
},
animateFigure: function($el, value) {
var that = this,
$top = $el.find('.top'),
$bottom = $el.find('.bottom'),
$back_top = $el.find('.top-back'),
$back_bottom = $el.find('.bottom-back');
// Before we begin, change the back value
$back_top.find('span').html(value);
// Also change the back bottom value
$back_bottom.find('span').html(value);
// Then animate
TweenMax.to($top, 0.8, {
rotationX : '-180deg',
transformPerspective: 300,
ease : Quart.easeOut,
onComplete : function() {
$top.html(value);
$bottom.html(value);
TweenMax.set($top, { rotationX: 0 });
}
});
TweenMax.to($back_top, 0.8, {
rotationX : 0,
transformPerspective: 300,
ease : Quart.easeOut,
clearProps : 'all'
});
},
checkHour: function(value, $el_1, $el_2) {
var val_1 = value.toString().charAt(0),
val_2 = value.toString().charAt(1),
fig_1_value = $el_1.find('.top').html(),
fig_2_value = $el_2.find('.top').html();
if(value >= 10) {
// Animate only if the figure has changed
if(fig_1_value !== val_1) this.animateFigure($el_1, val_1);
if(fig_2_value !== val_2) this.animateFigure($el_2, val_2);
}
else {
// If we are under 10, replace first figure with 0
if(fig_1_value !== '0') this.animateFigure($el_1, 0);
if(fig_2_value !== val_1) this.animateFigure($el_2, val_1);
}
}
};
// Let's go !
Countdown.init();
I have been trying to figure out for several hours how to modify it to support multiple countdown timers per page.
My approach so far was to try adding a numeric counter so that each "countdown" element gets a unique class, and then modifying the script to run on each element but this did not work and I don't think it will.
I'm not sure how else to approach it though so would appreciate some input.
You can create a new instance of this object with just a little bit of refactoring by converting it into a function.
For example, if you clone your <div class="countdown"/> HTML, and in JS you call:
new Countdown($($('.countdown')[0])).init();
new Countdown($($('.countdown')[1])).init();
Or, alternatively you could also initialize all .countdowns on page with:
$('.countdown').each((_, el) => (new Countdown($(el)).init()));
you will have two unique instances of the countdown.
// Create Countdown
function Countdown(node) {
this.$el = node;
this.countdown_interval = null;
this.total_seconds = 0;
this.init = function() {
// DOM
this.$ = {
hours: this.$el.find('.bloc-time.hours .figure'),
minutes: this.$el.find('.bloc-time.min .figure'),
seconds: this.$el.find('.bloc-time.sec .figure')
};
// Init countdown values
this.values = {
hours: this.$.hours.parent().attr('data-init-value'),
minutes: this.$.minutes.parent().attr('data-init-value'),
seconds: this.$.seconds.parent().attr('data-init-value'),
};
// Initialize total seconds
this.total_seconds = (this.values.hours * 60 * 60) +
(this.values.minutes * 60) +
this.values.seconds;
// Animate countdown to the end
this.count();
};
this.count = function() {
let that = this,
$hour_1 = this.$.hours.eq(0),
$hour_2 = this.$.hours.eq(1),
$min_1 = this.$.minutes.eq(0),
$min_2 = this.$.minutes.eq(1),
$sec_1 = this.$.seconds.eq(0),
$sec_2 = this.$.seconds.eq(1);
this.countdown_interval = setInterval(function() {
if (that.total_seconds > 0) {
--that.values.seconds;
if (that.values.minutes >= 0 && that.values.seconds < 0) {
that.values.seconds = 59;
--that.values.minutes;
}
if (that.values.hours >= 0 && that.values.minutes < 0) {
that.values.minutes = 59;
--that.values.hours;
}
// Update DOM values
// Hours
that.checkHour(that.values.hours, $hour_1, $hour_2);
// Minutes
that.checkHour(that.values.minutes, $min_1, $min_2);
// Seconds
that.checkHour(that.values.seconds, $sec_1, $sec_2);
--that.total_seconds;
} else {
clearInterval(that.countdown_interval);
}
}, 1000);
};
this.animateFigure = function($el, value) {
let that = this,
$top = $el.find('.top'),
$bottom = $el.find('.bottom'),
$back_top = $el.find('.top-back'),
$back_bottom = $el.find('.bottom-back');
// Before we begin, change the back value
$back_top.find('span').html(value);
// Also change the back bottom value
$back_bottom.find('span').html(value);
// Then animate
TweenMax.to($top, 0.8, {
rotationX: '-180deg',
transformPerspective: 300,
ease: Quart.easeOut,
onComplete: function() {
$top.html(value);
$bottom.html(value);
TweenMax.set($top, {
rotationX: 0
});
}
});
TweenMax.to($back_top, 0.8, {
rotationX: 0,
transformPerspective: 300,
ease: Quart.easeOut,
clearProps: 'all'
});
};
this.checkHour = function(value, $el_1, $el_2) {
let val_1 = value.toString().charAt(0),
val_2 = value.toString().charAt(1),
fig_1_value = $el_1.find('.top').html(),
fig_2_value = $el_2.find('.top').html();
if (value >= 10) {
// Animate only if the figure has changed
if (fig_1_value !== val_1) this.animateFigure($el_1, val_1);
if (fig_2_value !== val_2) this.animateFigure($el_2, val_2);
} else {
// If we are under 10, replace first figure with 0
if (fig_1_value !== '0') this.animateFigure($el_1, 0);
if (fig_2_value !== val_1) this.animateFigure($el_2, val_1);
}
}
}
// Let's go !
new Countdown($($('.countdown')[0])).init();
new Countdown($($('.countdown')[1])).init();
// Alternatively you could also initialize all countdowns on page with:
// $('.countdown').each((i, el) => (new Countdown($(el)).init()));
body {
background-color: #f2f1ed;
}
.wrap {
position: absolute;
bottom: 0;
top: 0;
left: 0;
right: 0;
margin: auto;
height: 310px;
}
a {
text-decoration: none;
color: #1a1a1a;
}
h1 {
margin-bottom: 60px;
text-align: center;
font: 300 2.25em "Lato";
text-transform: uppercase;
}
h1 strong {
font-weight: 400;
color: #ea4c4c;
}
h2 {
margin-bottom: 80px;
text-align: center;
font: 300 0.7em "Lato";
text-transform: uppercase;
}
h2 strong {
font-weight: 400;
}
.countdown {
width: 720px;
margin: 4px 0;
display: inline-block;
}
.countdown .bloc-time {
float: left;
margin-right: 45px;
text-align: center;
}
.countdown .bloc-time:last-child {
margin-right: 0;
}
.countdown .count-title {
display: block;
margin-bottom: 15px;
font: normal 0.94em "Lato";
color: #1a1a1a;
text-transform: uppercase;
}
.countdown .figure {
position: relative;
float: left;
height: 110px;
width: 100px;
margin-right: 10px;
background-color: #fff;
border-radius: 8px;
-moz-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
-webkit-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
}
.countdown .figure:last-child {
margin-right: 0;
}
.countdown .figure>span {
position: absolute;
left: 0;
right: 0;
margin: auto;
font: normal 5.94em/107px "Lato";
font-weight: 700;
color: #de4848;
}
.countdown .figure .top:after,
.countdown .figure .bottom-back:after {
content: "";
position: absolute;
z-index: -1;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.countdown .figure .top {
z-index: 3;
background-color: #f7f7f7;
transform-origin: 50% 100%;
-webkit-transform-origin: 50% 100%;
-moz-border-radius-topleft: 10px;
-webkit-border-top-left-radius: 10px;
border-top-left-radius: 10px;
-moz-border-radius-topright: 10px;
-webkit-border-top-right-radius: 10px;
border-top-right-radius: 10px;
-moz-transform: perspective(200px);
-ms-transform: perspective(200px);
-webkit-transform: perspective(200px);
transform: perspective(200px);
}
.countdown .figure .bottom {
z-index: 1;
}
.countdown .figure .bottom:before {
content: "";
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 50%;
background-color: rgba(0, 0, 0, 0.02);
}
.countdown .figure .bottom-back {
z-index: 2;
top: 0;
height: 50%;
overflow: hidden;
background-color: #f7f7f7;
-moz-border-radius-topleft: 10px;
-webkit-border-top-left-radius: 10px;
border-top-left-radius: 10px;
-moz-border-radius-topright: 10px;
-webkit-border-top-right-radius: 10px;
border-top-right-radius: 10px;
}
.countdown .figure .bottom-back span {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: auto;
}
.countdown .figure .top,
.countdown .figure .top-back {
height: 50%;
overflow: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.countdown .figure .top-back {
z-index: 4;
bottom: 0;
background-color: #fff;
-webkit-transform-origin: 50% 0;
transform-origin: 50% 0;
-moz-transform: perspective(200px) rotateX(180deg);
-ms-transform: perspective(200px) rotateX(180deg);
-webkit-transform: perspective(200px) rotateX(180deg);
transform: perspective(200px) rotateX(180deg);
-moz-border-radius-bottomleft: 10px;
-webkit-border-bottom-left-radius: 10px;
border-bottom-left-radius: 10px;
-moz-border-radius-bottomright: 10px;
-webkit-border-bottom-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.countdown .figure .top-back span {
position: absolute;
top: -100%;
left: 0;
right: 0;
margin: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrap">
<h1>Draft <strong>Countdown</strong></h1>
<!-- Countdown #1 -->
<div class="countdown">
<div class="bloc-time hours" data-init-value="24">
<span class="count-title">Hours</span>
<div class="figure hours hours-1">
<span class="top">2</span>
<span class="top-back">
<span>2</span>
</span>
<span class="bottom">2</span>
<span class="bottom-back">
<span>2</span>
</span>
</div>
<div class="figure hours hours-2">
<span class="top">4</span>
<span class="top-back">
<span>4</span>
</span>
<span class="bottom">4</span>
<span class="bottom-back">
<span>4</span>
</span>
</div>
</div>
<div class="bloc-time min" data-init-value="0">
<span class="count-title">Minutes</span>
<div class="figure min min-1">
<span class="top">0</span>
<span class="top-back">
<span>0</span>
</span>
<span class="bottom">0</span>
<span class="bottom-back">
<span>0</span>
</span>
</div>
<div class="figure min min-2">
<span class="top">0</span>
<span class="top-back">
<span>0</span>
</span>
<span class="bottom">0</span>
<span class="bottom-back">
<span>0</span>
</span>
</div>
</div>
<div class="bloc-time sec" data-init-value="0">
<span class="count-title">Seconds</span>
<div class="figure sec sec-1">
<span class="top">0</span>
<span class="top-back">
<span>0</span>
</span>
<span class="bottom">0</span>
<span class="bottom-back">
<span>0</span>
</span>
</div>
<div class="figure sec sec-2">
<span class="top">0</span>
<span class="top-back">
<span>0</span>
</span>
<span class="bottom">0</span>
<span class="bottom-back">
<span>0</span>
</span>
</div>
</div>
</div>
<div class="countdown">
<div class="bloc-time hours" data-init-value="4">
<span class="count-title">Hours</span>
<div class="figure hours hours-1">
<span class="top">0</span>
<span class="top-back">
<span>0</span>
</span>
<span class="bottom">0</span>
<span class="bottom-back">
<span>0</span>
</span>
</div>
<div class="figure hours hours-2">
<span class="top">4</span>
<span class="top-back">
<span>4</span>
</span>
<span class="bottom">4</span>
<span class="bottom-back">
<span>4</span>
</span>
</div>
</div>
<div class="bloc-time min" data-init-value="30">
<span class="count-title">Minutes</span>
<div class="figure min min-1">
<span class="top">3</span>
<span class="top-back">
<span>3</span>
</span>
<span class="bottom">3</span>
<span class="bottom-back">
<span>3</span>
</span>
</div>
<div class="figure min min-2">
<span class="top">0</span>
<span class="top-back">
<span>0</span>
</span>
<span class="bottom">0</span>
<span class="bottom-back">
<span>0</span>
</span>
</div>
</div>
<div class="bloc-time sec" data-init-value="30">
<span class="count-title">Seconds</span>
<div class="figure sec sec-1">
<span class="top">3</span>
<span class="top-back">
<span>3</span>
</span>
<span class="bottom">3</span>
<span class="bottom-back">
<span>3</span>
</span>
</div>
<div class="figure sec sec-2">
<span class="top">0</span>
<span class="top-back">
<span>0</span>
</span>
<span class="bottom">0</span>
<span class="bottom-back">
<span>0</span>
</span>
</div>
</div>
</div>
</div>
Here's a link to the updated codepen.
Hope this helps,
As a jQuery plugin it could go this way (you can customize it further):
// Create Countdown Plugin
$.fn.fancyCountdown = function() {
return this.each(function() {
var that=this;
var $el=$(this);
that.values = {
titleHours: 'Hours',
titleMinutes: 'Minutes',
titleSeconds: 'Seconds'
};
if( $el.data('settings') ) {
that.values = $el.data('settings');
} else {
that.values = $.extend( {}, that.values, $el.data() );
};
var explodeTime = that.values.time.split(':');
that.values.hours = explodeTime[0]*1;
that.values.minutes = explodeTime[1]*1;
that.values.seconds = explodeTime[2]*1;
that.values.hours1 = explodeTime[0][0];
that.values.hours2 = explodeTime[0][1];
that.values.minutes1 = explodeTime[1][0];
that.values.minutes2 = explodeTime[1][1];
that.values.seconds1 = explodeTime[2][0];
that.values.seconds2 = explodeTime[2][1];
that.values.totalSeconds = that.values.hours*60*60 + that.values.minutes*60 + that.values.seconds;
that.values.template = '\
<span class="top">#</span>\
<span class="top-back">\
<span>#</span>\
</span>\
<span class="bottom">#</span>\
<span class="bottom-back">\
<span>#</span>\
</span>\
';
that.countdownInterval = null;
if( !$el.hasClass('countdown-engaged') ) {
$el.addClass('countdown-engaged');
// Initialize the countdown
that.init=function() {
// DOM
that.createDom();
that.$ = {
hours: $el.find('.bloc-time.hours .figure'),
minutes: $el.find('.bloc-time.min .figure'),
seconds: $el.find('.bloc-time.sec .figure')
};
// Animate countdown to the end
that.count();
};
that.createDom = function() {
var html = '\
<div class="bloc-time hours">\
<span class="count-title">' + that.values.titleHours + '</span>\
<div class="figure hours hours-1">\
' + that.values.template.replace(/#/g, that.values.hours1) + '\
</div>\
<div class="figure hours hours-2">\
' + that.values.template.replace(/#/g, that.values.hours2) + '\
</div>\
</div>\
<div class="bloc-time min">\
<span class="count-title">' + that.values.titleMinutes + '</span>\
<div class="figure min min-1">\
' + that.values.template.replace(/#/g, that.values.minutes1) + '\
</div>\
<div class="figure min min-2">\
' + that.values.template.replace(/#/g, that.values.minutes2) + '\
</div>\
</div>\
<div class="bloc-time sec">\
<span class="count-title">' + that.values.titleSeconds + '</span>\
<div class="figure sec sec-1">\
' + that.values.template.replace(/#/g, that.values.seconds1) + '\
</div>\
<div class="figure sec sec-2">\
' + that.values.template.replace(/#/g, that.values.seconds2) + '\
</div>\
</div>\
';
$el.html(html);
};
that.count = function() {
var $hour_1 = that.$.hours.eq(0),
$hour_2 = that.$.hours.eq(1),
$min_1 = that.$.minutes.eq(0),
$min_2 = that.$.minutes.eq(1),
$sec_1 = that.$.seconds.eq(0),
$sec_2 = that.$.seconds.eq(1);
that.countdownInterval = setInterval(function() {
if (that.values.totalSeconds > 0) {
--that.values.seconds;
if (that.values.minutes >= 0 && that.values.seconds < 0) {
that.values.seconds = 59;
--that.values.minutes;
}
if (that.values.hours >= 0 && that.values.minutes < 0) {
that.values.minutes = 59;
--that.values.hours;
}
// Update DOM values
// Hours
that.checkHour(that.values.hours, $hour_1, $hour_2);
// Minutes
that.checkHour(that.values.minutes, $min_1, $min_2);
// Seconds
that.checkHour(that.values.seconds, $sec_1, $sec_2);
--that.values.totalSeconds;
} else {
clearInterval(that.countdownInterval);
};
}, 1000);
};
that.animateFigure = function($el, value) {
var $top = $el.find('.top'),
$bottom = $el.find('.bottom'),
$back_top = $el.find('.top-back'),
$back_bottom = $el.find('.bottom-back');
// Before we begin, change the back value
$back_top.find('span').html(value);
// Also change the back bottom value
$back_bottom.find('span').html(value);
// Then animate
TweenMax.to($top, 0.8, {
rotationX: '-180deg',
transformPerspective: 300,
ease: Quart.easeOut,
onComplete: function() {
$top.html(value);
$bottom.html(value);
TweenMax.set($top, {
rotationX: 0
});
}
});
TweenMax.to($back_top, 0.8, {
rotationX: 0,
transformPerspective: 300,
ease: Quart.easeOut,
clearProps: 'all'
});
};
that.checkHour=function(value, $el_1, $el_2) {
var val_1 = value.toString().charAt(0),
val_2 = value.toString().charAt(1),
fig_1_value = $el_1.find('.top').html(),
fig_2_value = $el_2.find('.top').html();
if (value >= 10) {
// Animate only if the figure has changed
if (fig_1_value !== val_1) that.animateFigure($el_1, val_1);
if (fig_2_value !== val_2) that.animateFigure($el_2, val_2);
} else {
// If we are under 10, replace first figure with 0
if (fig_1_value !== '0') that.animateFigure($el_1, 0);
if (fig_2_value !== val_1) that.animateFigure($el_2, val_1);
}
};
};
that.init();
});
};
$('.countdown').fancyCountdown();
body {
background-color: #f2f1ed;
}
.wrap {
margin: 0 auto;
height: 310px;
}
a {
text-decoration: none;
color: #1a1a1a;
}
h1 {
margin-bottom: 60px;
text-align: center;
font: 300 2.25em "Lato";
text-transform: uppercase;
}
h1 strong {
font-weight: 400;
color: #ea4c4c;
}
h2 {
margin-bottom: 80px;
text-align: center;
font: 300 0.7em "Lato";
text-transform: uppercase;
}
h2 strong {
font-weight: 400;
}
.countdown {
width: 720px;
margin: 0 auto;
}
.countdown .bloc-time {
float: left;
margin-right: 45px;
text-align: center;
}
.countdown .bloc-time:last-child {
margin-right: 0;
}
.countdown .count-title {
display: block;
margin-bottom: 15px;
font: normal 0.94em "Lato";
color: #1a1a1a;
text-transform: uppercase;
}
.countdown .figure {
position: relative;
float: left;
height: 110px;
width: 100px;
margin-right: 10px;
background-color: #fff;
border-radius: 8px;
-moz-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
-webkit-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
}
.countdown .figure:last-child {
margin-right: 0;
}
.countdown .figure>span {
position: absolute;
left: 0;
right: 0;
margin: auto;
font: normal 5.94em/107px "Lato";
font-weight: 700;
color: #de4848;
}
.countdown .figure .top:after,
.countdown .figure .bottom-back:after {
content: "";
position: absolute;
z-index: -1;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.countdown .figure .top {
z-index: 3;
background-color: #f7f7f7;
transform-origin: 50% 100%;
-webkit-transform-origin: 50% 100%;
-moz-border-radius-topleft: 10px;
-webkit-border-top-left-radius: 10px;
border-top-left-radius: 10px;
-moz-border-radius-topright: 10px;
-webkit-border-top-right-radius: 10px;
border-top-right-radius: 10px;
-moz-transform: perspective(200px);
-ms-transform: perspective(200px);
-webkit-transform: perspective(200px);
transform: perspective(200px);
}
.countdown .figure .bottom {
z-index: 1;
}
.countdown .figure .bottom:before {
content: "";
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 50%;
background-color: rgba(0, 0, 0, 0.02);
}
.countdown .figure .bottom-back {
z-index: 2;
top: 0;
height: 50%;
overflow: hidden;
background-color: #f7f7f7;
-moz-border-radius-topleft: 10px;
-webkit-border-top-left-radius: 10px;
border-top-left-radius: 10px;
-moz-border-radius-topright: 10px;
-webkit-border-top-right-radius: 10px;
border-top-right-radius: 10px;
}
.countdown .figure .bottom-back span {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: auto;
}
.countdown .figure .top,
.countdown .figure .top-back {
height: 50%;
overflow: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.countdown .figure .top-back {
z-index: 4;
bottom: 0;
background-color: #fff;
-webkit-transform-origin: 50% 0;
transform-origin: 50% 0;
-moz-transform: perspective(200px) rotateX(180deg);
-ms-transform: perspective(200px) rotateX(180deg);
-webkit-transform: perspective(200px) rotateX(180deg);
transform: perspective(200px) rotateX(180deg);
-moz-border-radius-bottomleft: 10px;
-webkit-border-bottom-left-radius: 10px;
border-bottom-left-radius: 10px;
-moz-border-radius-bottomright: 10px;
-webkit-border-bottom-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.countdown .figure .top-back span {
position: absolute;
top: -100%;
left: 0;
right: 0;
margin: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrap">
<h1>Draft <strong>Countdown</strong></h1>
<div class="countdown" data-time="22:30:00"></div>
</div>
<div class="wrap">
<h1>Second <strong>Countdown</strong></h1>
<div class="countdown" data-settings='{"time": "01:22:50", "titleHours": "Sati", "titleMinutes": "Minuti", "titleSeconds": "Sekunde"}'></div>
</div>
Also on JSFiddle.
I only kept the visual aspect in CSS (with some modifications) and I completely rewrote all the code in "pure" Javascript (no jQuery) and keeping the GSAP / TweenMax library.
You can put as many countDown as you want by filling in an array indicating the titles and durations etc.
There is only one setInterval handling the different countDown and it will stop with the last active countDown.
I chose this solution because using several setInterval at the same time seemed to me disproportionate for that, and unnecessarily overload the OS.
For greater accuracy, all countDowns are based on the system clock (and not on the call cycles of the setInterval, because they are "naturally" shifted and are therefore incompatible for any use of time measurement).
This script makes it possible to carry out 2 types of countDown:
- for a fixed duration (eg 6 minutes for eggs)
- either on a date (or time) end (ex: birthday, appointment ...)
The other interest of this script is that it generates on demand the set of elements html useful to the display of countDown on the page, and it allows to choose the display with the number of desired units
const myCountDowns= [ { title: 'timer <strong>24h</strong>'
, type : 'Hours'
, timer: '24h'
}
, { title: 'Tea cup <strong>2\' 45"</strong>'
, type : 'Minutes'
, timer: '2m 45s'
}
, { title: 'until the new year <strong>2020</strong>'
, type : 'Days'
, date : '01 01 2020' // local Month Day Year
}
]
CountDown.BuildsAndRun( myCountDowns )
// ->type : 'Days' or 'Hours' or 'Minutes' or 'seconds'
// set "timer" for time duration otherwise set a "date" value
// timer string format is _number_UNIT where UNIT = 'd','h','m','s' for Days, Hours, Minutes, Seconds
// ex : '3d 25m 6s' = 3 days 0 hours 25 minutes, 6 seconds (days = 0, 0 is defauls for all units)
// ex : '6s 3d 25m' = the same, there is no order
// date format is JS Date format see new Date( _STRING_ )
FULL CODE (on snippet below)
(function( CountDown )
{
// Private vars
const domEndBody = document.body.querySelector('script') || document.body // for countDowns insert place
, eWrapp = document.createElement('div')
, eTitle = document.createElement('h1')
, eCountDown = document.createElement('div')
, eTimeBlock = document.createElement('div')
, eCountTitle = document.createElement('span')
, eFigure = document.createElement('div')
, counters = [] // list of CountDowns
, one_Sec = 1000
, one_Min = one_Sec * 60
, one_Hour = one_Min * 60
, one_Day = one_Hour * 24
, padZ =(val,sz) => ('0'.repeat(sz)+val.toString(10)).slice(-sz) // return string with leading zeros
, Interface = [ { xL:8, Title:'Days', Fig:3 } // titles & counts of figures
, { xL:5, Title:'Hours', Fig:2 }
, { xL:3, Title:'Minutes', Fig:2 }
, { xL:0, Title:'Seconds', Fig:2 }
]
, animOpt = { rotationX: 0, transformPerspective: 300, ease: Quart.easeOut, clearProps: 'all' }
var activeCounters = 0
// finalize countDown elements
eWrapp.className = 'wrap'
eTitle.innerHTML = 'F<strong>D</strong>' // 'Draft <strong>Countdown</strong>'
eCountDown.className = 'countdown'
eTimeBlock.className = 'bloc-time'
eCountTitle.className = 'count-title'
eFigure.className = 'figure'
eFigure.innerHTML = '<span class="top" > </span>'
+ ' <span class="top-back" > <span> </span> </span>'
+ ' <span class="bottom" > </span>'
+ ' <span class="bottom-back"> <span> </span> </span>'
//Public Method ........................................................................
CountDown.BuildsAndRun = function( TimerArray )
{
for (let TimerParms of TimerArray)
{ CountDown_Build ( TimerParms ) }
setTimeout(() => { CountDown_Run() }, 300); // the Timeout is just for start spectacle
}
// Private Methods......................................................................
CountDown_Build = function( parms )
{
let len = parms.type==='Hours'?6:parms.type==='Minutes'?4:parms.type==='seconds'?2:9
, ctD = { lg : len // countDown number of figure (digits)
, face : ' '.repeat(len) // actuel face of countDown
, fig : [] // array of firures (DOM elements)
, ref : counters.length // ID of this countDown
, time : null // time to add to compute taget time for CountDown
, target : null // target Timie value
, activ : true // to check if count down is activ or not ( finished )
}
// generate all Figures of CountDown
for(let i=len;i--;) { // from len to 0 (just my fav ninja)
ctD.fig.push( eFigure.cloneNode(true) )
}
// CountDown DOM making
let xWrapp = eWrapp.cloneNode(true)
, xTitle = eTitle.cloneNode(true)
, xCountDown = eCountDown.cloneNode(true)
, noFig = 0 // ref on the first ctD.fig list (of figures)
xTitle.innerHTML = parms.title
xWrapp.style.width = len===9?'1105px':len===6?'740px':len===4?'485px':'230px'
//xCountDown.style.width = len===9?'1090px':len===6?'730px':len===4?'470px':'230px'
xWrapp.appendChild(xTitle)
xWrapp.appendChild(xCountDown)
// making of bloc-time elements
for(eBlk of Interface)
{
if(len>eBlk.xL)
{
let xTimeBlock = eTimeBlock.cloneNode(true)
, xCountTitle = eCountTitle.cloneNode(true)
xCountTitle.textContent = eBlk.Title
xTimeBlock.appendChild(xCountTitle)
for(let f=eBlk.Fig;f--;) // (fav ninja again)
{ xTimeBlock.appendChild(ctD.fig[noFig++]) } // move figures inside
xCountDown.appendChild(xTimeBlock)
}
}
document.body.insertBefore(xWrapp, domEndBody) // insert CountDowm on page
// set count down initial values
if (parms.timer) // in case of timer...
{
let TimeInfos = get_DHMS(parms.timer, len )
ctD.time = TimeInfos.add
counters.push( ctD )
activeCounters++
updateInterface( ctD.ref, TimeInfos.dis ) // show first figure faces
}
else if (parms.date) // in case of CountDown until date
{
ctD.target = new Date(parms.date);
counters.push( ctD )
if (showFaceOnNow( ctD.ref ))
{ activeCounters++ }
}
}
CountDown_Run = function()
{
for (let elm of counters)
{
if (elm.time)
{ elm.target = new Date().getTime() + elm.time }
}
let timerInterval = setInterval(_=>
{
counters.forEach((elm,ref)=>
{
if ( elm.activ )
{
if (!showFaceOnNow( ref ))
{ activeCounters-- }
}
if ( activeCounters<=0 )
{ clearInterval(timerInterval) }
})
}
, one_Sec )
}
showFaceOnNow = function(ref)
{
let now = new Date().getTime()
, tim = counters[ref].target - now
, face = '0'.repeat( counters[ref].lg )
if (tim >= 0)
{
face = padZ(Math.floor(tim / one_Day), 3)
face += padZ((Math.floor((tim % one_Day ) / one_Hour)), 2)
face += padZ((Math.floor((tim % one_Hour) / one_Min )), 2)
face += padZ((Math.floor((tim % one_Min ) / one_Sec )), 2)
face = padZ( face, counters[ref].lg )
}
else
{
counters[ref].activ = false
}
updateInterface ( ref, face)
return counters[ref].activ
}
updateInterface = function(ref, newVal)
{
for(let p = counters[ref].lg ; p--;) // update faces figures backward
{
if (counters[ref].face.charAt(p) !== newVal.charAt(p))
{
animateFigure( counters[ref].fig[p], newVal.charAt(p) )
}
}
counters[ref].face = newVal
}
get_DHMS = function (timer_val, lg)
{
let vDelay = { d:0, h:0, m:0, s:0 }
, vNum = '0'
, ret = { add: 0, dis: ''}
for (const c of timer_val)
{
if (/[0-9]/.test(c) ) vNum += c
if (/[dhms]/.test(c) )
{
vDelay[c] = parseInt(vNum)
vNum = '0'
}
}
ret.add = (vDelay.d*one_Day)+(vDelay.h*one_Hour)+(vDelay.m*one_Min)+(vDelay.s*one_Sec)
ret.dis = (padZ(vDelay.d,3)+padZ(vDelay.h,2)+padZ(vDelay.m,2)+padZ(vDelay.s,2)).slice(-lg)
return ret
}
animateFigure = function (domElm, newChar)
{
let eTop = domElm.querySelector('.top')
, eBottom = domElm.querySelector('.bottom')
, eBack_top = domElm.querySelector('.top-back')
// Before we begin, change the back value and the back bottom value
eBack_top.querySelector('span').textContent = newChar
domElm.querySelector('.bottom-back span').textContent = newChar
TweenMax.to(eTop, 0.8, // Then animate
{ rotationX : '-180deg'
, transformPerspective: 300
, ease : Quart.easeOut
, onComplete : function()
{
eTop.textContent = newChar
eBottom.textContent = newChar
TweenMax.set(eTop, { rotationX: 0 })
}
})
TweenMax.to(eBack_top, 0.8, animOpt)
}
}( window.CountDown = window.CountDown || {}));
/********************************************************************************************/
const myCountDowns= [ { title: 'timer <strong>24h</strong>'
, type : 'Hours'
, timer: '24h'
}
, { title: 'Tea cup <strong>2\' 45"</strong>'
, type : 'Minutes'
, timer: '2m 45s'
}
, { title: 'until the new year <strong>2020</strong>'
, type : 'Days'
, date : '01 01 2020' // local Month Day Year
}
]
CountDown.BuildsAndRun( myCountDowns )
// ->type : 'Days' or 'Hours' or 'Minutes' or 'seconds'
// set "timer" for time duration otherwise set a "date" value
// timer string format is _number_UNIT where UNIT = 'd','h','m','s' for Days, Hours, Minutes, Seconds
// ex : '3d 25m 6s' = 3 days 0 hours 25 minutes, 6 seconds (days = 0, 0 is defauls for all units)
// ex : '6s 3d 25m' = the same, there is no order
// date format is JS Date format see new Date( _STRING_ )
body {
background-color: #f2f1ed;
margin: 0;
}
.wrap {
margin: 2em auto;
height: 270px;
width: 1500px; /* be re-calculate on JS */
border-radius: 1em;
padding: 10px 5px 0 5px;
box-shadow: 0px 0px 1px 1px rgba(170, 170, 170, 0.64);
}
a {
text-decoration: none;
color: #1a1a1a;
}
h1 {
margin-bottom: 30px;
text-align: center;
font: 300 2.25em "Lato";
text-transform: uppercase;
}
h1 strong {
font-weight: 400;
color: #ea4c4c;
}
h2 {
margin-bottom: 80px;
text-align: center;
font: 300 0.7em "Lato";
text-transform: uppercase;
}
h2 strong {
font-weight: 400;
}
.countdown {
/* width: 100%; or be re-calculate on JS */
margin: 0 auto;
padding: 0 10px 10px 10px;
}
.countdown .bloc-time {
float: left;
margin-right: 45px;
text-align: center;
}
.countdown .bloc-time:last-child {
margin-right: 0;
}
.countdown .count-title {
display: block;
margin-bottom: 15px;
font: normal 0.94em "Lato";
color: #1a1a1a;
text-transform: uppercase;
}
.countdown .figure {
position: relative;
float: left;
height: 110px;
width: 100px;
margin-right: 10px;
background-color: #fff;
border-radius: 8px;
-moz-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
-webkit-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08);
}
.countdown .figure:last-child {
margin-right: 0;
}
.countdown .figure > span {
position: absolute;
left: 0;
right: 0;
margin: auto;
font: normal 5.94em/107px "Lato";
font-weight: 700;
color: #de4848;
}
.countdown .figure .top:after, .countdown .figure .bottom-back:after {
content: "";
position: absolute;
z-index: -1;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.countdown .figure .top {
z-index: 3;
background-color: #f7f7f7;
transform-origin: 50% 100%;
-webkit-transform-origin: 50% 100%;
-moz-border-radius-topleft: 10px;
-webkit-border-top-left-radius: 10px;
border-top-left-radius: 10px;
-moz-border-radius-topright: 10px;
-webkit-border-top-right-radius: 10px;
border-top-right-radius: 10px;
-moz-transform: perspective(200px);
-ms-transform: perspective(200px);
-webkit-transform: perspective(200px);
transform: perspective(200px);
}
.countdown .figure .bottom {
z-index: 1;
}
.countdown .figure .bottom:before {
content: "";
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 50%;
background-color: rgba(0, 0, 0, 0.02);
}
.countdown .figure .bottom-back {
z-index: 2;
top: 0;
height: 50%;
overflow: hidden;
background-color: #f7f7f7;
-moz-border-radius-topleft: 10px;
-webkit-border-top-left-radius: 10px;
border-top-left-radius: 10px;
-moz-border-radius-topright: 10px;
-webkit-border-top-right-radius: 10px;
border-top-right-radius: 10px;
}
.countdown .figure .bottom-back span {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: auto;
}
.countdown .figure .top, .countdown .figure .top-back {
height: 50%;
overflow: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.countdown .figure .top-back {
z-index: 4;
bottom: 0;
background-color: #fff;
-webkit-transform-origin: 50% 0;
transform-origin: 50% 0;
-moz-transform: perspective(200px) rotateX(180deg);
-ms-transform: perspective(200px) rotateX(180deg);
-webkit-transform: perspective(200px) rotateX(180deg);
transform: perspective(200px) rotateX(180deg);
-moz-border-radius-bottomleft: 10px;
-webkit-border-bottom-left-radius: 10px;
border-bottom-left-radius: 10px;
-moz-border-radius-bottomright: 10px;
-webkit-border-bottom-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.countdown .figure .top-back span {
position: absolute;
top: -100%;
left: 0;
right: 0;
margin: auto;
}
<link href='https://fonts.googleapis.com/css?family=Lato:300,400,700' rel='stylesheet' type='text/css'>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<!-- no more HTML code -->
// Create Countdown
var Countdown = {
// Backbone-like structure
$el: $('.countdown'),
// Params
countdown_interval: null,
total_seconds : 0,
// Initialize the countdown
init: function() {
// DOM
this.$ = {
hours : this.$el.find('.bloc-time.hours .figure'),
minutes: this.$el.find('.bloc-time.min .figure'),
seconds: this.$el.find('.bloc-time.sec .figure')
};
// Init countdown values
this.values = {
hours : this.$.hours.parent().attr('data-init-value'),
minutes: this.$.minutes.parent().attr('data-init-value'),
seconds: this.$.seconds.parent().attr('data-init-value'),
};
// Initialize total seconds
this.total_seconds = this.values.hours * 60 * 60 + (this.values.minutes * 60) + this.values.seconds;
// Animate countdown to the end
this.count();
},
count: function() {
var that = this,
$hour_1 = this.$.hours.eq(0),
$hour_2 = this.$.hours.eq(1),
$min_1 = this.$.minutes.eq(0),
$min_2 = this.$.minutes.eq(1),
$sec_1 = this.$.seconds.eq(0),
$sec_2 = this.$.seconds.eq(1);
this.countdown_interval = setInterval(function() {
if(that.total_seconds > 0) {
--that.values.seconds;
if(that.values.minutes >= 0 && that.values.seconds < 0) {
that.values.seconds = 59;
--that.values.minutes;
}
if(that.values.hours >= 0 && that.values.minutes < 0) {
that.values.minutes = 59;
--that.values.hours;
}
// Update DOM values
// Hours
that.checkHour(that.values.hours, $hour_1, $hour_2);
// Minutes
that.checkHour(that.values.minutes, $min_1, $min_2);
// Seconds
that.checkHour(that.values.seconds, $sec_1, $sec_2);
--that.total_seconds;
}
else {
clearInterval(that.countdown_interval);
}
}, 1000);
},
animateFigure: function($el, value) {
var that = this,
$top = $el.find('.top'),
$bottom = $el.find('.bottom'),
$back_top = $el.find('.top-back'),
$back_bottom = $el.find('.bottom-back');
// Before we begin, change the back value
$back_top.find('span').html(value);
// Also change the back bottom value
$back_bottom.find('span').html(value);
// Then animate
TweenMax.to($top, 0.8, {
rotationX : '-180deg',
transformPerspective: 300,
ease : Quart.easeOut,
onComplete : function() {
$top.html(value);
$bottom.html(value);
TweenMax.set($top, { rotationX: 0 });
}
});
TweenMax.to($back_top, 0.8, {
rotationX : 0,
transformPerspective: 300,
ease : Quart.easeOut,
clearProps : 'all'
});
},
checkHour: function(value, $el_1, $el_2) {
var val_1 = value.toString().charAt(0),
val_2 = value.toString().charAt(1),
fig_1_value = $el_1.find('.top').html(),
fig_2_value = $el_2.find('.top').html();
if(value >= 10) {
// Animate only if the figure has changed
if(fig_1_value !== val_1) this.animateFigure($el_1, val_1);
if(fig_2_value !== val_2) this.animateFigure($el_2, val_2);
}
else {
// If we are under 10, replace first figure with 0
if(fig_1_value !== '0') this.animateFigure($el_1, 0);
if(fig_2_value !== val_1) this.animateFigure($el_2, val_1);
}
}
};
function initializeCountdown ( $element ){
let uniqueCountdown = $.extend( {}, Countdown );
uniqueCountdown.$el = $element;
uniqueCountdown.init();
}
$('.countdown').each( function(){
initializeCountdown( $(this) );
});
I've changed the logic with the last function and its subsequent invocation. The method makes a copy of the Countdown to provide a unique this for each object. It then sets the $el it corresponds to before initializing. We then call this method for each of our countdown elements, and since the this is unique, each countdown will operate independently of each other and will allow for countdowns to have different starting times.

Categories

Resources