CSS styles not applying immediately when class toggled with jQuery - javascript

I am trying to change the style of an element while some javascript is running in the background, to indicate that the page is 'busy' or 'loading'. I've been attempting to do this by toggling a class on at the start of the JS script with jQuery's .toggleClass(), and toggling it off at the end, with some suitable CSS styling attached to the class.
Although the class is toggled immediately, the CSS styling attached to it doesn't apply until after the JS has finished executing, however. So if the class is toggled both on and off, the user does not see any change in style.
I've included a simple example below. How can I force the change in CSS styling to apply immediately, before the rest of the JS code executes?
$(function() {
$('#box').click(function() {
// Toggle class 'red' on.
$(this).toggleClass('red');
// Do something that takes time.
for (i = 0; i < 10000; i++) {
console.log(i);
}
// Toggle class 'red' off.
$(this).toggleClass('red');
});
});
.wrapper {
margin: 15px;
text-align: center;
color: #000;
}
#box {
margin: 15px 0;
width: 100px;
height: 100px;
line-height: 100px;
cursor: pointer;
background: #ccc;
border: solid 3px #ccc;
-webkit-transition: all .3s linear 0s;
transition: all .3s linear 0s;
}
#box.red {
background: #f43059;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div id="box">Click me.</div>
</div>

The problem is that your "something that takes time" is synchronous and blocking - while it's operating, browser repainting will be disabled.
One option would be to listen to a transitionend event, to ensure that the animation to red completes before the resource-intensive operation runs.
So that the removal of .red animates properly too, you could set a setTimeout right after the heavy operations finish. Note that your code will a bit be clearer if you use addClass and removeClass instead of toggleClass:
$('#box').click(function() {
$(this).one('transitionend', () => {
// Do something that takes time.
for (i = 0; i < 1000; i++) {
console.log(i);
}
// Toggle class 'red' off.
setTimeout(() => {
$(this).removeClass('red');
});
});
$(this).addClass('red');
});
.wrapper {
margin: 15px;
text-align: center;
color: #000;
}
#box {
margin: 15px 0;
width: 100px;
height: 100px;
line-height: 100px;
cursor: pointer;
background: #ccc;
border: solid 3px #ccc;
-webkit-transition: all .3s linear 0s;
transition: all .3s linear 0s;
}
#box.red {
background: #f43059;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div id="box">Click me.</div>
</div>

Related

Reliable way to fade in new element

In my app, I create a new element every second. They should be faded in as they are created. Here's how I did it:
window.setInterval(() => {
const element = document.createElement('div');
element.classList.add('dot', 'hidden');
document.getElementById('content').appendChild(element);
element.classList.remove('hidden');
}, 1000);
.dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: rgba(255, 255, 255, .7);
margin: 0 5px 5px 0;
transition: opacity 1s;
}
.hidden {
opacity: 0;
}
I thought this would make the transition work, but it doesn't. I also tried to remove the hidden class after a delay, like this:
setTimeout(() => element.classList.remove('hidden'), 100);
It's interesting, because it works only if the delay is long enough. If I set it to 10 ms, some dots are faded in and other appear instantly.
Is there a better, simple and reliable way to make it work, without guessing the delay of setTimeout()?
You could use CSS animations and avoid using JavaScript if it's a simple fade-in you want. This will only run when the DOM node is painted to the screen. If you need more advanced control over your animations then you can tap into JS.
#keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: rgba(255, 255, 255, .7);
margin: 0 5px 5px 0;
animation: 1s linear fadein;
}
Css transition works best when you hide things.
So here is a solution using javascript only.
window.setInterval(() => {
const element = document.createElement('div');
element.classList.add('dot');
document.getElementById('content').appendChild(element);
fade(element,0.1);
}, 1000);
// This will inc opacity by 0.1 each 100ms
function fade(item, i){
i += 0.1;
item.style.opacity = i;
if (i< 1)
setTimeout(()=> fade(item,i), 100);
}
.dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: rgba(255, 255, 255, .7);
margin: 0 5px 0px 5px;
}
#content {
background:black;
}
<div id="content"></div>
Your original problem makes sense because you are adding the hidden class and removing it inside the same function in your setTimeout block. Remember that the browser gets a chance to rerender only after your function completes executing. This means in the line where you add your hidden class is not useful at all because it is immediately removed later in the function (before the browser can do a render).
I would recommend inserting your DOM element and resetting it using a setTimeout or requestAnimationFrame. Also because the timing of setInterval and setTimeout are not fully dependable, it may make sense to use the use the transitionend event of one element to trigger the addition of the next.
(function() {
const container = document.getElementById('container');
let count = 0;
function addElement() {
const div = document.createElement('div');
div.innerHTML = 'item ' + ++count
container.append(div);
div.classList.add('item')
div.addEventListener('transitionend', addElement)
setTimeout(function() {
div.classList.add('show')
}, 0)
}
addElement()
})()
div {}
div.item {
opacity: 0;
transition: opacity 1s;
}
div.item.show {
opacity: 1;
}
<hr>
<div id="container">
</div>

Replicate CSS Animation on ::after using jquery

So I've done a lot of searching, and can't seem to find another question that helps. However I feel like this might be a common problem so could be a duplicate, anyway.
I have a really nice CSS Underline animation going on when I hover a piece of text, you can see the example here:
let options = ['Over Here', 'This Way', 'At Me']
$(document).ready(function () {
let jobInterval = setInterval(() => changeText(), 3000)
})
function changeText () {
let newText = options[Math.floor(Math.random() * options.length)]
$('#change').text(newText)
}
#change {
display: inline-block;
position: relative;
padding-bottom: 3px;
}
#change:after {
content: '';
display: block;
margin: auto;
height: 3px;
width: 0px;
background: transparent;
transition: width 3s ease, background-color 3s ease;
}
#change:hover:after {
width: 100%;
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h1>
<span>Look </span>
<span id="change">Over Here</span>
</h1>
</div>
https://jsfiddle.net/6b2cqrj7/7/
However, I don't want it to be done when I hover. The idea is that I have a piece of text that changes every X seconds, and I want the underline to animate for X seconds then the word switches and the animation plays again. Like a little timer bar.
I have tried all sorts, but because I can't manipulate the ::after tag using JS/JQuery I can't work out how to get this working.
Because the animation is happening on the ::after tag, I can't just add a new class I need to change the value of the CSS so the transition will apply. I think.
This is my first real attempt at using CSS animations so I'm quite stumped here.
Use CSS3 keyframes to get the desired result.
let options = ['Over Here', 'This Way', 'At Me']
$(document).ready(function () {
let jobInterval = setInterval(() => changeText(), 3000)
})
function changeText () {
let newText = options[Math.floor(Math.random() * options.length)]
$('#change').text(newText)
}
#change {
display: inline-block;
position: relative;
padding-bottom: 3px;
}
#change:after {
content: '';
display: block;
margin: auto;
height: 3px;
width: 0px;
background: transparent;
animation: myLine 3s ease infinite;
transition: width 3s ease, background-color 3s ease;
}
#change:hover:after {
width: 100%;
background: red;
}
#keyframes myLine{
from {width: 0; background: transparent;}
to {width: 100%; background: red;}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h1>
<span>Look </span>
<span id="change">Over Here</span>
</h1>
</div>
Hope this helps

Queue on click function

I am pretty new to jQuery and I am having a bit of difficulty adapting to it being a Java nerd.
I am trying to make these 3 boxes so that when you click one of them, it comes forward and the two in the back dim and stay there, in the back. The problem is that, I want to make it so when you click more than 1 box consecutively, the second box clicked doesn't come forward until the animation ends, much like a queue of box clicks. Right now it's all mixed up and the dimming is fine but the boxes come forward as soon as I click them and not when they should.
I tried callbacks and deferred to no avail.
Here is the code:
Javascript:
var zindex = 1;
$('.box_listener').click(function() {
$(this).css('z-index', zindex += 1);
$(this).siblings('.box_listener').fadeTo(3000, 0.5);
$(this).fadeTo(1, 1);
});
Here is the JSFiddle:
https://jsfiddle.net/asger/5yvvgoda/14/
var zindex = 1;
$('.box_listener').click(function() {
$(this).css('z-index', zindex += 1);
$(this).siblings('.box_listener').fadeTo(3000, 0.5);
$(this).fadeTo(1, 1);
});
#backgroundbox {
position: absolute;
width: 400px;
height: 200px;
background-color: #E5E8E8;
z-index: -5;
border-style: solid;
border-width: 1px;
}
.box_listener {
position: absolute;
width: 120px;
height: 120px;
background-color: white;
border-style: solid;
border-width: 1px;
}
#redbox {
left: 270px;
top: 20px;
border-color: red;
z-index: 0;
}
#bluebox {
left: 230px;
top: 60px;
border-color: blue;
z-index: 0;
}
#greenbox {
left: 210px;
top: 77px;
border-color: lightgreen;
z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="backgroundbox">
<div class="box_listener" id="redbox">
</div>
<div class="box_listener" id="bluebox">
</div>
<div class="box_listener" id="greenbox">
</div>
</div>
Cheers and thanks!
A more bulletproof approach is to not use jQuery animations at all and instead use CSS transitions. The reason for this is twofold; CSS transitions can be automatically reversed and they can be GPU accelerated. It also means you don't have to artificially wait for the transition to complete before allowing user input.
To accomplish this, just set up two CSS classes; One that tells the elements you're going to animate how they should transition. The other class changes the values on the element, which causes the transition to happen. Then all jQuery needs to do is addClass() and removeClass() in order to cause the transitions to occur.
Below is an example of it in action. I've highlighted the most important aspects with comments.
$('.btn').on('click', function() {
// remove the active class from all buttons,
// this will reverse the transition
$('.btn').removeClass('active');
// apply it to only the current button clicked,
//this will start the transition
$(this).addClass('active');
});
.btn {
display: block;
width: 200px;
padding: 10px 20px;
margin-bottom: 5px;
background: cornflowerblue;
border: 0;
cursor: pointer;
/* set up a transition on any css transformations like
translating, scaling, rotating, etc. */
transition: transform 300ms ease-in-out;
}
/* when this class is added to the button it will scale it, but the
transition already on the button will make sure it happens slowly */
.active {
transform: scale(1.2);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>Click the buttons</h2>
<button class="btn">First</button>
<button class="btn">Second</button>
<button class="btn">Third</button>

Can't change arrows on accordion

I am trying to replicate the accordion from W3C. My project is spread across 4 folders: CSS, FONTS, IMAGES and SCRIPT. The problem is that JS doesn't want to toggle the background images. Here is my code.
CSS
.accordion {
width: 100%;
height: 34px;
border-style: none;
border-bottom: 1px solid #515151;
background-color: #000;
color: #fff;
text-align: left;
padding: 10px;
font-family: Tahoma;
font-size: 12px;
background-image: url(../IMAGES/arrow-right.png);
background-repeat: no-repeat;
background-position: 330px 15px;
transition: 0.4s;
cursor: pointer;
}
JS
for (i = 0; i < accordion.length; i++) {
accordion[i].onclick = function() {
this.nextElementSibling.classList.toggle('show');
if (this.textContent === 'Click to open') {
this.textContent = 'Click to close';
} else {
this.textContent = 'Click to open';
}
if (this.style.backgroundImage === 'url(../IMAGES/arrow-right.png)') {
this.style.backgroundImage = 'url(../IMAGES/arrow-down.png)';
} else {
this.style.backgroundImage = 'url(../IMAGES/arrow-right.png)';
}
}
}
I think it would be easier to just make one arrow image. and add/remove classes on arrow with jquery/js and making a rotate transform: rotate(90deg); / transition like transition: all 0.5s ease-in-out; for it to rotate.
I figured it out. I put the images in the main folder and everythimg is working propperly now.

How to set when waypoints triggers

I am trying to figure out how I can trigger a waypoints function. Right now this function starts when my info div is at the very top of the screen. Ideally, I want this to start when the user's bottom of the screen just gets to the info-box section. I am unsure of how I can even modify when the event triggers.
Also, for some reason the info-boxes aren't transitions to the right like I am attempting. They just transition into a fade with now horizontal movement. What is wrong with what I am trying?
var $info_show = $('#info');
$info_show.waypoint(function () {
$('.info-box').addClass("fadeShow");
});
#info {
max-width: 100%;
height: auto;
padding: 300px 20%;
}
.info-box {
border: 1px solid black;
padding: 50px;
background: #00f;
color: #fff;
display: inline;
margin: 0 100px;
transition: 1s;
opacity: 0;
}
.info-box.fadeShow {
opacity: 1;
transform: translateX(150px);
}
<script src="https://leaverou.github.io/prefixfree/prefixfree.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="info">
<div class="info-box">Red</div>
<div class="info-box">Blue</div>
<div class="info-box">Green</div>
</div>

Categories

Resources