Raphael js - animate opacity on group while retaining individual elements opacities - javascript

How can I animate the opacity on a bunch of Raphael objects as one object while maintaining the individual elements opacity states? I can't animate on sets without affecting each individual element, so how do I create one object to handle—I'm thinking in a jQuery mindset if that helps answer.

If you keep global variable, then you can do this. Look at the DEMO.
var p = new Raphael(10,10, 500, 500);
var x = 0.5;
var r = p.rect(20, 20, 100, 80, 5).attr({fill: 'red', opacity: x}),
c = p.circle(200, 200, 80).attr({fill: 'orange'}),
s = p.set(r, c);
s.click(function() {
s[0].attr({opacity: x - 0.3});
s[1].attr({opacity: 0.3});
});

A general solution that works for any number of elements, any number of changes and any number of sets is to use .customAttributes which calculate and store the correct opacity level for each element in the set based on a value attached to that individual element and a value for the set.
For example (in the demo, click multiple times to see the opacity of the set of three circles change one way and the individual circle clicked on change the other way):
http://jsbin.com/oxeyih/9/edit
Using something like this, which essentially adds this feature to all existing and future sets and elements in this Raphael paper instance:
// apply this using .attr or .animate to sets or each in a set
// to set the opacity of the set as a whole, maintaining relative opacity
paper.customAttributes.setOpacity = function( setOpacity ){
// elemOpacity might not be set yet
if (typeof this.attr('elemOpacity') == 'undefined') {
this.attr('elemOpacity', this.attr('opacity'));
}
return {opacity: setOpacity * this.attr('elemOpacity')};
}
// apply this using .attr or .animate to indiviual elements
// to set that element's opacity, factoring in the opacity of the set
paper.customAttributes.elemOpacity = function( elemOpacity ){
// setOpacity might not be set yet; setting it could create infinite loop
var setOpacity = this.attr('setOpacity');
setOpacity = typeof setOpacity == 'undefined' ? 1 : setOpacity;
return {opacity: setOpacity * elemOpacity };
}
You may want to add some kind of validation if your code could go wrong if either of the custom attributes go below 0.0 or above 1.0.
Any time you set the opacity of an element, use animate({elemOpacity: xx}, time); (or .attr()) and it'll take the set opacity into account, and any time you want to set the opacity of a set, call animate({setOpacity: xx}, time); on a set, and it'll take each element's opacity into account.
In some cases with complex sets, rather than animating many elements which can be slow in many browsers, it's much better for performance, and simpler, to just plonk an overlay on top... This is a good idea if the elements in question can be safely sent .toBack() and you don't need to interact (click, hover) with them further.
Just add an overlay rectangle, set to match the inherited colour of the container element (here's a way to get inherited background colour dynamically), send it then the set .toBack(), and animate the opacity of that overlay.

Related

jquery: a better way to tie an animation to scrollbar position

var AddFootnoteScrollIndicator = function(){
$('.mobileFootnote').on('scroll touchmove', function (event) {
var scrollTop = that.$mobileFootnote.scrollTop();
if (scrollTop <= 20){
var opacity = 1 - (scrollTop/20);
$('.scroll-down-indicator').css({'opacity': opacity });
}
});
};
As the user scrolls down, the indicator slowly fades out until it is gone. They scroll back up, the indicator slowly re-appears. They stop in the middle, the indicator is half-visible.
Code works fine, but modifying the opacity via .css() seems expensive. Is there a more clever way of doing this via css or...
I don't want to delay the .on() polling because the animation needs to respond quickly to the scroll.
Any ideas?
When it comes to scroll events, modifying the css via javascript is the only way to go. There is not a way with pure CSS to detect scroll positions like you can with media queries and screen sizes.
The jquery css() function is setting the element.style.opacity property under the hood. You are only one short abstraction layer from the actual element property, so it is not "expensive".
The most costly part of that call would be the $('.scroll-down-indicator') selector, as it has to perform a DOM traversal to find elements with the class name.

JS Get Current Position of Animated Element

So I have an element that is using CSS3 transitions to move across the page. I'm trying to see how the actual output FPS of that animation on the page is (for instance, if the page is outputting at 5FPS, a div moving from 0px to 10px at a transition value of 1s should report back 2px, 4px, 6px, etc).
Instead, I just get whatever value I already set the div's position to.
// css has defined a transition of 10s on the moving div
document.getElementById("movingDiv").style.left = "0px";
console.log(document.getElementById("movingDiv").style.left); //outputs 0px
document.getElementById("movingDiv").style.left = "100px";
window.setTimeout(function(){
console.log(document.getElementById("movingDiv").style.left); //outputs 100px instead of, for instance, 43px or wherever the div would visually appear to be
}, 3000);
That's not the exact code, but just some that's generic enough to illustrate my point.
Restating the question, how would I find where an element visually appears to be during its transition between one position and another? I'm not using jQuery animations as many others have answered for, and don't just want to calculate where the element should be. I want to see where the element actually appears to be on the page. I would also like if this works off-screen as well (like to the left of or above the visible window).
To help see why I'm actually trying to do this, is that I'm trying to get the FPS output of the page. I have seen many cases where the page outputs terrible FPS but Javascript still outputs over 100 FPS because the Javascript can run faster than the page can render itself which I'm trying to avoid.
You can use window.requestAnimationFrame:
var moving = false,
el = document.getElementById("mover");
el.className = el.className + " move-right";
el.addEventListener('transitionend', function () {
moving = true;
});
function getPosition() {
var rect = el.getBoundingClientRect()
console.log(rect.top, rect.left);
if (!moving) {
window.requestAnimationFrame(getPosition);
}
}
window.requestAnimationFrame(getPosition);
http://jsfiddle.net/ob7kgmbk/1/

JavaScript: An efficient way to handle events (Firefox-specific)

I'm running a scroll event that triggers TweenMax animations, and I'm noticing that, while it looks good on Chrome, there is a considerable amount of lag on Firefox. Does anyone have a suggestion about how to handle this scroll event as efficiently as possible? Also, is there something about Firefox's rendering that I'm not aware of that might be causing this? Any leads would be appreciated!
The gist is that I'm looking for containers on my page called "customers", which each contain three individual "customer" elements. When a div that matches "customers" scrolls into view, trigger a TweenMax animation, and add a class called "animated", which prevents the element from re-animating subsequently.
Here is a fiddle with the basic demonstration:
http://jsfiddle.net/epp37jsq/
EDIT
To clarify, the fiddle only demonstrates the behavior of my animation function. The lag does not occur there because the file size is quite small. On the actual site, I have 11 groups of 3 "customers." The image is the same, but pulled in 33 times. In the future, the images will be unique. In essence, the animation is being called for each of these 11 groups. I'm looking for suggestions on how to improve the speed of my page.
And my code:
var scrollTimer = null;
$(window).scroll(function () {
if (scrollTimer) {
clearTimeout(scrollTimer); // clear any previous pending timer
}
scrollTimer = setTimeout(handleScroll, 500); // set new timer
console.log("fired!");
});
function handleScroll() {
scrollTimer = null;
$('.customers').each(function() {
if (!$(this).hasClass('animated')) {
if ($(this).isOnScreen(0.45, 0.45)) {
TweenMax.staggerFromTo($(this).find('.customer'), 0.3, {
y: 50,
opacity: 0
}, {
y: 0,
opacity: 1,
ease: Power2.easeOut
}, 0.15);
$(this).addClass('animated');
}
}
});
}
Usually with Firefox, translating on the x or y axis can cause some jank. Sometimes adding a slight rotation:0.001 to your tween can help make your tween more smooth in Firefox.
http://jsfiddle.net/pwkja058/
Also using the GSAP special property autoAlpha instead of opacity can help increase performance
TweenMax.staggerFromTo($(this).find('.customer'), 0.3, {
y: 200,
rotation:0.01, /* add a slight rotation */
autoAlpha: 0 /* use instead of opacity */
}, {
y: 0,
rotation:0.01, /* add a slight rotation */
autoAlpha: 1, /* use instead of opacity */
ease: Power2.easeOut
}, 0.15);
autoAlpha is part of the GSAP CSSPlugin:
http://greensock.com/docs/#/HTML5/GSAP/Plugins/CSSPlugin/
autoAlpha - Identical to opacity except that when the value hits 0 the visibility property will be set to "hidden" in order to improve browser rendering performance and prevent clicks/interactivity on the target. When the value is anything other than 0, visibility will be set to "inherit". It is not set to "visible" in order to honor inheritance (imagine the parent element is hidden - setting the child to visible explicitly would cause it to appear when that's probably not what was intended). And for convenience, if the element's visibility is initially set to "hidden" and opacity is 1, it will assume opacity should also start at 0. This makes it simple to start things out on your page as invisible (set your css visibility:hidden) and then fade them in whenever you want.

Robust way to event handle after the *last* CSS3 transition?

Say I have a CSS class that transitions in several settings, with different timings to boot:
.hidden {
opacity:0;
height:0px;
transition:opacity 1s ease;
transition:height 2s ease;
}
I want to handle some logic after ALL the transitions are done. I know there's a transitionend event for that:
$('#content').on('transitionend', function(e) {
mandatoryLogic()
})
$('#content').addClass('hidden');
But, how do I ensure this event handle happens once and only once, at the end of the last transition (after the 2s height transition in my case)?
I've seen a few examples that involve checking for the type of transitionend, but this closely couples the JS to a particular CSS definition. So far I'm unable to come up with a JS solution that would survive say:
opacity changed to 3s, now the longest.
new width transition to 0px over 4s introduced
extreme case, all transitions removed (so the effects are instant)
I'm not entirely sure what you're asking, but if you want to make sure it only happens once, then use a variable set to false initially, and once the transition takes place it gets set to true. For example:
var transition = false ;
if( !transition ){
// do what you need to do
transition = true ;
}
If you want to make sure it happens once all transitions are complete, then check for the values of #content or .hidden. Whichever you prefer.
var height = $(element).height() ;
var opacity = $(element).css('opacity') ;
var transition = false ;
if( !transition && opacity = 'wanted value' && height = 'wanted value' ){
// do what you need to do
transition = true ;
}
This way, with var transition being set to true it won't happen again.

Animating to a variable in JQuery

I'm animating several elements at once and the final properties of some elements depend on the properties of the other elements. For example, I something like $('#a').animate({height:$('#b').height()}), but $('#b').height() changes during the animation. Is there a way to make the animation finish at the final $('#b').height()?
I assume you want to animate the height of #a to match the final height of #b, i.e., the height the element has after the animation.
You can achieve this by updating the end value of the fx object in the step function. Example:
var $a = $('#a').animate({height: 50}, 2000);
$('#b').animate({height: $a.height()}, {
duration: 2000,
step: function(now, fx) {
if (fx.prop === 'height') {
fx.end = $a.height();
}
}
});
DEMO (note: I got the element names the other way round)
This of course only works if the second animation is at least as long as the animation it depends on.
More info in the documentation.

Categories

Resources