I am making a function to animate elements in the DOM. Everything works, except for one thing.
Transitions are only working while the element has the css property that has to be animated. For example, I want to slowly fade out an element (opacity 1 to opacity 0). This works only of I apply the opacity property manually. So:
<div class="header-logo" id="id" style="opacity: 1;">Logo</div>
works with the following code:
animate(css) {
if(this !== undefined && css) {
this.css = css;
}
let start = performance.now();
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / this.duration;
if( timeFraction > 1) timeFraction = 1;
let timing = this.timer(timeFraction); //Different timers for different easing (like ease-in-out)
this.draw(timing);
if (timeFraction < 1) {
requestAnimationFrame(animate.bind(this));
}
}.bind(this));
}
draw(timing) {
for(let property in this.css) {
this.element.style[property] -= timing * (this.element.style[property] - this.css[property]);
}
}
But if I don't apply the opacity: 1;, the code doesn't animate.
My concrete question
How can I check whether an element has the to be animated style property? And if it doesn't have the property, how can I apply (dynamically) the to be animated property (so without setting it by hand in the css sheet or inline)?
How can I check whether an element has the to be animated style
property?
To check if the opacity is actually 1, set manually or otherwise:
document.getElementById("id").style.opacity === "1"
To check if the opacity is effectively rendered by the browser as being 1:
document.getElementById("id").style.opacity === "" || document.getElementById("id").style.opacity === "1"
To check styles that are set with CSS:
window.getComputedStyle(document.getElementById("id")).opacity === "1"
how can I apply (dynamically) the to be animated property (so without
setting it by hand in the css sheet or inline)?
document.getElementById("id").style.opacity = "1"
As a final remark: be aware that there are a lot of great libraries doing all this work for you. A couple are:
Jquery (although this gives you a lot more than just animations)
D3 (awesome but can be a steep learning curve for a start)
Dynamicsjs (smaller and more specific for animations, but not a very big community behind it)
Related
I'm trying to implement an effect identical to jQuery's fadeIn() function, where an element is displayed, and then it's opacity is animated from 0 to 1. I need to do it programmatically (WITHOUT jQuery), and I need the element to be able to fade out (display:none) and then fade back in.
The ideal solution will use a CSS transition to leverage hardware acceleration - I can get the element to fadeOut with great success by listening to the transitionend event. Fading back in, however is proving to be a challenge as the following bit of code is not working as intended:
fader.style.transition = 'opacity 1s';
const fadeIn = () => {
fader.style.display = 'block';
fader.style.opacity = 1;
};
When fadeIn() is called the element simply snaps back in, instead of smoothly animating. I have a codePen that I've been tinkering with to illustrate the problem.
My theory is that the transition is unable to execute on an element that's not in the DOM, as I can get the animation to work by setting height:0 instead of display:none. Perhaps there is a delay between when I set fader.style.display = 'block'; and when the DOM is actually being updated, during which I cannot transition?
On that idea: I also seem to be able to get the animation to work by delaying the opacity change with setTimeout(() => {fader.style.opacity = 1}, 20}. This seems to create a sort of race condition however because as the timeout duration gets closer to 0 the animation works less and less dependably.
Please note that I do not want to toggle the visibility attribute like the solutions to this question, as that does not effectively remove the element from the DOM.
Changing the height/width to 0 is a more viable option, but because the height and width of the element are not known, it will require the extra step of capturing those values before fading out so they can be re-applied when fading in. This seems flimsy if, say, a different part of the application tries to change those values (for example a media query, and the user rotates their device while the element is hidden)
The following code should effectively replace jQuery's fadeOut() and fadeIn() functions (Much thanks to #Kyle for the clue!).
const fadeIn = (el, ms, callback) => {
ms = ms || 400;
const finishFadeIn = () => {
el.removeEventListener('transitionend', finishFadeIn);
callback && callback();
};
el.style.transition = 'opacity 0s';
el.style.display = '';
el.style.opacity = 0;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.addEventListener('transitionend', finishFadeIn);
el.style.transition = `opacity ${ms/1000}s`;
el.style.opacity = 1
});
});
};
const fadeOut = (el, ms, callback) => {
ms = ms || 400;
const finishFadeOut = () => {
el.style.display = 'none';
el.removeEventListener('transitionend', finishFadeOut);
callback && callback();
};
el.style.transition = 'opacity 0s';
el.style.opacity = 1;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.transition = `opacity ${ms/1000}s`;
el.addEventListener('transitionend', finishFadeOut);
el.style.opacity = 0;
});
});
};
This became super clear after digging into rAF (requestAnimationFrame) and watching this video on the event loop. Right around 21:00 is where I had the aha moment about why rAF needs to be nested inside another rAF.
Here is the working example on Codepen.
Please comment if you discover any edge cases that aren't solved for :)
I am currently running a script on a third-party page to determine the maximum dimensions of the page. At first, this may seem as if I could just use outerWidth() and outerHeight() on my parent wrapper, #base but the problem is that the page wrapper isn't sized from its children. So I might have a parent that is 0x0 and its child is 400x400 and a child inside of that which is 500x500. It seems they just allow overflow. I have tried some CSS tricks in attempt to force the parent #base to size itself correctly, but the children don't seem to drive this change and modifying their CSS causes actual alignment issues on the page. Additionally, there are many hidden items on the page that do not become visible until later or during page interaction so this further prevents me from just grabbing the outer dimensions of #base or something like that.
My current approach is to iterate through every single element on the page. I check to see where it is positioned and what its dimensions are. Based on those, I update my maximum dimensions. I also have to check for horizontal and vertical scrolling elements because those may be on the page too. If a wrapper is 500px wide and the child has a width of 1000px, but is scrolled, I wouldn't want that to affect my maximum dimensions. Anyways, this approach works but it's slow. Sometimes the page may have +15k elements. With these numbers, it takes 10 seconds on my machine. I might be able to optimize some of the conditional statements to use booleans instead of evaluating values, but I don't think this will make a significant difference. I'm hoping there is some process I'm completely overlooking. Below is my current code snippet and a demo showing how the page looks prior to running the code and after the code has been run.
Demo: https://p826ni.axshare.com/#g=1&p=without_code
$('#base *').not('script, style').each(function () {
currentElement = $(this);
// Initialize on first loop.
if (parentElementHorizontal === undefined && parentElementVertical === undefined) {
parentElementHorizontal = currentElement;
parentElementVertical = currentElement;
}
width = currentElement.outerWidth();
height = currentElement.outerHeight();
scrollWidthHidden = currentElement[0].scrollWidth;
scrollHeightHidden = currentElement[0].scrollHeight;
top = currentElement.offset().top;
left = currentElement.offset().left;
// Check if we're still within the parent containing horizontal-scrolling overflow.
if (!$.contains(parentElementHorizontal[0], currentElement[0])) {
hiddenWidth = false;
}
// Check if we're still within the parent containing vertical-scrolling overflow.
if (!$.contains(parentElementVertical[0], currentElement[0])) {
hiddenHeight = false;
}
// Check if we've found an element with horizontal-scrolling content.
if (!hiddenWidth) {
maxWidth = maxWidth < left + width ? left + width : maxWidth;
} else if (currentElement.width() > maxWidth) {
currentElement.addClass('redline-layer');
}
if (scrollWidthHidden > width && !hiddenWidth && width > 0) {
hiddenWidth = true;
parentElementHorizontal = currentElement;
}
// Check if we've found an element with vertical-scrolling content.
if (!hiddenHeight) {
maxHeight = maxHeight < top + height ? top + height : maxHeight;
} else if (currentElement.height() > maxHeight) {
currentElement.addClass('redline-layer');
}
if (scrollHeightHidden > height && !hiddenHeight && height > 0) {
hiddenHeight = true;
parentElementVertical = currentElement;
}
});
I want to remove/add classes when the user is at different distances from the top by using jQuery.
I have successfully done it, and it works fine, but I think I'm doing it wrong, and I would like your help to optimize the code.
The html is simple, basically the sections(including the header), have 100% width. and different colors. I want to make the header change color when its over the first section(for aesthetical purposes).
And I also want it to have a shadow when the page has been scrolled more than 1 pixel.
I'm doing it by adding/removing classes.
When I use one big else if statement it doesn't work well because whenever any any condition is matched js stops checking for other matches, so it doesn't apply all the classes needed.
The next code works, however as I said, I think that it's not optimal/bad written.
Here is the HTML markup:
<header class="dark no-shadow">
Header
</header>
<section class="blue">
Please Scroll Down to see the header changes...
</section>
<section>
The header color Should change when you pass through me.
</section>
And here is the jQuery code:
var header = $('header'),
blueSection = $('section.blue'),
// Calculate when to change the color.
offset = blueSection.offset().top + blueSection.height() - header.height();
$(window).scroll(function(){
var scroll = $(window).scrollTop();
// Remove Class "dark" after scrolling over the dark section
if (scroll >= offset) {
header.removeClass('dark');
} else {
header.addClass('dark');
}
// Remove Class "no-shadows" whenever not on the top of the page.
if (scroll >= 1) {
header.removeClass('no-shadow');
} else {
header.addClass('no-shadow');
}
});
And for those of you who like to use jsfiddle(like me!):
https://jsfiddle.net/shock/wztdt077/6/
Thanks ahead guys!
Here is what I've come up with:
var header = $('header'),
blueSection = $('section.blue'),
// Calculate when to change the color.
offset = blueSection.offset().top + blueSection.height() - header.height();
var add = function(obj, cls) {obj.addClass(cls);}
var remove = function(obj, cls) {obj.removeClass(cls);}
var stylePoints = [offset, 1, 100, 200];
var styleTo = ['dark', 'no-shadow', 'blue', 'tall'];
var styleType = [add, add, remove, remove];
$(window).scroll(function() {
var scroll = $(window).scrollTop();
for (i = 0; i < stylePoints.length; i++) {
var func = styleType[i];
if (scroll >= stylePoints[i])
(styleType[i] == add) ? remove(header, styleTo[i]) : add(header, styleTo[i]);
else func(header, styleTo[i]);
}
});
It's not that much longer than your current jQuery, and allows for (theoretically) an infinite number of style changes without having to add a million long if/else statements. To add a new style change, you have to add a value to the end of each of the three arrays. stylePoints specifies the scrollTop() value at which a style should either be added or removed. styleTo specifies the class to be added or removed. styleType specifies whether this class should be added or removed when the user is scrolled above the corresponding stylePoints value. The opposite will occur when the user is scrolled below or at the corresponding stylePoints value. For instance, you can see from the code that the tall class will be removed from the header when the user is scrolled above 200, and added when the user is scrolled below or at 200.
I have a web page with three divs that are synced together.
+----------+
| TOP |
+---+----------+
| | ||
| L | CENTER ||
| |_________||
+---+----------+
<!--Rough HTML-->
<div>
<div class="topbar-wrapper">
<div class="topbar"></div>
</div>
<div class="container">
<div class="sidebar-wrapper">
<div class="sidebar"></div>
</div>
<div class="center-wrapper"> <!-- Set to the window size & overflow:scroll -->
<div class="center"></div> <!-- Has the content -->
</div>
</div>
</div>
The center div has scroll bars, both horizontal and vertical. The top and left divs do not have scroll bars. Instead, when the horizontal scroll bar is moved, the scroll event updates the top div's margin-left appropriately. Likewise when the vertical scroll bar is moved, the margin-top is updated. In both cases, they are set to a negative value.
$('.center-wrapper').scroll(function(e) {
$('.sidebar').css({marginTop: -1 * $(this).scrollTop()});
$('.topbar').css({marginLeft: -1 * $(this).scrollLeft()});
});
This works fine in Chrome and Firefox. But in Safari, there is a delay between moving the scroll bar and the negative margin being properly set.
Is there a better way to do this? Or is there some way to get rid of the lag in Safari?
EDIT:
Check out this Fiddle to see the lag in Safari: http://jsfiddle.net/qh2K3/
Let me know if this JSFiddle works better. I experienced the same "lag" on my end too (Safari 7 on OS X) and these small CSS changes significantly improved it. My best guess is that Safari is being lazy and has not turned on its high-performance motors. We can force Safari to turn it on using some simple CSS:
.topbar-wrapper, .sidebar-wrapper, .center-wrapper {
-webkit-transform: translateZ(0);
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
}
This enables hardware accelerated CSS in Safari by offloading some of the work to the GPU. It improves rendering in the browser, which may have been the issue in the delay.
I've had this issue a few times, where safari seems to be lagging when incrementally moving elements based on events like this.
Here's how I've solved the problem in the past.
In browsers that support hardware acceleration, using any "3d" CSS property will enable the hardware acc.
If 3D transforms is supported, we might as well use translate3d to move the elements, as that makes for smoother movements than shifting pixels.
This means we first have to figure out the right prefix for the CSS transform property, then check support for 3D transforms. If we have support, translate the elements, if not shift pixels.
By figuring out the prefixes and 3D support without libraries it should be just about as fast as it can get, and has no dependencies.
var parent = document.querySelector('.center-wrapper'),
sidebar = document.querySelector('.sidebar'),
topbar = document.querySelector('.topbar'),
has3D = false,
transform = null,
transforms = {
'webkitTransform' : '-webkit-transform',
'OTransform' : '-o-transform',
'msTransform' : '-ms-transform',
'MozTransform' : '-moz-transform',
'transform' : 'transform'
};
for (var k in transforms) if (k in parent.style) transform = k;
if ( transform !== null ) {
sidebar.style[transform] = 'translate3d(0,0,0)'; // start hardware acc
topbar.style[transform] = 'translate3d(0,0,0)'; // start hardware acc
// check support
var s = window.getComputedStyle(sidebar).getPropertyValue(transforms[transform]);
has3D = s !== undefined && s.length > 0 && s !== "none";
}
if (has3D) {
parent.onscroll = function (e) {
sidebar.style[transform] = 'translate3d(0px, '+ (this.scrollTop * -1) +'px, 0px)';
topbar.style[transform] = 'translate3d('+ (this.scrollLeft * -1) +'px, 0px, 0px)';
}
}else{
parent.onscroll = function (e) {
sidebar.style.marginTop = (this.scrollTop * -1) + 'px';
topbar.style.marginLeft = (this.scrollLeft * -1) + 'px';
}
}
FIDDLE
Remember to put this after the elements in the DOM, as in right before </body>, or if that's not possible, it has to be wrapped in a DOM ready handler.
As a sidenote, querySelector is not supported in IE7 and below, if that's an issue you have to polyfill it.
Caching selectors will help.
Changing :
$('.center-wrapper').scroll(function(e) {
$('.sidebar').css({marginTop: -1 * $(this).scrollTop()});
$('.topbar').css({marginLeft: -1 * $(this).scrollLeft()});
});
To :
var _scroller = $('.center-wrapper'),
_swrapper = $('.sidebar-wrapper'),
_twrapper = $('.topbar-wrapper');
_scroller.scroll(function (e) {
_swrapper.scrollTop(_scroller.scrollTop());
_twrapper.scrollLeft(_scroller.scrollLeft());
});
.scroll() will be working very hard if it has to recreate jquery objects $(this) each time.
Wrote this in a comment last night, and while not been able to test yet, actually felt today that this is should be added as directly relates to performance.
We can try it out - here
More / The Methods - making them native too
There are also another jquery method calls we can cut out too. .scrollTop() and .scrollLeft() by caching and returning the native _scrollernative = _scroller.get(0) to make use of the readily available properties scrollLeft.
Like :
var _scroller = $('.center-wrapper'),
_scrollernative = _scroller.get(0),
_swrapper = $('.sidebar-wrapper').get(0),
_twrapper = $('.topbar-wrapper').get(0);
_scroller.scroll(function (e) {
_swrapper.scrollTop = _scrollernative.scrollTop;
_twrapper.scrollLeft = _scrollernative.scrollLeft;
});
We can try this here
I appreciate that your code might be looking at more than one of these controls on the page, and that's why you have used classes and needing to find S(this) instead of using ID's.
For that I would still use the above caching methods but before that we need to create them one by one. ( caching before we initialise the scroll() function per instance )
(function() {
var _scroller = $('.center-wrapper'),
_swrapper = $('.sidebar-wrapper').get(0),
_twrapper = $('.topbar-wrapper').get(0);
function setScrollers(_thisscroller) {
_scrollernative = _thisscroller.get(0);
/* start the scroll listener per this event */
_thisscroller.scroll(function (e) {
_swrapper.scrollTop = _scrollernative.scrollTop;
_twrapper.scrollLeft = _scrollernative.scrollLeft;
});
}
_scroller.each(function() { setScrollers($(this)); });
})();
We can try this here
But As I see that .sidebar-wrapper and .topbar-wrapper are unique, mabye .center-wrapper is unique too.
So how about finally using id's for these elements to shorten everything up.
var _scroller = document.getElementById('center-wrapper'),
_swrapper = document.getElementById('sidebar-wrapper'),
_twrapper = document.getElementById('topbar-wrapper');
_scroller.onscroll = function() {
_swrapper.scrollTop = _scroller.scrollTop;
_twrapper.scrollLeft = _scroller.scrollLeft;
};
We can try this here
Instead of using marginTop & marginLeft to change margins of .sidebar & .topbar use scrollTop & scrollLeft for the overflowing divs .sidebar-wrapper & .topbar-wrapper and see if it now works in Safari:
Demo Fiddle
$('.center-wrapper').scroll(function(e) {
$('.sidebar-wrapper').scrollTop($(this).scrollTop());
$('.topbar-wrapper').scrollLeft($(this).scrollLeft());
});
Try this fiddle made to look best in safari and tested
I want the javascript code to show a div in slow motion.
function showDiv(divID)
{
if(document.getElementById(divID).style.display=='none')
{
document.getElementById(divID).style.display='block';
}
}
Here div appears, but not in slow motion. Can anyone help ??
Thanks in advance
Dev..
There is no need of jQuery in this atall , its just a basic I am using your function to explain how thats done.
function showDiv(divID)
{
if(document.getElementById(divID).style.display=='none')
{
document.getElementById(divID).style.display='block';
}
}
What your function is doing is basically removing the whole Element from BOX Model ( the toggle of block and none removes the element totally from the BOX Model so it doesnt occupies any space or anything , this but may / may not cause some layout issues );
Now to animate it in slow motion you need a timing function.
a timing function is a simple mathematical function which gives the value of the property ( opacity in your case ) for a given time or depending on other parameters .
Other then that you also need to use properties like opacity in order to fade it (Opacity is a CSS property that defines the transparency of an element and its childrens )
So let us begin with a very basic show / hide using setTimeout Function in JS.
function getValue(t,dir){
if( dir > 0){
return 0.5*t; /* Y = mx + c */
}else{
return 1-(0.5*t);
}
/*
Here the slope of line m = 0.5.
t is the time interval.
*/
}
function animator(divID){
if(!(this instanceof animator)) return new animator(divID); /* Ignore this */
var Node = document.getElementById(divID),
start = new Date.getTime(), // The initiation.
now = 0,
dir = 1,
visible = true;
function step( ){
now = new Date.getTime();
var val = getValue( now - start,dir)
Node.style.opacity = val;
if( dir > 0 && val > 1 || dir < 0 && val < 0 ){
visible = !(visible*1);
// Optionally here u can call the block & none
if( dir < 0 ) { /* Hiding and hidden*/
Node.style.display = 'none'; // So if were repositioning using position:relative; it will support after hide
}
/* Our animation is finished lets end the continous calls */
return;
}
setTimeout(step,100); // Each step is executated in 100seconds
}
this.animate = function(){
Node.style.display = 'block';
dir *= -1;
start = new Date.getTime();
setTimeout(step,100);
}
}
now you can simply call the function
var magician = new animator('divName');
then toggle its animation by
magician.animate();
Now playing with the timing function you can create whatever possibilities you want as in
return t^2 / ( 2 *3.23423 );
or even higher polynomial equations like
return t^3+6t^2-38t+12;
As you can see our function is very very basic but it explains the point of how to make animations using pure js . you can later on use CSS3 module for animation and trigger those classes with javascript :-)
Or perhaps write a cross browser polyfill using CSS3 where available ( it is faster ) , and JS if not :-) hope that helps
Crossbrowser solution (without jQuery) :
HTML :
<div id="toChange" ></div>
CSS :
#toChange
{
background-color:red;
width:200px;
height:200px;
opacity:0;//IE9, Firefox, Chrome, Opera, and Safari
filter:alpha(opacity=0);//IE8 and earlier
}
Javascript :
var elem=document.getElementById("toChange");
var x=0;
function moreVisible()
{
if(x==1)clearInterval(t);
x+=0.05;
elem.style.opacity=x;
elem.style.filter="alpha(opacity="+(x*100)+")";
}
var t=setInterval(moreVisible,25);
Fiddle demonstration : http://jsfiddle.net/JgxW6/1/
So you have a few jQuery answers but I wouldn't recommend jQuery if fading the div is all you want.
Certainly jQuery makes things easier but it is a lot of overhead for a single simple functionality.
Here is someone that did it with pure JS:
Fade in and fade out in pure javascript
And a CSS3 example:
How to trigger CSS3 fade-in effect using Javascript?
You can use jquery $.show('slow') for the same, if you want to do the same without using jquery then you might be required to code something to show the effect yourself, you may have a look at source of jquery's show function http://james.padolsey.com/jquery/#v=1.6.2&fn=show . alternatively , you can also use fadein() for fade in effect in jquery
Yes you can do it using Jquery. Here is my sample example
$('#divID').click(function() {
$('#book').show('slow', function() {
// Animation complete.
});
});
For details clik here
Thanks.