I have this array:
myArr = [1, 2, 3, 4]
The first element in this array is displayed on the screen.
There are two buttons (left and right). They fire functions that move the elements in the array forward or backward:
shiftLeft() {
var firstElement = myArr.shift()
myArr.push(firstElement)
}
Resulting in: myArr = [2, 3, 4, 1] and now element 2 shows up on the screen.
The unfortunate side affect of this method is that it is difficult to animate the transition for the old element to the new one.
I tried to use keyframes but the animation is not as smooth (and I lack experience).
You can find a generic version of my setup here: https://codepen.io/riza-khan/pen/KKVyaaR
Results I am looking for:
A smooth transition from the first to the second element. Much like any generic carousels from (pick your favorite CSS framework).
Happy to provide additional information should it be required.
Thank you,
You could have an overlay layer that is spawning and removing nodes to trigger the animation, and a setTimer to change the underlay value after the animation completes.
Note: Stack Overflow snippets do not currently support scss, instead see my codepen here.
const arr = [1, 2, 3, 4];
const left = document.querySelector(".left");
const right = document.querySelector(".right");
const container = document.querySelector(".container");
const overlay = document.querySelector("#overlay");
let prevEle = document.querySelector(".number");
let disabled = false;
left.addEventListener("click", function() {
if (disabled) return;
movement("left", arr);
});
right.addEventListener("click", function() {
if (disabled) return;
movement("right", arr);
});
// document.addEventListener("DOMContentLoaded", function () {
// setupUI();
// });
function createNew() {
let ele = document.createElement("div");
ele.classList.add("number");
ele.classList.add("fadeIn");
ele.innerText = arr[0];
return ele;
}
function setupUI() {
disabled = true;
let newEle = createNew();
overlay.removeChild(overlay.firstChild);
overlay.prepend(newEle);
setTimeout(() => disabled = false, 350);
setTimeout(() => prevEle.innerText = arr[0], 350);
}
function movement(direction, array) {
if (direction === "right") {
var element = array.shift();
array.push(element);
} else {
var element = array.pop();
array.unshift(element);
}
setupUI();
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
height: 100vh;
background: rgb(250,175,170);
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 2fr 1fr;
.number {
grid-column: 1 / -1;
cursor: default;
}
.fadeIn {
animation: fadeIn ease .5s;
-webkit-animation: fadeIn ease .5s;
-moz-animation: fadeIn ease .5s;
-o-animation: fadeIn ease .5s;
-ms-animation: fadeIn ease .5s;
}
#keyframes fadeIn {
0% {opacity:0;}
100% {opacity:1;}
}
}
.overlay {
position: absolute;
width: 100vw;
height: 100vh;
pointer-events: none;
.number {
background: rgb(250,175,170);
}
.container{
background: transparent;
}
}
.number {
margin: auto;
font-size: 25em;
}
.btn {
margin: auto;
border: solid 2px black;
padding: 1em 2em;
border-radius: 5px;
transition: all 0.5s ease;
cursor: pointer;
&:hover {
background: black;
color: white;
}
}
<div class="overlay">
<div class="overlay container" id="overlay">
</div>
</div>
<div class="container">
<div class="number">1</div>
<button class="btn left">Left</button>
<button class="btn right">Right</button>
</div>
Related
I'm trying to simulate the animation of flips of a coin with JS & CSS.
I guess the keys are transform-style, backface-visibility, rotateY, animation-fill-mode and transform in CSS as well as Math.random in JS.
If the coin is the heads, everything is OK.
If the coin is tail, clicking the button will flip it to head and then start the expected flipping animation.
How do I make it start flipping animation directly from the tail?
const coin = document.querySelector('#coin');
const button = document.querySelector('#flip');
const status = document.querySelector('#status');
const heads = document.querySelector('#headsCount');
const tails = document.querySelector('#tailsCount');
let headsCount = 0;
let tailsCount = 0;
function deferFn(callback, ms) {
setTimeout(callback, ms);
}
function processResult(result) {
if (result === 'heads') {
headsCount++;
heads.innerText = headsCount;
} else {
tailsCount++;
tails.innerText = tailsCount;
}
status.innerText = result.toUpperCase();
}
function flipCoin() {
coin.setAttribute('class', '');
const random = Math.random();
const result = random < 0.5 ? 'heads' : 'tails';
deferFn(function() {
coin.setAttribute('class', 'animate-' + result);
deferFn(processResult.bind(null, result), 2900);
}, 100);
}
button.addEventListener('click', flipCoin);
h2 {
margin: .25rem;
}
div.container {
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
}
button {
padding: 1rem;
background-color: skyblue;
}
#coin {
position: relative;
width: 15rem;
height: 15rem;
margin: 2rem 0rem;
transform-style: preserve-3d;
}
#coin div {
width: 100%;
height: 100%;
border: 2px solid black;
border-radius: 50%;
backface-visibility: hidden;
background-size: contain;
position: absolute;
}
.heads {
background-image: url("https://en.numista.com/catalogue/photos/inde/2311-original.jpg");
}
.animate-heads {
animation: flipHeads 3s;
animation-fill-mode: forwards;
}
#keyframes flipHeads {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(1800deg);
}
}
.tails {
background-image: url("https://en.numista.com/catalogue/photos/inde/3165-original.jpg");
transform: rotateY(-180deg);
}
.animate-tails {
animation: flipTails 3s;
animation-fill-mode: forwards;
}
#keyframes flipTails {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(1620deg);
}
}
<div class='container'>
<h2>Confused about your life decision? Just flip this coin!</h2>
<h2>Btw, don't forget to assign something to both sides.</h2>
<p>And don't take your life decision based on this stupid coin flip. I was kidding.</p>
<div id="coin" class=''>
<div id="heads" class="heads"></div>
<div id="tails" class="tails"></div>
</div>
<button id="flip">Flip this thing</button>
<p>Heads: <span id="headsCount">0</span> Tails: <span id="tailsCount">0</span></p>
<p><span id="status"></span></p>
</div>
You can use the css property:
animation-fill-mode: forwards;
I've been trying to develop a CSS keyframes animation for a navigation bar.
You can see in the code snippet how the animation works - the red line is animated when the user clicks an element of the nav bar. The first element of the nav bar is active by default (the red line is under this element). When an element is clicked, the JS takes the properties of the animation element, as well as the properties of the element that was clicked. These properties are incorporated into new keyframes that are inserted into the single keyframes rule.
When the second element is clicked, the animation runs successfully from element 1 --> 2. The animation also runs successfully from element 1 --> 3.
But after the animation plays from element 1 --> 2, it won't play from element 2 --> 3. The animationend event does not trigger (I checked this). As of now, I'm only concerned with the animation going forwards.
After researching, I tried several methods to fix this. Removing and reattaching the animation class does not work, even with a DOM reflow being triggered. Changing the animation-play-state from 'running' to 'paused' does not work either. Other solutions, such as changing the animation-name to 'none' and then back, only generate more problems, like the position of the animation element being reset upon the ending of the animation. I truly do not know how to fix this.
I would prefer to make a flexible keyframes animation, such as this, rather than brute-forcing it. A brute force scenario would include making 6 different keyframes rules, and I want the code to be applicable to any number of elements in the navigation bar. Adding keyframes rules for every addition of an element would require exponentially more code each addition.
Thanks.
~ Code for demo ~
var keyframes = findKeyframesRule('movey');
$(document).ready(() => {
$('div.one').click(() => {
if (!($('div.one').hasClass('active'))) {
/* unfinished */
}
})
$('div.two').click(() => {
if (!($('div.two').hasClass('active'))) {
/* transfer active class */
$('div.active').removeClass('active');
$('div.two').addClass('active');
var left = ( parseInt($('div.absolute').css('left')) / $(window).width() ) * 100;
/* reset keyframes before animation */
clearKeyframes();
/* add new keyframes for when div.two is clicked */
keyframes.appendRule("0% { width: 15%; left: " + left + "%;}");
keyframes.appendRule("49.99% { width: 30%; left: " + left + "%; right: 70%;}");
keyframes.appendRule("50% { width: 30%; left: unset; right: 70%;}");
keyframes.appendRule("100% { width: 15%; right: 70%;}");
/* first animation - add animation class */
if (!($('div.absolute').hasClass('animateMovey'))) {
$('div.absolute').addClass('animateMovey');
/* animations after first - remove and reattach animation class with new keyframes */
} else {
$('div.absolute').removeClass('animateMovey');
$('div.absolute').addClass('animateMovey');
}
/* ensure animation occurs */
$('div.animateMovey').on('animationend', () => {
console.log('Animation ended');
})
}
})
$('div.three').click(() => {
if (!($('div.three').hasClass('active'))) {
$('div.active').removeClass('active');
$('div.three').addClass('active');
var left = ( parseInt($('div.absolute').css('left')) / $(window).width() ) * 100;
var width = 45 - left;
clearKeyframes();
keyframes.appendRule("0% { width: 15%; left: " + left + "%;}");
keyframes.appendRule("49.99% { width: " + width + "%; left: " + left + "%; right: 55%;}");
keyframes.appendRule("50% { width: " + width + "%; left: unset; right: 55%;}");
keyframes.appendRule("100% { width: 15%; right: 55%;")
if (!($('div.absolute').hasClass('animateMovey'))) {
$('div.absolute').addClass('animateMovey');
} else {
$('div.absolute').removeClass('animateMovey');
$('div.absolute').addClass('animateMovey');
}
$('div.animateMovey').on('animationend', () => {
console.log('Animation ended');
})
}
})
})
function findKeyframesRule(rule) {
var ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
for (var j = 0; j < ss[i].cssRules.length; ++j) {
if (ss[i].cssRules[j].type == window.CSSRule.KEYFRAMES_RULE && ss[i].cssRules[j].name == rule)
return ss[i].cssRules[j];
}
}
return null;
}
function clearKeyframes() {
for (var i = 0; i <= 3; ++i) {
if (keyframes[0]) {
var keyToRemove = keyframes[0].keyText;
keyframes.deleteRule(keyToRemove);
}
}
}
body {
margin: 0;
}
div.nav {
position: relative;
display: block;
overflow: hidden;
width: 100%;
}
div.nav div {
float: left;
width: 15%;
height: 75px;
}
div.nav div:hover {
opacity: 0.5;
}
div.one {
background-color: #7a7a7a;
}
div.two {
background-color: #9e9e9e;
}
div.three {
background-color: #bdbdbd;
}
.active {
box-shadow: inset 3px 5px 6px #000;
}
div.animateMovey {
animation-name: movey;
animation-duration: 0.6s;
animation-fill-mode: forwards;
animation-timing-function: ease-in-out;
}
div.relative {
position: relative;
width: 100%;
height: 20px;
}
div.absolute {
position: absolute;
background-color: #ff8c69;
width: 15%;
height: 100%;
}
#keyframes movey {
100% { }
}
<div>
<div class="nav">
<div class="one active"></div>
<div class="two"></div>
<div class="three"></div>
</div>
<div class="relative">
<div class="absolute"></div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
Interesting question. I'm not sure why the event is not re-triggering in this case, but will suggest a few changes to your approach:
Aim to animate transform and opacity instead of width and left, right
(https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance)
One way to do this is to use a separate red element under each box, and slide it left or right using transform
Use animation-delay to create the lengthening and shortening effect
Try to reuse the animation logic, so it will work regardless of the number of items.
The challenging part of this effect is managing the opacity of each line. I've used animationEnd to help with that, and it appears to work fine.
Additional comments in the example code. It could be improved by handling clicks while animation is active, consolidating animation functions etc. You could also vary the animation duration depending on the number of items.
let boxes = null;
let lines = null;
let fromIndex = 0;
let toIndex = 0;
const ANIMATION_DURATION = 0.1; // seconds
const animation = {
animating: false,
lines: [],
direction: "right",
inOrOut: "in"
};
function getEls() {
boxes = [...document.querySelectorAll(".box")];
lines = [...document.querySelectorAll(".line")];
}
function setAnimationDuration() {
lines.forEach((line) => {
line.style.animationDuration = `${ANIMATION_DURATION}s`;
});
}
function addEvents() {
boxes.forEach((box, index) => {
box.addEventListener("click", () => {
// User has clicked the currently active box
if (fromIndex === index) return;
// Line is currently animating
if (animation.animating) return;
toIndex = index;
updateActiveBox();
handleLineAnimation();
});
});
document.addEventListener("animationend", (e) => {
// Maintain opacity on lines that animate in
if (animation.inOrOut === "in") {
e.target.style.opacity = 1;
}
});
}
function updateActiveBox() {
boxes[fromIndex].classList.remove("active");
boxes[toIndex].classList.add("active");
}
function updateActiveLine(line) {
lines[fromIndex].classList.remove("active");
line.classList.add("active");
}
function handleLineAnimation() {
animation.animating = true;
animation.lines = [];
if (toIndex > fromIndex) {
animation.direction = "right";
for (let i = fromIndex; i <= toIndex; i++) {
animation.lines.push(lines[i]);
}
} else {
animation.direction = "left";
for (let i = fromIndex; i >= toIndex; i--) {
animation.lines.push(lines[i]);
}
}
animate();
}
function animate() {
const wait = (animation.lines.length - 1) * ANIMATION_DURATION * 1000;
animation.inOrOut = "in";
animateIn();
setTimeout(() => {
resetLine();
updateActiveLine(lines[toIndex]);
animation.inOrOut = "out";
animateOut();
setTimeout(() => {
resetLine();
onAnimationComplete();
}, wait);
}, wait);
}
function animateIn() {
const {
direction,
lines
} = animation;
lines.forEach((line, index) => {
// index = 0 is currently active, no need to animate in
if (index > 0) {
line.classList.add(`animate-in-${direction}`);
line.style.animationDelay = `${(index - 1) * ANIMATION_DURATION}s`;
}
});
}
function animateOut() {
const {
direction,
lines
} = animation;
lines.forEach((line, index) => {
// lines.length - 1 is new active, don't animate out
if (index < lines.length - 1) {
line.classList.remove(`animate-in-${direction}`);
line.classList.add(`animate-out-${direction}`);
line.style.animationDelay = `${index * ANIMATION_DURATION}s`;
}
});
}
function resetLine() {
const {
direction,
lines,
inOrOut
} = animation;
lines.forEach((line) => {
line.classList.remove(`animate-${inOrOut}-${direction}`);
line.style.animationDelay = null;
// After animating out, remove inline opacity
if (inOrOut === "out") {
if (!line.classList.contains("active")) {
line.style.opacity = "";
}
}
});
}
function onAnimationComplete() {
animation.animating = false;
fromIndex = toIndex;
}
function init() {
getEls();
setAnimationDuration();
addEvents();
}
function reset() {
fromIndex = 0;
init();
lines.forEach((line, index) => {
line.classList.remove('active');
line.style.opacity = "";
boxes[index].classList.remove('active');
});
boxes[0].classList.add("active");
lines[0].classList.add("active");
}
init();
// DEBUG
document.getElementById("debug").addEventListener("change", (e) => {
document.querySelector("nav").classList.toggle("debug-on");
});
document.getElementById("add").addEventListener("click", (e) => {
const div = document.createElement("div");
div.classList.add("box");
div.innerHTML = '<div class="new"></div><span class="line"></span>';
document.querySelector("nav").appendChild(div);
reset();
});
document.getElementById("remove").addEventListener("click", (e) => {
const indexToRemove = boxes.length - 1;
if (indexToRemove > 0) {
const box = boxes[indexToRemove];
box.parentNode.removeChild(box);
reset();
}
});
nav {
display: flex;
flex-wrap: wrap;
overflow: hidden;
}
.debug-on .line {
border: 1px solid;
box-sizing: border-box;
opacity: 0.2;
}
.box {
display: flex;
flex-direction: column;
position: relative;
float: left;
flex: 0 0 15%;
/* Allows the line to slide left or right with opacity: 1 */
overflow: hidden;
}
.box>div {
cursor: pointer;
height: 75px;
}
.one {
background-color: #7a7a7a;
}
.two {
background-color: #9e9e9e;
}
.three {
background-color: #bdbdbd;
}
.new {
background-color: pink;
border: 1px solid;
box-sizing: border-box;
}
.line {
background-color: #ff8c69;
height: 20px;
opacity: 0;
pointer-events: none;
width: 100%;
animation-fill-mode: forwards;
animation-timing-function: linear;
}
.active>div {
box-shadow: inset 3px 5px 6px #000;
}
.box:hover div {
opacity: 0.5;
}
.line.active {
opacity: 1;
}
.line.show {
opacity: 1;
}
.animate-in-right {
animation-name: SLIDE_IN_RIGHT;
}
.animate-out-right {
animation-name: SLIDE_OUT_RIGHT;
}
.animate-in-left {
animation-name: SLIDE_IN_LEFT;
}
.animate-out-left {
animation-name: SLIDE_OUT_LEFT;
}
#keyframes SLIDE_IN_RIGHT {
from {
opacity: 1;
transform: translateX(-100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
#keyframes SLIDE_OUT_RIGHT {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 1;
transform: translateX(100%);
}
}
#keyframes SLIDE_IN_LEFT {
from {
opacity: 1;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
#keyframes SLIDE_OUT_LEFT {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 1;
transform: translateX(-100%);
}
}
/* for demo only */
.debug {
background: #eee;
padding: 1rem;
display: inline-flex;
flex-direction: column;
font: 14px/1 sans-serif;
position: fixed;
bottom: 0;
right: 0;
}
.debug button {
margin-top: 1rem;
padding: .25rem;
}
<nav>
<div class="box active">
<div class="one"></div>
<span class="line active"></span>
</div>
<div class="box">
<div class="two"></div>
<span class="line"></span>
</div>
<div class="box">
<div class="three"></div>
<span class="line"></span>
</div>
</nav>
<br><br>
<div class="debug">
<label for="debug">Debug Lines <input type="checkbox" id="debug">
</label>
<button id="add">Add cell</button>
<button id="remove">Delete cell</button>
</div>
How to create an animation with rarity of it to appear together with animations that always appear?
Example here:
The number 1 has a rare chance to appear, let's say 40% chance. When it doesn't appear, it will start with 2 then after the animation of 2 is done 3 will start.
Should it 1 make it through the chance, it will appear and move up and after its animation is done, 2 will play and then after 2, 3 will play. So they just blend in as example.
2 and 3 will always appear, but 1 has a 40% for it to appear, as example. On 3 I've managed to put a random background chance on it. I've commented it on the code.
Should 1 not appear, then it should act like display: none. When I put display: none however on box1, the animation never starts, or it started but I can't see it, but I put it in the keyframes.
What I'm thinking is that, I guess it requires Javascript so it can change the animation-delay CSS property, I'm not sure though.
This is what I have so far: https://jsfiddle.net/edy3xjvz/2/
var box1 = document.getElementById("box1"); /* The one with the rarity */
var box2 = document.getElementById("box2");
var box3 = document.getElementById("box3"); /* Maybe give it a chance of which color */
var boxes = document.getElementById("boxes");
var box3Colors = {"blue": 90, "red": 50}; /* Blue has 90% chance and red has 50% not sure if done right, but what if I want to add a "green" with the chance of 90% too like "blue"??? */
/* Probably has to be done here, or when reaching a certain area, maybe with a button */
/*document.onload*/
var btn = document.getElementById("btn");
btn.addEventListener("click", startAnimation);
boxes.style.display = "none";
function randomizerWithChances(input) {
var array = [];
for(var item in input) {
if ( input.hasOwnProperty(item) ) {
for( var i=0; i<input[item]; i++ ) {
array.push(item);
}
}
}
return array[Math.floor(Math.random() * array.length)];
}
/* Start Animation */
function startAnimation() {
boxes.style.display = "none"; /* to replay but doesn't work*/
/* Do radomize stuff */
/* Don't really know for box1 */
/* I've tried box3, like that before
var random2 = box3Colors[Math.floor(Math.random() * box3Colors.length)]
box3.style.backgroundColor = random2;*/
box3.style.backgroundColor = randomizerWithChances(box3Colors);
/* Animation starts here */
boxes.style.display = "block";
}
#boxes {
}
.box {
display: inline-block;
position: relative;
height: 50px;
width: 50px;
margin-left: 20px;
}
#box1 {background: #00afe8;}
#box2 {background: green;}
#box3 {background: blue;}
#keyframes box1-up {
0% { top: 70px; position: relative; visibility: visible;}
100% {top: 0px; position: relative; visibility: visible;}
}
#keyframes blend {
0% { opacity: 0; }
100% { opacity: 1; }
}
#box1 {
top: 70px;
/* display: none; Can't start with this I wanted that when it isn't there, it should not appear but with display: none it didn't work because it can never appear then */
/*position: absolute; visibility: hidden;*/ /* So had to use this but when using this
it didn't work the box is somehow upside
https://i.imgur.com/3vER5ja.png so not sure */
animation: box1-up 2s;
animation-fill-mode: forwards;
}
#box2 {
opacity: 0;
animation: blend 3s;
animation-fill-mode: forwards;
animation-delay: 3s;
}
#box3 {
opacity: 0;
animation: blend 3s;
animation-fill-mode: forwards;
animation-delay: 5s;
}
<div id="boxes">
<div id="box1" class="box"></div>
<div id="box2" class="box"></div>
<div id="box3" class="box"></div>
</div>
<button id="btn" style="margin-top: 200px;">Start Animation</button>
Below there should be a button. What I've tried is to put a randomizer for box3 background color, seemed to work. I've tried to use box1 but display: none broke it.
I've tried to make something that when the animation doesn't start at all, that box2 is like not here, but when I use display: none the animation never starts not sure why.
https://jsfiddle.net/edy3xjvz/3/
Then I removed it so this is what you seen on the snippet above. https://jsfiddle.net/edy3xjvz/4/
Here's a quick PoC of how it could behave with our without box1 if I'm understanding your description correctly so that if the active class is there it will look as you have it, and if not then give the illusion equivalent of display: none, hope it helps, cheers.
const box1 = document.getElementById('box1');
toggleActive = () => {
let classes = box1.classList;
classes.contains('active') ? classes.remove('active') : classes.add('active');
}
.container {
display: inline-block;
outline: lightgray 1px dashed;
padding: 1rem;
margin: 1rem;
}
.container div {
display: inline-block;
height: 5rem;
width: 5rem;
background-color: lime;
opacity: 0;
animation: reveal 3s ease forwards;
}
.container div:nth-child(2) {
animation-delay: 1s;
}
.container div:nth-child(3) {
animation-delay: 2s;
}
.container div:not(:last-child) {
margin-right: 1rem;
}
#box1 {
height: 0;
width: 0;
margin: 0;
transform: translateY(6rem);
transition: transform 1s ease;
}
#box1.active {
height: 5rem;
width: 5rem;
margin-right: 1rem;
animation: revealUp 2s ease forwards;
}
#keyframes reveal {
to { opacity: 1 }
}
#keyframes revealUp {
to {
opacity: 1;
transform: translateY(0);
}
}
<div class="container">
<div id="box1" class="active"></div>
<div id="box2"></div>
<div id="box3"></div>
</div>
<br/>
<button onclick="toggleActive()">Toggle First One</button>
I think the animation handling, as in how it should be randomized and then what happens with the other animations, has to be kinda done manually but you can save the values or get them.
But that what I made is basically a basic concept and you can do even more stuff, but you have to adjust the delays and all that stuff.
This is the first concept:
https://jsfiddle.net/8z9obyLh/
Also you have to notice that once display is gone that the delay will start from there depending on which element just got out of display none.
The other one has a bit complex way but just look at it and how it's done.
Tbh, there should be another way to do this, which I think there is.
https://jsfiddle.net/d25kx6cj/5/
var box1 = document.getElementById("box1"); /* The one with the rarity */
var box2 = document.getElementById("box2");
var box3 = document.getElementById("box3"); /* Maybe give it a chance of which color */
var boxes = document.getElementById("boxes");
var box3Colors = {"blue": 90, "red": 50};
var btn = document.getElementById("btn");
btn.addEventListener("click", toggleAnimation);
boxes.classList.add("deactivated");
function randomizerWithChances(input) {
var array = [];
for(var item in input) {
if ( input.hasOwnProperty(item) ) {
for( var i=0; i<input[item]; i++ ) {
array.push(item);
}
}
}
/*console.log(array)
var randomizerValue = Math.floor(Math.random() * array.length);
console.log(randomizerValue)*/
return array[Math.floor(Math.random() * array.length)];
}
function propertyFromStylesheet(selector, attribute) {
var value;
[].some.call(document.styleSheets, function (sheet) {
return [].some.call(sheet.rules, function (rule) {
if (selector === rule.selectorText) {
return [].some.call(rule.style, function (style) {
if (attribute === style) {
value = rule.style.getPropertyValue(attribute);
return true;
}
return false;
});
}
return false;
});
});
return value;
}
var box1_defaultDurs = propertyFromStylesheet("#box1", "animation-duration");
var box2_defaultDur = parseFloat(propertyFromStylesheet("#box2", "animation-duration"));
var box4_defaultDur = parseFloat(propertyFromStylesheet("#box4", "animation-duration"));
var box3_defaultDurs = propertyFromStylesheet("#box3", "animation-duration");
var box1AppearChance = {no:6, yes:4} /* 40% Appear chance I guess*/
var box4AppearChance = {no:8, yes:2}
/*
defaultDurs.split(",").map(function(item) {
return item.trim();
});*/
var box1_defaultDur = box1_defaultDurs.split(",").map(function(item) {
return item.trim();
});
var box3_defaultDur = box3_defaultDurs.split(",").map(function(item) {
return item.trim();
});
var box1_defaultDurStart = parseFloat(box1_defaultDur[0]);
var box1_defaultDurEnd = parseFloat(box1_defaultDur[1]);
var box3_defaultDurStart = parseFloat(box3_defaultDur[0]);
var box3_defaultDurEnd = parseFloat(box3_defaultDur[1]);
var box3_delays = [];
function animationHandler() {
box3.style.backgroundColor = randomizerWithChances(box3Colors);
var box1Value = randomizerWithChances(box1AppearChance);
var box4Value = randomizerWithChances(box4AppearChance);
/*console.log(box1Value)*/
box3_delays[0] = "0s"; /* Put first delay value */
if (box1Value == "yes") {
box1.classList.add("active");
box2.style.animationDelay = box1_defaultDurStart + "s";
box3_delays[0] = box1_defaultDurStart + "s";
}
if (box1Value == "yes" || box4Value == "yes") {
box3_delays[0] = parseFloat(box3_delays[0]) + box2_defaultDur + "s";
}
/*box3.style.animationDelay = box3_defaultDurs.split(",").map(function(item) {
var itemTrimmed = item.trim();
return parseFloat(itemTrimmed) + box1_defaultDurStart + box2_defaultDur + "s";
});
}*/
/* Use this incase you have to summarize something with two delays, if it has 0s you might want to do something else or check if it's the first one in the array just to leave it alone. But in this case I didn't needed it */
/* box4.style.animationDelay = "0s"; To prevent NaN
Don't do this it it just breaks it just check it
*/
if (box4Value == "yes") {
box4.classList.add("active");
if ( isNaN(parseFloat(box2.style.animationDelay)) ) {
box4.style.animationDelay = box2_defaultDur + "s";
}
else if ( !isNaN(parseFloat(box2.style.animationDelay)) ) {
box4.style.animationDelay = parseFloat(box2.style.animationDelay) + box2_defaultDur + "s";
} /* box4 doesn't have a delay and we set one */
box3_delays[0] = parseFloat(box3_delays[0]) + box4_defaultDur + "s";
/* Delay of box3 is getting extended because of box4 when it appears */
}
if (box1Value == "yes" || box4Value == "yes") {
box3.style.animationDelay = [ parseFloat(box3_delays[0]) + "s", parseFloat(box3_delays[0]) + parseFloat(box3_defaultDurStart) + "s" ];
}
if (box1Value == "yes") {
if (box4Value == "no") {
box1.style.animationDelay = ["0s", box2_defaultDur + box3_defaultDurStart + box1_defaultDurStart + box3_defaultDurEnd + "s"]
}
else {
box1.style.animationDelay = ["0s", box2_defaultDur + box3_defaultDurStart + parseFloat(box4.style.animationDelay) + box1_defaultDurStart + box3_defaultDurEnd + "s"];
}
/* The + 2 is because of the box1_defaultDurStart which is needed */
/* And box3_defaultDurEnd also needed in this case */
}
}
function animationHandlerReset() {
box1.classList.remove("active");
box4.classList.remove("active"); /* And don't forget to remove the class at the end*/
/* Reset to default to stylesheet */
box1.style.removeProperty("animation-delay");
box2.style.removeProperty("animation-delay");
box3.removeAttribute("style"); /* or you could do this if you didn't give it any inline style by default */
box4.style.removeProperty("animation-delay");
}
function toggleAnimation() {
if (!boxes.classList.contains("deactivated")) {
animationHandlerReset();
boxes.classList.add("deactivated");
btn.innerHTML = "Start Animation";
}
else if (boxes.classList.contains("deactivated")) {
animationHandler();
boxes.classList.remove("deactivated");
btn.innerHTML = "Stop Animation"
}
}
#boxes {
}
.active {
display: inline-block!important;
}
.deactivated {
display: none!important;
/*visibility: hidden!important;*/
}
.box {
display: inline-block;
position: relative;
height: 50px;
width: 50px;
margin-left: 20px;
}
#box1 {background: #00afe8;}
#box2 {background: green;}
#box3 {background: blue;}
#box4 {background: orange;}
#keyframes box1-up {
0% { top: 70px;}
100% {top: 0px;}
}
#keyframes box1-down {
0% { top: 0px;}
100% {top: 70px; opacity: 0;}
}
#keyframes box4-anim {
0% { height: 50px; width: 50px; transform: scale(0.5) rotate(0deg); }
100% { height: 50px; width: 50px; transform: scale(1) rotate(180deg); }
}
#keyframes blend {
0% { opacity: 0; }
100% { opacity: 1; }
}
#box1 {
top: 70px;
display: none;
animation: box1-up 2s, box1-down 3s;
animation-fill-mode: forwards;
}
#box2 {
opacity: 0;
animation: blend 3s;
animation-fill-mode: forwards;
/*animation-delay: 3s;*/
}
#box3 {
opacity: 0;
animation: blend 3s, blend 4s reverse;
animation-fill-mode: forwards;
animation-delay: 3s, 6s; /* Both delays start together. Probably you want the other delay to be the twice as the size of the first one in this case for the end, but maybe not everytime */
}
#box4 {
display: none;
height: 0px;
width: 0px;
animation: box4-anim 1s;
animation-fill-mode: forwards;
}
<div id="boxes">
<div id="box1" class="box"></div>
<div id="box2" class="box"></div>
<div id="box4" class="box"></div>
<div id="box3" class="box"></div>
</div>
<button id="btn" style="margin-top: 200px;">Start Animation</button>
I'm trying to change the transition duration in-between the steps of a setTimeout loop.
When the button centre is clicked, setView changes the position of content so that the inner div remains in the centre of the screen. It uses a forEach loop to change the centring using an array of inner divs.
I'd like the transition between the change from alpha inner to bravo inner to take 1s, but once it gets there the transition should be 0s so that is no lag with the centring.
Here's a codepen, then built in is getting an error.
I'm using vanilla ES6.
const content = document.querySelector('.content');
let frame;
function getBounds(div) {
const element = document.getElementById(div);
const body = element.children[0];
const {
x, y, width, height,
} = body.getBoundingClientRect();
return box = {
element, body, x: Math.round(x), y: Math.round(y), width, height,
};
}
function setView(div) {
const centerW = window.innerWidth / 2;
const centerH = window.innerHeight / 2;
const { left, top } = content.getBoundingClientRect();
content.style.left = `${(centerW + left) - (div.x + div.width / 2)}px`;
content.style.top = `${(centerH + top) - (div.y + div.height / 2)}px`;
}
function centreDiv(string) {
const outers = string.split(' ');
outers.forEach(function(outer, index) {
setTimeout(function() {
content.style.transition = 'all 0s linear'; // this works
if (frame) {
cancelAnimationFrame(frame);
}
function tick(now) {
setView(getBounds(outer));
frame = requestAnimationFrame((timestamp) => tick(timestamp, outer));
}
frame = requestAnimationFrame((timestamp) => tick(timestamp, outer));
},
5000 * index);
content.style.transition = 'all 1s linear'; // this isn't working
});
}
document.getElementById('centre').onclick = () => centreDiv(centreText.value.toLowerCase());
* {box-sizing: border-box;}
html,body,div,span {padding: 0; margin: 0; border: 0;}
.controls {
position: absolute;
z-index: 1000;
padding-top: 10px;
padding-left: 20px;
font-family: sans-serif;
color: white;
}
.content {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height:100vh;
background-color: rgb(138,141,143);
transition: all 0s linear;
}
.centre {
position: absolute;
display: block;
width: 5px;
height: 5px;
background-color: white;
}
.outer {
position: absolute;
display: flex;
align-items: center;
justify-content: flex-end;
}
.inner {
display: block;
width: 30px;
height: 30px;
background-color: rgb(37,40,42);
}
.animate {
animation: spin infinite linear;
}
#alpha {
width: 80%;
height: 80%;
background-color: rgb(255,205,0);
animation-duration: 10s;
}
#bravo {
width: 30%;
height: 30%;
background-color: rgb(242,169,0);
animation-duration: 5s;
}
#keyframes spin {
100% {
transform: rotateZ(360deg);
}
}
<div class="controls">
centre: <input id="centreText" type="text" value="alpha bravo"/><button type="button" id="centre">centre</button>
</div>
<div class="content">
<div id="alpha" class="animate outer">
<div class="inner"></div>
</div>
<div id="bravo" class="animate outer">
<div class="inner"></div>
</div>
<div class="centre"></div>
</div>
I am using JavaScript to toggle notification like below.
How can I add transition between display: block and display: none;
I don't want to add an external library like jQuery because I am only going to be using the toggle effect alone.
var btn = document.querySelector('button');
btn.addEventListener('click', function(){
var hint = document.getElementById('hint');
if(hint.style.display == 'none'){
hint.style.display = 'block';
}
else{
hint.style.display = 'none';
}
});
div#hint{
background: gold;
color: orangered;
padding: .5em;
font-weight: bold;
}
<div id='hint'>
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button> show hint </button>
I know I can use jQuery to achieve this like below.
$(document).ready(function(){
$('button').click(function(){
$('#hint').toggle('slow');
});
});
div#hint{
background: gold;
color: orangered;
padding: .5em;
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='hint'>
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button> show hint </button>
Can I make the button moves up and down gradually while the #hint is being toggle like in the jQuery example above? I don't want the button to jump from one position to another.
#vothaison's suggestion: CSS transitions
Technically, #vothaison wanted to use setInterval as opposed to setTimeout, but I don't see the need for that. It's just more work.
var hint = document.getElementById('hint');
var btn = document.getElementById('btn_show');
btn.addEventListener('click', function(){
var ctr = 1;
hint.className = hint.className !== 'show' ? 'show' : 'hide';
if (hint.className === 'show') {
hint.style.display = 'block';
window.setTimeout(function(){
hint.style.opacity = 1;
hint.style.transform = 'scale(1)';
},0);
}
if (hint.className === 'hide') {
hint.style.opacity = 0;
hint.style.transform = 'scale(0)';
window.setTimeout(function(){
hint.style.display = 'none';
},700); // timed to match animation-duration
}
});
#hint {
background: yellow;
color: red;
padding: 16px;
margin-bottom: 10px;
opacity: 0;
transform: scale(0);
transition: .6s ease opacity,.6s ease transform;
}
<div id="hint" style="display: none;">
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button id="btn_show"> Show hint </button>
Using CSS animations
var hint = document.getElementById('hint');
var btn = document.getElementById('btn_show');
btn.addEventListener('click', function(){
hint.className = hint.className !== 'show' ? 'show' : 'hide';
if (hint.className === 'show') {
setTimeout(function(){
hint.style.display = 'block';
},0); // timed to occur immediately
}
if (hint.className === 'hide') {
setTimeout(function(){
hint.style.display = 'none';
},700); // timed to match animation-duration
}
});
#-webkit-keyframes in {
0% { -webkit-transform: scale(0) rotate(12deg); opacity: 0; visibility: hidden; }
100% { -webkit-transform: scale(1) rotate(0); opacity: 1; visibility: visible; }
}
#keyframes in {
0% { transform: scale(0) rotate(12deg); opacity: 0; visibility: hidden; }
100% { transform: scale(1) rotate(0); opacity: 1; visibility: visible; }
}
#-webkit-keyframes out {
0% { -webkit-transform: scale(1) rotate(0); opacity: 1; visibility: visible; }
100% { -webkit-transform: scale(0) rotate(-12deg); opacity: 0; visibility: hidden; }
}
#keyframes out {
0% { transform: scale(1) rotate(0); opacity: 1; visibility: visible; }
100% { transform: scale(0) rotate(-12deg); opacity: 0; visibility: hidden; }
}
#hint {
background: yellow;
color: red;
padding: 16px;
margin-bottom: 10px;
}
#hint.show {
-webkit-animation: in 700ms ease both;
animation: in 700ms ease both;
}
#hint.hide {
-webkit-animation: out 700ms ease both;
animation: out 700ms ease both;
}
<div id="hint" style="display: none;">
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button id="btn_show"> Show hint </button>
Using vanilla JavaScript
There are many, many ways to do this sort of thing with vanilla JavaScript, so here's a quick sketch of one way:
// you may need to polyfill requestAnimationFrame
var hint = document.getElementById('hint');
var btn = document.getElementById('btn_show');
btn.addEventListener('click', function(){
var ctr = 0;
hint.className = hint.className !== 'show' ? 'show' : 'hide';
if (hint.className === 'show') {
window.setTimeout(function(){
hint.style.display = 'block';
fadein();
},0); // do this asap
}
if (hint.className === 'hide') {
fadeout();
window.setTimeout(function(){
hint.style.display = 'none';
},700); // time this to fit the animation
}
function fadein(){
hint.style.opacity = ctr !== 10 ? '0.'+ctr : 1;
hint.style.transform = ctr !== 10 ? 'scale('+('0.'+ctr)+')' : 'scale(1)';
ctr++;
if (ctr < 11)
requestAnimationFrame(fadein);
else
ctr = 0;
}
function fadeout(){
hint.style.opacity = 1 - ('0.'+ctr);
hint.style.transform = 'scale('+(1 - ('0.'+ctr))+')';
ctr++;
if (ctr < 10)
requestAnimationFrame(fadeout);
else
ctr = 0;
}
});
#hint {
background: yellow;
color: red;
padding: 16px;
margin-bottom: 10px;
opacity: 0;
}
<div id="hint" style="display: none;">
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button id="btn_show"> Show hint </button>
Say what you want about GreenSock, Velocity.js, jQuery, etc — they all trivialise this process of showing and hiding of things. Why not just borrow the show and hide functions from jQuery's source code?
see my example below:
var btn = document.querySelector('button');
var hint = document.getElementById('hint');
var height = hint.clientHeight;
var width = hint.clientWidth;
console.log(width + 'x' + height);
// initialize them (within hint.style)
hint.style.height = height + 'px';
hint.style.width = width + 'px';
btn.addEventListener('click', function(){
if(hint.style.visibility == 'hidden'){
hint.style.visibility = 'visible';
//hint.style.opacity = '1';
hint.style.height = height + 'px';
hint.style.width = width + 'px';
hint.style.padding = '.5em';
}
else{
hint.style.visibility = 'hidden';
//hint.style.opacity = '0';
hint.style.height = '0';
hint.style.width = '0';
hint.style.padding = '0';
}
});
div#hint{
background: gold;
color: orangered;
padding: .5em;
box-sizing: border-box;
overflow: hidden;
font-weight: bold;
transition: height 1s, width 1s, padding 1s, visibility 1s, opacity 0.5s ease-out;
}
<div id='hint'>
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button> show hint </button>
Hi I dont use display: block to display:none but changing the opacity, height and padding instead
please review this one:
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
var hint = document.getElementById('hint');
if (hint.classList.contains('h-hide')) {
hint.classList.remove('h-hide');
} else {
hint.classList.add('h-hide');
}
});
div#hint {
display: block;
background: gold;
color: orangered;
padding: .5em;
font-weight: bold;
transition: .5s all linear;
opacity: 1;
overflow: hidden;
height: 100px;
}
#hint.h-hide {
padding: 0;
opacity: .25;
height: 0;
}
<div id='hint'>
<p>This is some hint on how to be safe in this community</p>
<p>This is another hint on how to be safe in this community</p>
</div>
<button>show hint</button>
the drawback for this approach is we have to keep tract of the div#hint height and change it using javascript if needed.
var btn = document.querySelector('button');
btn.addEventListener('click', function(){
var hint = document.getElementById('hint');
if(hint.style.visibility == 'hidden'){
hint.style.visibility = 'visible';
hint.style.opacity = '1';
}
else{
hint.style.visibility = 'hidden';
hint.style.opacity = '0';
}
});
div#hint{
background: gold;
color: orangered;
padding: .5em;
font-weight: bold;
transition: visibility 1s, opacity 0.5s linear;
}
<div id='hint'>
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button> show hint </button>
I think using visibility over display is better option
Without using css3 transition, you can use js setInterval to change some css property of the div, such as:
Change opacity from 0 to 1
Change height from 0 to full height
Change width from 0 to full width
Initially, you should have display: none; opacity: 0; height: 0; width: 0'
Then you have to change display: none to display: block; before you use setInterval to change other properties.
(I guess you know how to hide the div)
You can also use setTimeout(), with a trick of recursive.
Try something like this:
var btn = document.querySelector('button');
btn.addEventListener('click', function(){
var hint = document.getElementById('hint');
hint.classList.toggle("hide");
});
.hint{
background: gold;
color: orangered;
padding: .5em;
font-weight: bold;
visibility: visible;
opacity: 1;
max-height: 500px;
transition: visibility 0s, opacity 0.3s, max-height 0.6s linear;
}
.hide {
visibility: hidden;
opacity: 0;
max-height: 0px;
transition: max-height 0.3s, opacity 0.3s, visibility 0.3s linear;
}
<div id='hint' class="hint">
<p>This is some hint on how to be safe in this community </p>
<p>This is another hint on how to be safe in this community </p>
</div>
<button> show hint </button>
I have also tried to do this
please have a look if it can help you
var btn = document.querySelector('button');
var hint = document.getElementById('hint');
hint.style.opacity = 1;
hint.style.transition = "opacity 1s";
btn.addEventListener('click', function(){
if(hint.style.opacity == 0 || hint.style.opacity==''){
hint.style.opacity = 1;
}
else{
hint.style.opacity = 0;
}
});
let redBox = document.getElementById('redBox');
let blueBox = document.getElementById('blueBox');
let [redButton, blueButton] = document.querySelectorAll('button'); //Destructuring
redButton.addEventListener('click', () => {
smoothDisplayNone(redBox);
});
blueButton.addEventListener('click', () => {
smoothDisplayNone(blueBox);
});
//By using smoothDisplayNone() function, you can add this effect to whatever element you want.
function smoothDisplayNone(selectedElement){
if(!selectedElement.classList.contains('animationDisplayNone')){
selectedElement.classList.add('animationDisplayNone');
selectedElement.classList.remove('animationDisplayBlock');
}
else{
selectedElement.classList.remove('animationDisplayNone');
selectedElement.classList.add('animationDisplayBlock');
}
}
#redBox{
width: 200px;
height: 200px;
background-color: red;
}
#blueBox{
width: 200px;
height: 200px;
background-color: blue;
}
.animationDisplayNone{
animation: smoothDisplayNone 0.5s linear forwards;
}
.animationDisplayBlock{
animation: smoothDisplayBlock 0.5s linear forwards;
}
/*You should set the width and height according to the size of your element*/
#keyframes smoothDisplayBlock{
0% { opacity: 0; width: 0px; height: 0px; }
25% { opacity: 0.25; }
50% { opacity: 0.50; }
75% { opacity: 0.75; }
100% { opacity: 1; width: 200px; height: 200px; }
}
#keyframes smoothDisplayNone {
0% { opacity: 1; width: 200px; height: 200px; }
25% { opacity: 0.75; }
50% { opacity: 0.50; }
75% { opacity: 0.25; }
100% { opacity: 0; width: 0px; height: 0px; }
}
<div id="redBox"></div>
<div id="blueBox"></div>
<button type="button" style="margin-top:10px;">Red</button>
<button type="button" style="margin-top:10px;">Blue</button>
The code looks long at first glance but it is actually very simple to understand. I used the power of css animation to create a smooth effect.
You can use smoothDisplayNone() function easily.