CSS transition not working after element is appended - javascript

I have encountered an issue with CSS transitions and before I try something else, I want to understand what the problem is.
There are 3 boxes in a container and a "next" button. The goal is for the next box top appear on top and to fade in when the "next" button is pressed. The box is positioned on top by appending it to the container, so that it is added as the last element and thus visible on top, and should fade in through css transition.
The problem is that the css transition does not seem to work after the box was appended.
The css transition works well if tested on a box element that is not appended.
Fiddle here, code below:
HTML:
<div class="container">
<div class="box red"></div>
<div class="box blue"></div>
<div class="box green"></div>
</div>
<div id="next">Next</div>
JS:
var container = $(".container");
// Save the current list order
var list = container.children(".box");
// The current index
var current = 0;
// Put the first on top
container.append(list[0]);
function next() {
// Figure out what is the index of the next box
if (current + 1 < list.length) current++;
else current = 0;
// Save it in a variable
var target = $(list[current]);
// Put it on top
container.append(target);
// Hide it and then fade it in
target.css("opacity", 0).css("transition", "opacity 1000ms ease-out").css("opacity", 1);
// The fading in is not working
}
$("#next").click(next);
Update:
A basic solution to this problem was to call offset() on the target after setting the opacity to 0 and before setting the transition css:
target.css("opacity", 0);
target.offset();
target.css("transition", "opacity 1000ms ease-out").css("opacity", 1);
Updated version of the above fiddle here

The "list" variable is a jQuery object, but the elements you pull out of it as "target" are not jQuery objects - they're the DOM nodes. Thus your calls to ".css()" are failing (which is reported in the error console for me).
Once you've fixed that, then the next thing is the issue of how the browser deals with a sequence of CSS updates. It's not clear to me what exactly I'm seeing (from Firefox 18 on Linux), but I think the basic issue is that because no layout reflow is done between the changes, the net effect is that the styles are "collapsed" so that there's no net change.
In this update to the fiddle I took a different approach. I put the transition rules in the "box" class, and then added a "prefade" class:
.prefade {
transition-duration: 0;
opacity: 0;
}
Then, instead of messing with the element style, I add "prefade" before appending, and then trigger a layout update by asking for the element's offset. Then I can remove the "prefade" class, and the box fades in.
target.addClass('prefade');
// Put it on top
container.append(target);
var p = target.offset();
target.removeClass('prefade');
I don't know whether that's the "right" way to do things. edit — to make it work in Chrome, the "transition" properties need to be repeated with the -webkit- prefix.

Is there any particular reason to use CSS transitions, when you could use jQuery fades instead and have your code work on browsers that don't support CSS transitions?
var $container = $(".container");
function next() {
var $next = $container.children().first();
$next.hide().appendTo($container).fadeIn();
}
$("#next").click(next);
Note that you can avoid a lot of the state you're maintaining merely by taking the first element from the container and moving it to the back - the DOM maintains your state for you!
See http://jsfiddle.net/alnitak/w6y3B/

Related

Prevent viewport from moving when removing DOM elements

I'm trying to implement an HTML infinite scroller in which at any given time there are only a handful of div elements on list (to keep the memory footprint small).
I append a new div element to the list and at the same time I'm removing the first one, so the total count of divs remains the same.
Unfortunately the viewport doesn't stay still but instead it jumps backwards a little bit (the height of the removed div actually).
Is there a way to keep the viewport still while removing divs from the list?
I made a small self contained HTML page (well, it still needs JQuery 3.4.1) which exposes the problem: it starts by adding 5 divs and then it keeps adding a new one and removing the first one every 1 second
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function removeit() {
// remove first one
var tiles = $(".tile");
$(tiles[0]).remove();
}
function addit() {
// append new one
var jqueryTextElem = $('<div class="tile" style="height:100px;background-color:' + getRandomColor() + '"></div>');
$("#inner-wrap").append(jqueryTextElem);
}
function loop() {
removeit();
addit();
window.setTimeout(loop, 1000);
}
addit();
addit();
addit();
addit();
addit();
loop();
<div id="inner-wrap"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
You can temporarily add position: fixed to the parent element:
first add position: fixed to the parent;
then remove the item;
then remove position: fixed from the parent
I have a feeling you're trying to have your cake and eat it, in that if you get the viewport to be "still", I think you're meaning you don't want a user to see the scrollbar move and then also not have any new affordance to scroll further down the page, because you would want the scrollbar thumb/grabber to still sit at the bottom of the scrollbar track?
I mean, you could just use $(window).scrollTop($(window).scrollTop() + 100); in your example to make it so the scroll position of the viewport won't visually move when removing elements, but at that point, you wouldn't be keeping the users view of the current elements the same or even allowing a user to have new content further down the page to scroll towards. You'd just be "pushing up" content through the view of the user?
If you are trying to lighten the load of what is currently parsed into document because you are doing some heavy lifting on the document object at runtime, maybe you still want to remove earlier elements, but retain their geometry with some empty sentinel element that always has the height of all previously removed elements added to it? This would allow you to both have a somewhat smaller footprint (though not layout-wise), while still having a usable scrollbar that can communicate to a user and both allow a user to scroll down, towards the content that has been added in.
All in all, I think what you currently have is how most infinite scrollers do and should work, meaning the scroll position and scrollbar should change when content is added in the direction the user is scrolling towards, this communicates to them that they can in fact keep scrolling that way. You really shouldn't want the viewports scroll position to be "still".
To see more clearly why I don't think you have an actual issue, replace your loop() definition with something like this...
function loop() {
$(window).scroll(function() {
// check for reaching bottom of scroller
if ($(window).scrollTop() == ($(document).height() - $(window).height())) {
addit();
removeit();
}
})
}

Toggling marginTop without making an element disappear

I've thrown together a cool little script that will make my search box appear using jQuery UI. However, there are links above the search box that must move up at the same speed as well. For this, the margin-top must be adjusted, but by toggling the margin-top, it seems it is disappearing.
Does anyone know how I can toggle the margin-top without making the links disappear AND keep the speed as close as possible to the other one?
$(document).ready(function() {
$('.pwcustomsearch').hide();
$("#pwcustomsearchlink").click(function () {
var effect = 'slide';
var options = { direction: 'down' };
var duration = 400;
$('.pwcustomsearch').toggle(effect, options, duration);
$('.social-media').toggle({"marginTop": "15px"});
})
});
Here is a fiddle:
http://jsfiddle.net/hcmLw/1030/
.toggle() is adding display:none as an inline style to your element, therefore it disappears.
Use .animate() instead to change the top margin.
See my updated fiddle here: http://jsfiddle.net/hcmLw/1032/
EDIT: Updated the fiddle again to make the toggling work properly.

Transition not executing properly

I am trying to execute a transition for an image gallery. When the gallery transitions to the next slide, I will put it just to the left of the current slide, and slide it over the current slide by transitioning on its left style.
My current markup:
<section id="nd-gallery">
<div class="nd-slide nd-slide-current">
slide 1
</div>
<div class="nd-slide nd-slide-next">
slide 2
</div>
</section>
Relevant CSS:
#nd-gallery{
position:relative;
height:100px;
width:100px;
}
#nd-gallery > .nd-slide{
position:absolute;
height:100px;
height:100px;
outline:1px solid red;
left:-40px;
}
#nd-gallery > .nd-slide.nd-slide-current{
z-index:5;
left:0;
}
So, by default, I offset slides by moving them left by 40px (obviously I will hide them better outside of this demo with a larger offset)
Now, with some script, I want to:
Apply a new left value to the next slide to offset it (for the purpose of this demo, 50px which offset the next slide over the current slide by half)
Assign the appropriate transition properties
Assign a new left value so that the next slide will slide over the current slide
SCRIPT:
var next = document.querySelector('.nd-slide-next');
next.style.left = '-50px';
next.style.transitionProperty = 'left';
next.style.transitionDuration = '5s';
next.style.left = '0';
However, when I run this, the next slide appears on top of the current slide without a transition:
http://jsfiddle.net/ACdc7/4/
Note: Apparently Firefox performs the transition properly. In Chrome v.32 on Windows 7, the transition is not performed. Can anyone point out if I've done something incorrect, or perhaps I'm dealing with a bug in Chrome?
Like it happens with many UI frameworks that execute all UI operations in only one thread (Windows Forms, WPF ...), most layout operations are blocked while user code is executing. Why? Well, while Javascript code is executing, the browser is tipically unresponsive, because its UI thread is occupied processing your code. So, if you change the value of the same property multiple times before relinquishing control to the browser, only the last value remains. It would be a waste of processing power to go through a layout process when you change, let's say, the left value of an element, when maybe you can change it later in the same event handler. So, the browser just creates a queue of pending layout operations an performs them when it has nothing else to do.
Applied to your case, the browser adds to the queue the operation {next.style, 'left', -50px}. When you update left again, the browser doesn't add a new operation. As there is one pending that does the same, it just updates it. So, to the browser, you aren't changing the left property from -50px to 0px, you are just setting it to 0px.
You need to relinquish control to the browser after setting left to -50px. Then, you set it back to 0px. You can do it this way:
var next = document.querySelector('.nd-slide-next');
next.style.left = '-50px';
setTimeout(function() {
next.style.transitionProperty = 'left';
next.style.transitionDuration = '5s';
next.style.left = '0';
}, 10);
It also could work if you force a synchronous layout (which is often undesirable). You can achieve this by accessing some properties of a DOM node, like the offset- or client- properties. These properties always return the most updated values, so, if there are layout operations pending, the browser stops the execution of javascript code, performs the layout, and continues executing:
var next = document.querySelector('.nd-slide-next');
next.style.left = '-50px';
var offset = next.offsetLeft; //force layout. The broswer know acknowledges that next.style.left is -50px.
next.style.transitionProperty = 'left';
next.style.transitionDuration = '5s';
next.style.left = '0';

Fade In and Out on scroll using CSS opacity and Javascript

I have a div element that I would like to have fade in a certain scroll point, but instead of using (slow) or (fast) properties I have used the CSS opacity, that way it will still be visible while you scroll and change opacities as you go up and down the page. This is used in the top logo and works perfect, but for some reason I cannot find a solution to use it again on the second logo. You can see it in use on my site so far here:
http://abezieleniec.com/SIDWeb
HTML
<div class="jumbotronsecond">
<div class="container">
<div class="biglogo2">
<img src="images/biglogofull.png">
</div>
</div>
</div>
CSS
.biglogo2 {
width:80%;
display:block;
margin-left:auto;
margin-right:auto;
margin-top:130px;
margin-bottom:130px;
opacity:1;
}
JavaScript
$(function(){
var fadeBegin = 500,
fadeFinish = 800,
fadingElement = $('.biglogo2');
$(window).bind('scroll', function(){
var offset = $(document).scrollTop(), opacity = 0;
if( offset <= fadeBegin ){
opacity = 1;
} else if( offset <= fadeFinish ){
opacity = 1 - offset / fadeFinish;
}
fadingElement.css('opacity',opacity);
});
On second thoughts it's a good thing you posted a link
<script src="js/vendor/jquery-scrollspy.js"></script>
<script src="js/vendor/jquery-1.10.1.min.js"></script>
You're loading the jQuery library AFTER the extension that requires it to exist. If you look at your console (in Chrome f12 then click console) you would see Uncaught ReferenceError: jQuery is not defined
Your code works fine, except what you provided isn't balanced - there needs to be another }); to close that $(function(){. I've indented your code to show this. I wasn't so tidy in the jsfiddle sorry.
http://jsfiddle.net/xtqLz/
To make the animation smoother replace this line
// fadingElement.css('opacity',opacity);
fadingElement.stop().animate({opacity: opacity}, 200);
This tells jQuery to animate the element to the next opacity, but each time .stop()s the previous animation, otherwise they queue.
http://jsfiddle.net/xtqLz/2/

jquery Flashes Div Element Before Executing .hide() and .fadeIn() Methods

This is my code:
$('.items').html(response).hide().fadeIn();
The problem is that when this loads, the page "jumps" up due to the element being rendered on page first (having a height) before the .hide().fadeIn() is triggered.. is there some other way to do this?
You could using the opacity instead if you want to keep the dimensions of the element intact:
$('.items').html(response).css({'opacity':0}).animate({'opacity':1});
Hide using CSS and then fade it in when required :
css :
.items {
display:none;
}
JavaScript
$('.items').html(response).fadeIn();
This is a cleaner solution since it avoids a flashing effect of the element being added first, then quickly having its opacity set to 0.
This way the elem is added already having an opacity of 0.
var elem = $(response).css({'opacity': 0});
$('.items').html(elem);
elem.animate({'opacity': 1});
If you want to show a smooth transtion between existing content and new, try the following:
$(function(){
$('.items').fadeOut(function(){
$(this).html(response).fadeIn();
})
});

Categories

Resources