How do you achieve VueJS-like control over transitions using vanilla JavaScript?
The goal is to make an element have display: none; when it's hidden, but maintain the ability to fade it in/out.
I managed to make it fade out by applying a class with an opacity of 0 and then listening for the transitionend event to swap the class with a hide class that sets display: none;. That part actually seems to work well...
However, when trying to do the inverse, it seems to fail. Instead of tranitioning in; it ignores the transition entirely and just appears with full opacity and never fires a transitionend event.
const btnShow = document.querySelector('#btnShow');
const btnHide = document.querySelector('#btnHide');
const div = document.querySelector('div');
btnShow.addEventListener('click', () => {
console.log('btnShow clicked');
div.classList.remove('hide');
div.classList.add('div-enter');
div.classList.replace('div-enter', 'div-enter-to');
div.addEventListener('transitionend', function() {
console.log('show transition ended');
div.classList.remove('div-enter-to');
}, { once: true });
});
btnHide.addEventListener('click', () => {
console.log('btnHide clicked');
div.classList.add('div-leave');
div.classList.replace('div-leave', 'div-leave-to');
div.addEventListener('transitionend', () => {
console.log('hide transition ended');
div.classList.replace('div-leave-to', 'hide');
}, { once: true });
});
.hide {
display: none;
opacity: 0;
}
.div-enter {
opacity: 0;
}
.div-enter-to {
opacity: 1;
}
.div-leave {
opacity: 1;
}
.div-leave-to {
opacity: 0;
}
/* Irrelevant styles */
html, body {
height: 100vh;
width: 100vw;
margin: 0;
padding: 1em 1.5em;
font-size: 100%;
}
div {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 1em 0;
padding: 3rem 0;
width: 50%;
background: #4bfa;
border-radius: 5px;
transition: all 1s linear;
}
button {
border: none;
border-radius: 2px;
}
button:hover {
cursor: pointer;
}
<button id="btnShow">show</button>
<button id="btnHide">hide</button>
<div>
fade out, then set display: none<br />
then do the reverse...
</div>
The hide class should be written like:
.hide {
visibility: hidden;
opacity: 0;
}
CSS transitions and animations allow you to animate a specific set of CSS properties. You cannot animate the display property.
Source: https://www.w3.org/TR/css-transitions-1/#animatable-properties
Use animations instead of transitions , working example of your changed code provided below.
const btnShow = document.querySelector('#btnShow');
const btnHide = document.querySelector('#btnHide');
const div = document.querySelector('div');
btnShow.addEventListener('click', () => {
div.classList.remove('hide');
div.classList.add('fade-in');
div.addEventListener('animationend', function() {
div.classList.remove('fade-in');
}, { once: true });
});
btnHide.addEventListener('click', () => {
console.log('btnHide clicked');
div.classList.add('fade-out');
div.addEventListener('animationend', () => {
div.classList.replace('fade-out', 'hide');
}, { once: true });
});
#keyframes fade-out {
0% {opacity: 1;}
100% {opacity: 0;}
}
#keyframes fade-in {
100% {opacity: 1;}
0% {opacity: 0;}
}
.hide { display: none; }
.fade-out {animation-name: fade-out; animation-duration: 1s; }
.fade-in {animation-name: fade-in; animation-duration: 1s; }
/* Irrelevant styles */
html, body {
height: 100vh;
width: 100vw;
margin: 0;
padding: 1em 1.5em;
font-size: 100%;
}
div {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 1em 0;
padding: 3rem 0;
width: 50%;
background: #4bfa;
border-radius: 5px;
transition: all 1s linear;
}
button {
border: none;
border-radius: 2px;
}
button:hover {
cursor: pointer;
}
<button id="btnShow">show</button>
<button id="btnHide">hide</button>
<div>
fade out, then set display: none<br />
then do the reverse...
</div>
I was able to get it working by wrapping the enter animations in a setTimeout(() => {}, 0);
I don't fully understand why this works, so I'd appreciate comments explaining it. I had tried putting them in a requestAnimationFrame() but that didn't seem to have the same effect.
If anyone knows a better or cleaner way to accomplish this, I'd love to see it.
const btnShow = document.querySelector('#btnShow');
const btnHide = document.querySelector('#btnHide');
const div = document.querySelector('div');
btnShow.addEventListener('click', () => {
console.log('btnShow clicked');
div.classList.replace('hide', 'div-enter');
setTimeout(() => {
div.classList.replace('div-enter', 'div-enter-to');
div.addEventListener('transitionend', function() {
console.log('show transition ended');
div.classList.remove('div-enter-to');
}, { once: true });
}, 0);
});
btnHide.addEventListener('click', () => {
console.log('btnHide clicked');
div.classList.add('div-leave');
div.classList.replace('div-leave', 'div-leave-to');
div.addEventListener('transitionend', () => {
console.log('hide transition ended');
div.classList.replace('div-leave-to', 'hide');
}, { once: true });
});
.hide {
display: none;
opacity: 0;
}
.div-enter {
opacity: 0;
}
.div-enter-to {
opacity: 1;
}
.div-leave {
opacity: 1;
}
.div-leave-to {
opacity: 0;
}
/* Irrelevant styles */
html, body {
height: 100vh;
width: 100vw;
margin: 0;
padding: .5em 1em;
font-size: 100%;
}
div {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 1em 0;
padding: 2rem 0;
width: 50%;
background: #4bfa;
border-radius: 5px;
transition: all 1s linear;
}
button {
border: none;
border-radius: 2px;
}
button:hover {
cursor: pointer;
}
<div>
fade out, then set display: none<br />
then do the reverse...
</div>
<button id="btnShow">show</button>
<button id="btnHide">hide</button>
Related
I have 2 <hr> elements that are animated and move into close to each other. I want the animation to take place once the <hr> elements are in the viewport, instead of occurring right when the page is loaded. I have included my JavaScript code but this code didn't work; not sure if I'm using the right event.
'use strict';
const linebreak = document.querySelector('#hero--one');
const symbolContainer = document.querySelector('.header__container-symbol')
linebreak.addEventListener('scroll', function(e) {
e.preventDefault();
const containerCoords = symbolContainer.getBoundingClientRect();
symbolContainer.scrollIntoView({
behavior: 'smooth'
});
linebreak.classList.remove('hidden');
})
/* Lines and Diamond above title */
.header__container-symbol {
display: flex;
justify-content: center;
align-content: center;
background-color: rgba(25, 25, 25, 1);
}
.span-D {
align-self: center;
}
.hr-symbol {
width: 60rem;
display: inline-block;
height: 0.3rem;
background-color: #eee;
}
#hero--one {
margin-right: 3px;
animation: moveInLeftHr;
animation-duration: 2s;
}
#symbol {
font-size: 3rem;
}
#hero--two {
margin-left: 3px;
animation: moveInRightHr;
animation-duration: 2s;
}
#symbol {
color: #eee;
margin-right: 2rem;
margin-left: 2rem;
}
.hidden {
display: none;
}
/* End Diamon and Lines above title */
/* KeyFrames Animations */
#keyframes moveInLeftHr {
0% {
opacity: 0;
transform: translateX(-100px);
}
100% {
opacity: 1;
transform: translate(0);
}
}
#keyframes moveInRightHr {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translate(0);
}
}
<div class="header__container-symbol">
<span class="span-D"><hr class="hidden hr-symbol" id="hero--one"/></span>
<span id="symbol"> ⬙ </span>
<span class="span-D"><hr class="hidden hr-symbol" id="hero--two"/></span>
</div>
You can use the Intersection Observer API to detect when the element intersects with the viewport.
The threshold option is set to 1, meaning it will check for when the element is fully within the viewport.
A test div has been inserted at the top of the html to demonstrate the behavior.
'use strict';
const linebreak1 = document.querySelector('#hero--one');
const linebreak2 = document.querySelector('#hero--two');
const symbolContainer = document.querySelector('.header__container-symbol')
const observer = new IntersectionObserver((entries, observer) => {
if (!entries[0].isIntersecting) return;
linebreak1.classList.remove('hidden');
linebreak2.classList.remove('hidden');
}, { threshold: 1 });
observer.observe(symbolContainer);
/* Lines and Diamond above title */
.header__container-symbol {
display: flex;
justify-content: center;
align-content: center;
background-color: rgba(25, 25, 25, 1);
}
.span-D {
align-self: center;
}
.hr-symbol {
width: 60rem;
display: inline-block;
height: 0.3rem;
background-color: #eee;
}
#hero--one {
margin-right: 3px;
animation: moveInLeftHr;
animation-duration: 2s;
}
#symbol {
font-size: 3rem;
}
#hero--two {
margin-left: 3px;
animation: moveInRightHr;
animation-duration: 2s;
}
#symbol {
color: #eee;
margin-right: 2rem;
margin-left: 2rem;
}
.hidden {
display: none;
}
/* End Diamon and Lines above title */
/* KeyFrames Animations */
#keyframes moveInLeftHr {
0% {
opacity: 0;
transform: translateX(-100px);
}
100% {
opacity: 1;
transform: translate(0);
}
}
#keyframes moveInRightHr {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translate(0);
}
}
<div style="height: 500px;"></div>
<div class="header__container-symbol">
<span class="span-D"><hr class="hidden hr-symbol" id="hero--one"/></span>
<span id="symbol"> ⬙ </span>
<span class="span-D"><hr class="hidden hr-symbol" id="hero--two"/></span>
</div>
I am trying to trigger the menu icon off when clicked, and trigger on an 'x' icon. Likewise, I need the reverse of this to happen. Trigger the 'x' icon off when clicked, and the menu icon back on.
When nav-button-menuOpen is clicked, the three divs inside go from opacity: 1; to opacity: 0; and the button fades away like it's supposed to. Then the span inside nav-button-menuClose is supposed to go from opacity: 0; to opacity: 1;. I can see the class being added in the browser, but the animation on the span doesn't change the opacity property.
const mobileMenuOpenAndClose = () => {
const menuOpenButton = document.querySelector('.nav-button-menuOpen');
const menuCloseButton= document.querySelector('.nav-button-menuClose');
const navMobileMenu = document.querySelector('.nav-mobileMenu');
const navMobileMenuLinks = document.querySelectorAll('.nav-mobileMenu-links');
menuOpenButton.addEventListener('click', () => {
console.log('menuButtonOpen clicked');
menuOpenButton.classList.toggle('nav-button-toggle');
menuCloseButton.classList.toggle('nav-button-toggle');
});
menuCloseButton.addEventListener('click', () => {
console.log('menuButtonClose clicked')
menuCloseButton.classList.toggle('nav-button-toggle');
menuOpenButton.classList.toggle('nav-button-toggle');
});
};
mobileMenuOpenAndClose();
/* Hamburger Menu */
nav .nav-button-menuOpen {
justify-self: end;
cursor: pointer;
display: inline-block;
opacity: 1;
}
nav .nav-button-menuOpen div {
width: 30px;
height: 30px;
height: 3px;
margin: 5px 0;
background-color: var(--color-black);
transition: all;
}
nav .nav-button-menuClose {
justify-self: end;
cursor: pointer;
display: inline-block;
opacity: 0;
}
nav .nav-button-menuClose span {
color: var(--color-black);
font-size: 1rem;
transition: all;
}
nav .nav-button-toggle .menuOpen-line {
opacity: 0;
}
nav .nav-button-toggle .nav-button-menuClose {
opacity: 1;
}
<nav>
<div class="nav-button-menuOpen">
<div class="menuOpen-line"></div>
<div class="menuOpen-line"></div>
<div class="menuOpen-line"></div>
</div>
<div class="nav-button-menuClose">
<span>x</span>
</div>
</nav>
Writing the css with the space in the middle like this: .nav-button-toggle .nav-button-menuClose applys the style to the elements children. Write .nav-button-toggle.nav-button-menuClose without the space in the middle to apply it to itself.
const mobileMenuOpenAndClose = () => {
const menuOpenButton = document.querySelector('.nav-button-menuOpen');
const menuCloseButton= document.querySelector('.nav-button-menuClose');
const navMobileMenu = document.querySelector('.nav-mobileMenu');
const navMobileMenuLinks = document.querySelectorAll('.nav-mobileMenu-links');
menuOpenButton.addEventListener('click', () => {
console.log('menuButtonOpen clicked');
menuOpenButton.classList.toggle('nav-button-toggle');
menuCloseButton.classList.toggle('nav-button-toggle');
});
menuCloseButton.addEventListener('click', () => {
console.log('menuButtonClose clicked')
menuCloseButton.classList.toggle('nav-button-toggle');
menuOpenButton.classList.toggle('nav-button-toggle');
});
};
mobileMenuOpenAndClose();
/* Hamburger Menu */
nav .nav-button-menuOpen {
justify-self: end;
cursor: pointer;
display: inline-block;
opacity: 1;
}
nav .nav-button-menuOpen div {
width: 30px;
height: 30px;
height: 3px;
margin: 5px 0;
background-color: black;
transition: all;
}
nav .nav-button-menuClose {
justify-self: end;
cursor: pointer;
display: inline-block;
opacity: 0;
}
nav .nav-button-menuClose span {
color: black;
font-size: 1rem;
transition: all;
}
nav .nav-button-toggle .menuOpen-line {
opacity: 0;
}
nav .nav-button-toggle.nav-button-menuClose {
opacity: 1;
}
<nav>
<div class="nav-button-menuOpen">
<div class="menuOpen-line"></div>
<div class="menuOpen-line"></div>
<div class="menuOpen-line"></div>
</div>
<div class="nav-button-menuClose">
<span>x</span>
</div>
</nav>
For the animation part I would recommand you to use the CSS #keyframes Rule (https://www.w3schools.com/cssref/css3_pr_animation-keyframes.asp) instead of transiton. This way you can easily apply a hide class with display: none; to the hidden elements, and if you remove the class, the animation gets triggered.
const mobileMenuOpenAndClose = () => {
const menuOpenButton = document.querySelector('.nav-button-menuOpen');
const menuCloseButton= document.querySelector('.nav-button-menuClose');
const navMobileMenu = document.querySelector('.nav-mobileMenu');
const navMobileMenuLinks = document.querySelectorAll('.nav-mobileMenu-links');
menuOpenButton.addEventListener('click', () => {
console.log('menuButtonOpen clicked');
menuOpenButton.classList.toggle('hidden');
menuCloseButton.classList.toggle('hidden');
});
menuCloseButton.addEventListener('click', () => {
console.log('menuButtonClose clicked')
menuCloseButton.classList.toggle('hidden');
menuOpenButton.classList.toggle('hidden');
});
};
mobileMenuOpenAndClose();
/* Hamburger Menu */
.nav-button-menuOpen {
cursor: pointer;
display: inline-block;
animation: fadeIn 0.2s;
}
.nav-button-menuOpen div {
width: 30px;
height: 30px;
height: 3px;
margin: 5px 0;
background-color: black;
}
.nav-button-menuClose {
cursor: pointer;
display: inline-block;
animation: fadeIn 0.2s;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
#keyframes fadeIn {
from { opacity: 0 }
to { opacity: 100 }
}
.nav-button-menuClose span {
color: black;
font-size: 2rem;
}
.hidden {
display: none
}
<div class="nav-button-menuOpen">
<div class="menuOpen-line"></div>
<div class="menuOpen-line"></div>
<div class="menuOpen-line"></div>
</div>
<div class="nav-button-menuClose hidden">
<span>x</span>
</div>
This is because you are adding nav-button-toggle class to the .nav-button-menuClose element, but in your css selector you trying apply opacity if this element's parent has nav-button-toggle class:
nav .nav-button-toggle .nav-button-menuClose {
(basically you have an extra space between classes in this selector)
const mobileMenuOpenAndClose = () => {
const menuOpenButton = document.querySelector('.nav-button-menuOpen');
const menuCloseButton= document.querySelector('.nav-button-menuClose');
const navMobileMenu = document.querySelector('.nav-mobileMenu');
const navMobileMenuLinks = document.querySelectorAll('.nav-mobileMenu-links');
menuOpenButton.addEventListener('click', () => {
console.log('menuButtonOpen clicked');
menuOpenButton.classList.toggle('nav-button-toggle');
menuCloseButton.classList.toggle('nav-button-toggle');
});
menuCloseButton.addEventListener('click', () => {
console.log('menuButtonClose clicked')
menuCloseButton.classList.toggle('nav-button-toggle');
menuOpenButton.classList.toggle('nav-button-toggle');
});
};
mobileMenuOpenAndClose();
/* Hamburger Menu */
nav .nav-button-menuOpen {
justify-self: end;
cursor: pointer;
display: inline-block;
opacity: 1;
}
nav .nav-button-menuOpen div {
width: 30px;
height: 30px;
height: 3px;
margin: 5px 0;
background-color: var(--color-black);
transition: all;
}
nav .nav-button-menuClose {
justify-self: end;
cursor: pointer;
display: inline-block;
opacity: 0;
}
nav .nav-button-menuClose span {
color: var(--color-black);
font-size: 1rem;
transition: all;
}
nav .nav-button-toggle .menuOpen-line {
opacity: 0;
}
nav .nav-button-toggle.nav-button-menuClose {
opacity: 1;
}
<nav>
<div class="nav-button-menuOpen">
<div class="menuOpen-line">menu1</div>
<div class="menuOpen-line">menu2</div>
<div class="menuOpen-line">menu3</div>
</div>
<div class="nav-button-menuClose">
<span>x</span>
</div>
</nav>
As of animation, since you are only changing opacity, there nothing else to animate as there are no other changes applied to the style between the states.
You can achieve with single .nav-menu-toggle element to open and close menu with single click event of JavaScript.
document.querySelector('.nav-menu-toggle').addEventListener('click', (t) => {
t.target.classList.toggle('active');
});
/* Hamburger Menu */
:root{
--color-black : #000
}
nav{
background-color: #f2f2f2;
padding: 20px;
}
nav .nav-menu-toggle {
width: 30px;
height: 30px;
display: block;
cursor: pointer;
overflow: hidden;
}
nav .nav-menu-toggle div{
width: 100%;
height: 3px;
margin-bottom: 10px;
background-color: #000;
transition: 0.5s transform cubic-bezier(0.075, 0.82, 0.165, 1);
pointer-events: none;
}
nav .nav-menu-toggle.active div{
background-color: #ff0000;
}
nav .nav-menu-toggle.active div:nth-child(1){
transform: rotate(-45deg);
transform-origin: 110% 9px;
}
nav .nav-menu-toggle.active div:nth-child(2){
transform: scale(0);
}
nav .nav-menu-toggle.active div:nth-child(3){
transform: rotate(45deg);
transform-origin: 95% -4.5px;
}
<nav>
<div class="nav-menu-toggle">
<div></div>
<div></div>
<div></div>
</div>
</nav>
Can someone help me with toggle method? Please see below my code
My problem is that by pressing on image toggle adds class of active, thus adding some CSS properties to it, however by consecutive pressing on the same image it doesn't remove class of active from it.
I cannot remove removeActiveClass function, because it has to go through NodeList and check which one has class of active and remove it, thus adding active only to the image that was clicked.
const panels = document.querySelectorAll('.panel');
panels.forEach((panel) => {
panel.addEventListener('click', () => {
removeActiveClasses();
panel.classList.toggle('active');
});
});
function removeActiveClasses() {
panels.forEach(panel => {
panel.classList.remove('active');
});
}
CSS CODE
#import url('https://fonts.googleapis.com/css?family=Muli&display=swap');
* {
box-sizing: border-box;
}
body {
font-family: 'Muli', sans-serif;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
overflow: hidden;
}
.container {
display: flex;
width: 90vw;
}
.panel {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
height: 80vh;
border-radius: 50px;
color: white;
cursor: pointer;
flex: 0.5;
margin: 10px;
position: relative;
transition: flex 0.7s ease-in;
}
.panel h3 {
font-size: 24px;
position: absolute;
bottom: 20px;
left: 20px;
margin: 0;
opacity: 0;
}
.panel.active {
flex: 5;
}
.panel.active h3 {
opacity: 1;
transition: opacity 0.3s ease-in 0.6s;
}
#media(max-width: 768px) {
.container {
width: 100vw;
}
.panel:nth-of-type(4),
.panel:nth-of-type(5) {
display: none;
}
}
Thanks in advance :)
Currently you just remove the active class from all your panels and then toggle the active class on the current panel so it will always get set to active (as the active class was just removed). You can try to check to see if the current panel already has the active class and if not then add the class after you call the remove:
panels.forEach((panel) => {
panel.addEventListener('click', () => {
const isActive = panel.classList.contains('active');
removeActiveClasses();
if (!isActive) {
panel.classList.add('active');
}
});
});
I have a plus sign that appears if you push the space button. But now it appears once. Can you help me to make it appear every time I press the space button? Here is my Code Pen.
import './style.scss';
let counter = 0;
document.addEventListener('keydown', ({ keyCode }) => {
const increment = document.getElementsByClassName('increment')[0];
if (keyCode === 32) {
counter++;
document.getElementsByClassName('counter')[0].innerText = counter;
increment.classList.remove('hidden');
increment.classList.add('move-increment');
}
});
.container {
/* ... */
.counter {
background-color: gray;
color: white;
border-radius: 10px;
padding: 10px;
}
.move-increment {
margin-top: -20px;
opacity: 0;
}
.hidden {
visibility: hidden;
}
.increment {
position: absolute;
margin-left: -33px;
z-index: 1;
transition: margin-top 1s cubic-bezier(0, 0.5, 0.5, 1),
opacity 1s ease-in-out;
}
}
Although I consider #ikiK's answer as the correct answer because the question was specifically about using CSS transitions, I would like to share a different approach. I think the goal of the 'plus' icon is to be displayed each time the counter increments. But when the counter increments while the transition of the previous increment is still playing it is impossible to display a second 'plus' symbol.
My suggestion would be to use some jQuery and, on each increment, append a new li item to an unordered list that is positioned right on top of the counter. Animate that li, fading it out to the top. And then use the callback function of animate() to remove the li element from the DOM once it has faded out of view.
let counter = 1;
$(document).on( 'keypress',function(e) {
if( e.which == 32 ) {
$('.counter').text(counter++);
let increment = $('<li><span class="increment">+</span></li>');
$('#increments').append(increment);
increment.animate({
opacity: 0,
top: '-=30px'
}, 500, function() {
increment.remove();
});
}
});
.container {
font-family: Arial, Helvetica, sans-serif;
font-size: 40px;
font-weight: bold;
display: flex;
height: 500px;
align-items: top;
justify-content: center;
position: relative;
overflow: hidden;
height: 100px;
}
.counter {
background-color: gray;
color: white;
border-radius: 10px;
padding: 10px;
}
#increments {
padding: 0px;
z-index: 1;
float: left;
margin-left: -33px;
list-style: none;
}
#increments li {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<p>Counter: <span class="counter">0</span></p>
<ul id="increments"></ul>
</div>
Remove added .move-increment and add again removed hidden classes with slight delay, this will re-apply your transition: margin-top (read in provided links why delay):
setTimeout(function() {increment.classList.add('hidden');
increment.classList.remove('move-increment');}, 600);
Solution (changed key-code to arrow up: ↑ ):
let counter = 0;
document.addEventListener('keydown', ({
keyCode
}) =>
{
const increment = document.getElementsByClassName('increment')[0];
if (keyCode === 38) {
counter++;
document.getElementsByClassName('counter')[0].innerText = counter;
increment.classList.remove('hidden');
increment.classList.add('move-increment');
setTimeout(function() {
increment.classList.add('hidden');
increment.classList.remove('move-increment');
}, 600);
}
});
.container {
font-family: Arial, Helvetica, sans-serif;
font-size: 40px;
font-weight: bold;
display: flex;
height: 100px;
align-items: center;
justify-content: center;
}
.counter {
background-color: gray;
color: white;
border-radius: 10px;
padding: 10px;
}
.move-increment {
margin-top: -20px;
opacity: 0;
}
.hidden {
visibility: hidden;
}
.increment {
position: absolute;
margin-left: -33px;
z-index: 1;
transition: margin-top 1s cubic-bezier(0, 0.5, 0.5, 1), opacity 1s ease-in-out;
}
<div class="container">
<div>
Counter: <span class="counter">0</span>
<span class="increment hidden">+</span>
</div>
</div>
But however, this is not working perfectly when pressing key too fast. Try changing setTimeout duration and see what suits your need.
In links provided you have examples how to reset animation (not transition) all together and would solve this fast key press issues.
Read about this here, few really useful info's:
Restart CSS Animation
Controlling CSS Animations and Transitions with JavaScript
I think you don't need to hidden class, Simply you can use setTimeout for reset class, like this:
let counter = 0;
document.addEventListener("keydown", ({ keyCode }) => {
const increment = document.getElementsByClassName("increment")[0];
if (keyCode === 32) {
counter++;
document.getElementsByClassName("counter")[0].innerText = counter;
increment.classList.add("move-increment");
setTimeout(function () {
increment.classList.remove("move-increment");
}, 1000);
}
});
.container {
font-family: Arial, Helvetica, sans-serif;
font-size: 40px;
font-weight: bold;
display: flex;
height: 500px;
align-items: center;
justify-content: center;
}
.container .counter {
background-color: gray;
color: white;
border-radius: 10px;
padding: 10px;
}
.container .increment {
position: absolute;
margin-left: -33px;
z-index: 1;
visibility: hidden;
margin-top: 0;
opacity: 1;
transition: margin-top 1s cubic-bezier(0, 0.5, 0.5, 1), opacity 1s ease-in-out;
}
.container .increment.move-increment {
visibility: visible;
margin-top: -20px;
opacity: 0;
}
<div class="container">
<div>
Counter: <span class="counter">0</span>
<span class="increment">+</span>
</div>
</div>
I just got off support with ActiveCampaign and they said they couldn't provide me with code examples on how to add their modal pop-up forms to be triggered by wordpress buttons.
I found a few resources online but they are all slightly different than the functionality I'm looking for.
I already added the ActiveCampaign plugin to my wordpress site and there are two options of embedding the form within the site.
shortcode "[activeCampaign formId=1]" or
<script src="https://exampledomain.com/f/embed.php?id=1" type="text/javascript" charset="utf-8"></script>
I'm currently using the divi theme, and the buttons have sections for CSS ID's and CSS Classes.
so to summarize, I would like to be able to click a button and have the activecampaign modal form popup.
If you could show me how I can add code to the button and my site to trigger the modal popup that'd be amazing.
Let me know if you have any other information.
Thanks!
Sugesstion:
This involves DOM manipulation. create a css class called active which should be set to the form container to show. Here's an example:
var formToggle = document.getElementById("form-toggler");
var formContainer = document.querySelector(".form-container");
formToggle.addEventListener('click', function(){
// When you click the button, first check if the form is open
// so that you know if you should close or open
if(formContainer.classList.contains("active")){
// Form is currently open, because it has active as one of it's classes
// so remove active to hide it.
formContainer.classList.remove("active");
}else{
// Form is currently closed, because it does not have active as one of it's classes
// so add active to show it.
formContainer.classList.add("active");
}
});
.form-container{
width: 100%;
height: 100%;
min-height: 200px;
background-color: rgba(0,0,0,0.2);
display: none;
}
/* When form has active class, set display to block */
.form-container.active{
display: block !important;
}
<div class="form-container">
<!-- your Form Here -->
<h1>Yey, form is active!!</h1>
</div>
<button id="form-toggler">OpenForm<button>
This is just at the basic level of approaching your scenario. So you've got to work on your css to make your Modal cover the entire window and add a close button on it in case someone decides to close it.
Hey this should work for you also. Bear in mind there is some extra code you probably wont need all of it such as the animations but I will leave these in as they make the modal look a little slicker. You won't need bootstrap or any additional libraries for this code.
HTML:
<a id="gdx-lighbox-modal-unique-1" data-hover="true" type="button" class="gdx-lightbox-tooltip-open-modal lightbox-link gdx-lightbox-button" data-open="gdx-lighbox-modal-1">
Click Here
</a>
<div class="gdx-modal" id="gdx-lighbox-modal-1" data-animation="slideInOutLeft">
<div class="gdx-modal-dialog">
<header class="gdx-modal-header">
<a class="gdx-close-modal" aria-label="close modal" data-close="">✕</a>
</header>
<section class="gdx-modal-content">
//Form would go here instead of the image (image just an example)
<img src="https://gdxdesigns.com/wp-content/uploads/2020/11/little-frog.jpg"> </section>
<footer class="gdx-modal-footer"> <h3 class="gdx-modal-image-title"></h3></footer>
</div>
</div>
CSS:
/* RESET RULES
–––––––––––––––––––––––––––––––––––––––––––––––––– */
:root {
--lightgray: #efefef;
--blue: steelblue;
--white: #fff;
--black: rgba(0, 0, 0, 0.8);
--bounceEasing: cubic-bezier(0.51, 0.92, 0.24, 1.15);
}
* {
padding: 0;
margin: 0;
}
.gdx-body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
font: 16px/1.5 sans-serif;
}
.lightbox-link, a.lightbox-link {
cursor: pointer;
margin-left: 2.5%;
}
.gdx-lightbox-tooltip-open-modal img {
width: 20px;
height: 20px;
}
.gdx-lightbox-button {
padding: 10px 20px;
background: #000;
padding: 10px 20px;
font-weight: normal;
border: 2px solid #000;
color: #94c93b;
}
.gdx-lightbox-button:hover {
background: #FFF;
color: #000;
}
/* MODAL
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.gdx-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: var(--black);
cursor: pointer;
visibility: hidden;
opacity: 0;
transition: all 0.35s ease-in;
z-index: 9999 !important;
}
.gdx-modal.is-visible {
visibility: visible;
opacity: 1;
}
.gdx-modal-dialog {
position: relative;
max-width: 100vw;
max-height: 100vh;
border-radius: 5px;
background: var(--white);
overflow: auto;
cursor: default;
margin-top: 5%;
}
.gdx-modal-dialog > * {
padding: 1rem;
}
.gdx-modal-header,
.gdx-modal-footer {
background: #FFF;
}
.gdx-modal-header .gdx-close-modal {
font-size: 1.5rem;
}
.gdx-modal-header a {
font-size: 2em;
}
.gdx-modal-content {
text-align: center;
}
.gdx-modal-content img {
margin: 0 !important;
}
.gdx-close-modal {
float: right;
cursor: pointer;
}
.gdx-modal p + p {
margin-top: 1rem;
}
.gdx-modal-image-title {
text-align: center;
font-size: 1em;
margin: 0;
}
/* ANIMATIONS
–––––––––––––––––––––––––––––––––––––––––––––––––– */
[data-animation] .gdx-modal-dialog {
opacity: 0;
transition: all 0.5s var(--bounceEasing);
}
[data-animation].is-visible .gdx-modal-dialog {
opacity: 1;
transition-delay: 0.2s;
}
[data-animation="slideInOutDown"] .gdx-modal-dialog {
transform: translateY(100%);
}
[data-animation="slideInOutTop"] .gdx-modal-dialog {
transform: translateY(-100%);
}
[data-animation="slideInOutLeft"] .gdx-modal-dialog {
transform: translateX(-100%);
}
[data-animation="slideInOutRight"] .gdx-modal-dialog {
transform: translateX(100%);
}
[data-animation="zoomInOut"] .gdx-modal-dialog {
transform: scale(0.2);
}
[data-animation="rotateInOutDown"] .gdx-modal-dialog {
transform-origin: top left;
transform: rotate(-1turn);
}
[data-animation="mixInAnimations"].is-visible .gdx-modal-dialog {
animation: mixInAnimations 2s 0.2s linear forwards;
}
[data-animation="slideInOutDown"].is-visible .gdx-modal-dialog,
[data-animation="slideInOutTop"].is-visible .gdx-modal-dialog,
[data-animation="slideInOutLeft"].is-visible .gdx-modal-dialog,
[data-animation="slideInOutRight"].is-visible .gdx-modal-dialog,
[data-animation="zoomInOut"].is-visible .gdx-modal-dialog,
[data-animation="rotateInOutDown"].is-visible .gdx-modal-dialog {
transform: none;
}
#keyframes mixInAnimations {
0% {
transform: translateX(-100%);
}
10% {
transform: translateX(0);
}
20% {
transform: rotate(20deg);
}
30% {
transform: rotate(-20deg);
}
40% {
transform: rotate(15deg);
}
50% {
transform: rotate(-15deg);
}
60% {
transform: rotate(10deg);
}
70% {
transform: rotate(-10deg);
}
80% {
transform: rotate(5deg);
}
90% {
transform: rotate(-5deg);
}
100% {
transform: rotate(0deg);
}
}
/* Backend Instructions
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.lightbox-instructions-heading {
font-size: 1.8em !important;
}
.lightbox-instructions strong {
font-size: 1.2em !important;
}
.gdx-lightbox-tooltip-open-modal img {
margin: -0.3em;
margin-left: 0.25em;
}
xmp {
white-space: normal;
}
.lightbox-tooltip-instructions-content xmp{
margin-bottom: 2em;
}
Javascript
const openEls = document.querySelectorAll("[data-open]");
const closeEls = document.querySelectorAll("[data-close]");
const isVisible = "is-visible";
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".gdx-modal.is-visible")) {
document.querySelector(".gdx-modal.is-visible").classList.remove(isVisible);
}
});
document.addEventListener("keyup", e => {
// if we press the ESC
if (e.key == "Escape" && document.querySelector(".gdx-modal.is-visible")) {
document.querySelector(".gdx-modal.is-visible").classList.remove(isVisible);
}
});
JSFiddle Example can be seen here.
If you want to download this as a Wordpress Plugin (free of course) you can do so here
If you want to see the a demo of the plugin in action with the button modal popup you see this here