Rich-Harris / ramjet

Morph DOM elements from one state to another with smooth animations and transitions

Home Page:http://www.rich-harris.co.uk/ramjet/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Elements are semi-transparent when animating

williammalo opened this issue · comments

At the middle of the animation, both elements are at 50% opacity, meaning that combined together, they are 75% opacity (not 100% like they should)

The bottom element should always be 100% opacity, only the top element should change.

screenshot:
screenshot 2015-05-01 09 52 02

Great library btw, can't wait to use it in a project.

I wondered how long it'd be before someone noticed that!

The bottom element should always be 100% opacity, only the top element should change.

I was going to say that that would only solve cases where both elements had 100% opacity in their resting state, but then I realised that ramjet isn't respecting opacity anyway, so I just added a separate issue for that (#11). May have to have one rule for the both-100% situation, and another for everything else. Hmm.

You could wrap both elements in another element and set the opacity on that
For example:
element 1 has 50% opacity
element 2 has 75% opacity
To transition them:
transition element 1 from 100% to 0%
keep element 2 at 100% opacity
transition the wrapper from 50% to 75%

It would fix both bugs.

That's totally brilliant! Thanks, I think that should do the trick.

I'm literally giggling of excitement and my coworkers are looking at me funny.

Ah, I just realised where this gets tricky. If you transform a to b, and they're in different parts of the DOM, for them to be in the same wrapper element at least one of them has to move (right now, the clones are siblings of the originals). That would mean any cumulative opacity or transforms from their parent elements would have to be taken account of. Might get messy... will have to look into it

I know it's really hacky, but how about something like this?
http://html2canvas.hertzen.com

You could put the styles of the cloned elements in their style attributes.
Code example:

var flattenStyles = function(foo){
    var bla=window.getComputedStyle(foo);
    Object.getOwnPropertyNames(window.getComputedStyle(foo))
        .filter(function(a){return isNaN(parseFloat(a))})
        .forEach(function(a){foo.style[a]=bla[a]});
}

Run something similar on every element of the clone, and then you can place the element anywhere and the styles will work.

That's actually what already happens - it's the nuclear option, but I found it necessary (though I can't remember why exactly - this was when I first started hacking on this idea several months ago!)

I was thinking about this over the weekend. Appending both cloned elements to a single parent, which is then appended to <body>, should work, if we calculate the cumulative opacity and transform matrix. (That's basically what already happens with SVG elements, albeit imperfectly, but it's easier there because SVG elements have a node.getScreenCTM() method that returns the current screen-space transform matrix - it's tricker for HTML elements.)

What you lose is

  • z-index (because there's no global stacking order you can inspect and modify - the rules for the application of z-index are pretty wild, IIRC)
  • clipping, e.g. if part of an element is outside a parent with overflow: hidden
  • possibly some other stuff we haven't thought of?

My hunch is that the trade-off is worth it, if it means better visual fidelity in all cases at the expense of some glitchiness in a small, contrived minority.

This stuff is way more complicated than I would have expected, I'm happy you are making this library so I don't have to :D
You are doing a great service to mankind.

commented

I'm pretty sure theres a solvable formula for the opacity of the bottom and top layers at a given time T such that the bottom layer is 50% visible at T=.5 and the top layer is 50% visible. This should work even if you're transitioning from opacity levels != 1. I was working on it a little bit and I'm pretty sure I'm on track for a solution. This formula would also solve bug #11 .

@DDKnoll have a look at #27 - it solves (or rather, will solve) both this and #11. As far as I can see it's the only way - any combination of opacities that are less than one will also be less than one (e.g. bottom layer = 0.9 + top layer = 0.9 = (1 - ((1 - 0.9) * (1 - 0.9))) = (1 - (0.1 * 0.1)) = 0.99, so the bottom layer has to stay at 100% opaque.

commented

I'm pretty positive the bottom layer doesn't have to be 100% opaque, and thats what I'm trying to nail down. Say you are transition between .5 opacity on the first element and 1 opacity on the second element. Then at the halfway point of the animation, where time T = .5 then you should be able to calculate two opacity values such that their combined opacity =.75 and the bottom one is still 50% visible. So at that time the bottom element might have opacity .6 and the top layer has opacity .3 which would get you close to .75 opacity (I'm still working on the calculation, need to figure out the compositing formula).

Does that make sense? If this works then you shouldn't have to lose the SVG support.

Afraid I don't quite follow - as far as I can see, if both layers are less than 100% opaque then they'll be less than 100% opaque in combination, and you'll be able to see stuff underneath, which is exactly what we're trying to avoid. Unless I've misunderstood you?

SVG will still work, it's just a matter of rejigging a few things (right now they get shoved in the same <div> as HTML elements, so they fail to render - they just need to be wrapped in an <svg> and have the appropriate transforms applied)

commented

Right, so the problem is rather easy when you're transitioning from opacity 1 to 1. Then you just make the bottom layer 100% and transition the top layer. But it gets much harder when you're transitioning from one partially opaque element to another. In this case you can't just make the bottom layer 100% because it needs to know how to transition evenly between the two values.

I think theres a formula that can solve this perfectly. So in your getKeyframes function, it would have a much smarter opacity value than just using (t) and (1-t). It would handle cases where elements have different opacity levels and transition them smoothly.

For most cases we can get the right effect by controlling the container element's opacity - by transitioning that from the first element's opacity to the second element's opacity, and having the first element stay at 100% while the second element goes from 100% to 0%, you get the right effect:

ramjet-opacity-semi

That breaks down when the second element has a background that isn't opaque (e.g. rgba colour or image with alpha channel). I was planning to work around that with a crossfade option that falls back to the current behaviour, to replace any jarring glitches with 'wrong, but in a way that most people won't notice' - if there's an easing formula that gets us closer to the intended effect then I'm definitely all ears!

commented

Hmm... well I have a formula that works by transitioning the opacity of the child elements. It has the added benefit of also being able to animate the alpha channel of rgba backgroundColors. I don't think there is anything that can be done for transitioning something like an image (png) with a transparent background, but that is a whole different bucket of crabs. I think in that case you just fallback to using the crossfade option.

It also can work without creating a new container for the new elements. Do you think there is a benefit of creating the new container? I'm not sure if its a good thing or a bad thing... definitely has different behaviour when parent elements have filters, clip paths, overflow:hidden, etc.

Either way... I'll push a branch once I have these changes finished tonight and you can see which you prefer.

commented

Alright, I pushed the code for animating both rgba backgrounds and opacities so that they blend when they composite. I haven't tested yet on IE, but was able to do Safari, Firefox, Chrome, Chrome for Android, and mobile Safari.

animating-rgba

This branch is still duplicating into the new container, but it works without that. You can decide if thats the method you want to use or not. Heres the link to the pull request... #28

This branch is still duplicating into the new container, but it works without that

That's great, I was prepared to sacrifice things like filters and stacking order (which would affect a few cases) for the sake of more accurate total opacity (which affects all cases), but now it looks like there's no need. Still, it was a worthwhile diversion - I think I now know how to do SVG transitions with CSS animations as a result.