Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
ryangiglio

Tweening top: calc() value not working in Safari

Recommended Posts

I'm creating a relatively complex nav layout for a single-page scrolling site using GSAP and ScrollMagic. The "upcoming" page in the navigation tweens upward as you scroll down until it hits the top of the nav, and then a page transition is triggered (which is unrelated to this issue). I've included a stripped-down Codepen with only the Nav - please view it in Full View using Chrome or Firefox to see the effect working properly.

 

The value it's tweening to achieve that effect is relatively complicated - it's top: calc(-100vh + [nav container height] + [2x nav bottom property]) which has the effect of moving each item up but maintaining the same spacing once they're stacked at the top. I know there's an outstanding issue with tweening calc() values that's been on GitHub for a while - you can't tween them unless you first use TweenMax.set to establish the pre-tween calc value. Using that workaround got it to work in Chrome and Firefox, but it's still not working in Safari. Instead Safari waits until the end of the ScrollMagic duration and then simply jumps to the end value.

 

I'm not sure what to do about this issue - I've tested it in Safari and tweening a non-calc value works fine, but I need a calc in order to achieve the effect I want. Any help would be appreciated!

See the Pen bpZjpe by ryangiglio (@ryangiglio) on CodePen

Link to post
Share on other sites

Hello ryangiglio, and Welcome to the GSAP Forum!

 

Safari & iOS Safari (both 6 and 7) do not support viewport units (vw, vh, etc) in calc().

 

So in your case, try not to use viewport units in calc(), since you will have issues in Safari 6 and 7.

 

This looks like a browser support issue in Safari webkit:

 

http://caniuse.com/#feat=calc

 

You could try to use the webkit prefix for calc() .. but it looks like your main issue is using viewport units inside calc() within Safari / iOS Safari

 

Usually with calc() you need to also use the -webkit prefix which is required for Safari 6 and Chrome 19-25 per the spec?

-webkit-calc()

For example, in a stylesheet it would look like this, having a fallback for calc()

.my-element {
    top: -webkit-calc(100% - 80px); /* Safari 6, Chrome 19-25 - no viewport units allowed */
    top: calc(100% - 80px); /* other modern browsers */
}

One thing to also take into consideration, as a general rule of thumb when animating elements. Is that you should always animate using x and y instead position offsets of top and left. The reason being is that animating x and y will allow you to animate on a sub-pixel level whereas css position offsets like top, left, bottom, and right only animate on pixel level and will give you choppy animation.

 

Also your changing CSS properties with jQuery css() method instead of allowing GSAP to apply your css properties. The GSAP equivalent of the jQuery css() method is the GSAP set() method. GSAP is better since it will add prefixes if needed. This way GSAP can keep track of what css properties you are changing. By using jQuery css() you are changing css properties outside of GSAP so it will not know if you change a property outside itself.

 

Have you seen if this behavior in Safari happens without using ScrollMagic. The reason i ask is that Scroll Magic is made with GSAP, but it is not made by GSAP.

 

Also you really don't need calc() to achieve what you want, since calc() is just an expression for css inside your stylesheet. But since you are using JS, you can take advantage of doing that same logic with JS expression conditional logic and get the same results cross browser.

 

So try not use viewport units inside calc() within Safari / iOS Safari and you should be able to still use calc()

 

:)

  • Like 4
Link to post
Share on other sites

Thanks for your really thorough answer! I'm relatively new to GSAP after having done most of my animation/transitions using pure CSS for a long time so I'm not used to thinking outside of the confines of CSS. You're right, it doesn't make sense to use calc() when I can just...calculate the exact pixel value with Javascript. That was a fairly trivial change and it totally worked!

  • Like 1
Link to post
Share on other sites

So actually I was a little hasty - problem not totally solved yet...

 

Now I remember the reason I used calc in the first place - this needs to be vertically responsive. So if the user resizes the height of the browser window after they've loaded the page, the target value of the tween needs to update to reflect that. With calc() that happens automatically, but it doesn't when the value is calculated at page load. Is there a way to update the target value of the tween after it's been created?

Link to post
Share on other sites

In that case you can use the GSAP special property that is part of the GSAP CSSPlugin called yPercent.

 

So instead of using top, you would use y (translateY) for a smoother sub-pixel animation. But since you want responsive elements, then you would use yPercent instead.

 

yPercent and xPercent help with animating your elements for responsive animations.

 

http://greensock.com/gsap-1-13-1

 

Codepen examples of xPercent and yPercent:
 

See the Pen rAetx by GreenSock (@GreenSock) on CodePen

 

See the Pen axzmb by GreenSock (@GreenSock) on CodePen

 

Some notes on using xPercent and yPercent for responsive animation:

  • To do percentage-based translation use xPercent and yPercent (added in version 1.13.0) instead of x or y which are typically px-based. Why does GSAP have special properties just for percentage-based translation? Because it allows you to COMBINE them to accomplish useful tasks, like perhaps you want to build your own "world" where everything has its origin in the very center of the world and then you move things in a px-based fashion from there - you could set xPercent and yPercent to -50 and position:"absolute" so that everything starts with their centers in the same spot, and then use x and y to move them from there. If you set x or y to a percent-based value like 50%", GSAP will recognize that and funnel that value to xPercent or yPercent appropriately as a convenience. Our 1.13.1 Release Notes have some great demos showing how xPercent and yPercent can help with responsive layouts.

Happy Tweening! :)

  • Like 2
Link to post
Share on other sites

I'm having trouble figuring out how to use yPercent to implement this specific calculation, because it's still partly a fixed-pixel value (which is why calc() was my instinct - it lets you mix those kinds of things). Simplifying the variables for the sake of discussion, I'm trying to tween towards y = -window height + 350px. Is this possible with yPercent?

Link to post
Share on other sites

yPercent uses percentages not pixels. So it would be y = -(window height + 350) without the px in your calculation.

 

Do you have a fork (copy) of your original example then we could help you better by seeing what you have tried, Thanks!

Link to post
Share on other sites

I fear this won't be possible as-is without using vh. I may need to rethink the way the whole layout works. Because they're relatively positioned inside a fixed position container, any kind of percentage-based y value on the links themselves is going to be based on their own height which isn't helpful. To stick the links a fixed distance from the top I need to know the height of the window dynamically whenever it's resized which only seems to be possible with the vh unit :(

 

Here's a few iterations that I've tried:

 

Eliminating calc entirely and replacing top with y (your original comment): 

See the Pen WwWbjJ by ryangiglio (@ryangiglio) on CodePen

This works perfectly on page load - but if you vertically resize the window the spacing gets thrown off until you refresh

 

Quick sample of what yPercent does: 

See the Pen MyRwYW by ryangiglio (@ryangiglio) on CodePen

It moves them relative to their own height, so -100 tweens them up by 1, -200 up by 2, etc.

Link to post
Share on other sites

I need to know the height of the window dynamically whenever it's resized which only seems to be possible with the vh unit :(

 

Hi there ryangiglio  :)

 

I'm jumping into this mid-conversation, but couldn't you use a variable to check the window height on load and on resize? Then just use that for any calculations you may need.

var h = window.innerHeight

I'm not sure if that helps, but thought I'd throw it out there.

 

Happy tweening.

:)

  • Like 2
Link to post
Share on other sites

Thanks for chiming in! That was what I tried originally, before resorting to calc(). As far as I understand, GSAP caches the value you passed when you originally setup the Tween. After that it's just animating in stages between its current state and the original value you gave it, regardless of whether or not that value has changed since you set it up. Here's an explanation as to why that's the case: http://greensock.com/forums/topic/10332-update-tween-value-while-its-tweening/

Link to post
Share on other sites

Yep, I was thinking the same way PointC was - just use straight JS (like window.innerHeight). 

 

I certainly wouldn't consider the lack of tweening "calc()" values in GSAP to be a "bug" because it strikes me as impossible to do. How could it animate between dynamically calculated values? What if it starts at "calc(50px + [window height])" and ends at "calc(-100vh + [nav container height] + [2x nav bottom property])"? There are completely different numbers of properties, units, relative values, etc. Sure, we could apply them at the moment the tween starts and use the calculated values but that's not technically accurate. What if someone resized the window DURING the tween? See what I mean? Perhaps I'm missing something, but I'm pretty sure the only way to do this would be to force the use of computed values at a give time (start of the tween) and live with the fact that they wouldn't be dynamic during the tween at all. In other words, it'd convert to something like "235px to 131px" instead. 

 

If it were me, I'd probably just use JS since it's so dynamic by its very nature and avoid CSS calc() values altogether.

  • Like 3
Link to post
Share on other sites

Oh, and if window.innerHeight changes, you just create a new tween the overwrites the old one (or you could manually kill() it). It's pretty trivial to set up a "resize" listener to trigger that. 

  • Like 4
Link to post
Share on other sites

Just to add my two cents.

CSS calc() is still listed as experimental, so it's really not ready for prime time due to all the browser bugs and inconsistent calculations within each browser. It will probably be awhile before calc() can be used where it will calculate properly across modern browsers.

https://developer.mozilla.org/en-US/docs/Web/CSS/calc

In my opinion there is no need to use css calc() if using JavaScript, since JavaScript is not limited by the css calc() spec that is still buggy and lacking support. CSS calc() has not even been set in stone, as far as the standard spec in W3C and WHATWG.

;)

  • Like 2
Link to post
Share on other sites

Thanks for your input everyone! I appreciate all the thinking here!

 

I certainly wouldn't consider the lack of tweening "calc()" values in GSAP to be a "bug" because it strikes me as impossible to do. How could it animate between dynamically calculated values?

 

I definitely don't consider it a bug in GSAP, but I do think it's a bug in Safari based on the fact that it works as expected in Chrome and Firefox. I understand the logical impossibility there so I imagine it's doing something tricky under the hood to return an actual pixel value to anything trying to use/manipulate it and it's just representing it to the dev/user as a calculated value.

 

 

Oh, and if window.innerHeight changes, you just create a new tween the overwrites the old one (or you could manually kill() it). It's pretty trivial to set up a "resize" listener to trigger that. 

 

This is probably what I need to do - I was thinking it would be a little processor-heavy but I can't imagine resizing the height of the browser to be so common a thing as for that to be a significant bottleneck.

 

 

Just to add my two cents.

CSS calc() is still listed as experimental, so it's really not ready for prime time due to all the browser bugs and inconsistent calculations within each browser. It will probably be awhile before calc() can be used where it will calculate properly across modern browsers.

 

While I do realize it's considered experimental, I've used the basic functionality like width: calc(100% - 200px) to stretch the body of a site next to a fixed-sized sidebar and found it to be pretty reliable and well-supported. I'm definitely stretching it beyond its limits here.

  • Like 1
Link to post
Share on other sites

Nice points you bring up ryangiglio ;)

 

CSS calc() can be reliable in some case, but is not reliable in all modern browsers (mobile and desktop), for cross browser compatibility!

 

The following are known bugs and issues with CSS calc() cross browser.

  • IE11 is reported to not support calc() correctly in generated content
  • IE11 is reported to have trouble with calc() with nested expressions, e.g. width: calc((100% - 10px) / 3); (i.e. it rounds differently)
  • Firefox does not support calc() inside the line-height, stroke-width, stroke-dashoffset, and stroke-dasharray properties. Bug report
  • Safari & iOS Safari (both 6 and 7) does not support viewport units (vw, vh, etc) in calc().
  • IE & Edge are reported to not support calc inside a 'flex'. (Not tested on older versions)
    This example does not work: flex: 1 1 calc(50% - 20px);
  • IE10 and IE11 don't support using calc() inside a transform. Bug report
  • IE 9 - 11 don't render box-shadow when calc() is used for any of the values
  • IE11 does not support transitioning values set with calc()
  • IE10 crashes when a div with a property using calc() has a child with same property with inherit.

That is why if your using JavaScript there is no need for calc(), since JavaScript is more reliable and faster in its calculations than CSS calc() would be, due to the lack support and non standard behavior.

 

calc() also has limitations due to the calculation in calc() is based on its immediate parent, not the window. . So you need to make sure that immediate parent does not have position fixed, and has a defined width. So that is why JavaScript is better for calculations if your already using JavaScript.

 

I only use calc() if i am using CSS variables, but only if i use pixels and / or percentages. If i am using javascript, then there is no need to use calc() due to the limitation of cross browser support.

 

Regarding window.innerHeight .. There should be no bottleneck for getting the window height In JavaScript, since it is a very common thing, and is very straight forward.

 

JavaScript parses very fast in the browser. The bottleneck comes from the browser itself that use programming languages like C#. Kind of like a C# wrapper for its shell. That is what slow down in modern browsers ;)

 

Another suggestion you can do is if you have to have the element with CSS position fixed. Make sure that its first immediate child is an element with CSS position relative. So its children elements are relative to that, instead of your fixed position element. So this way you don't have to deal with that limitation of using calc() on an element who's immediate parent has CSS position fixed, which could affect the calculation.

 

And another thing when using calc() is that you should always have a fallback for the CSS property that is using calc(). So if it fails the browser can use your fallback

 

Don't get me wrong i love using CSS calc(), since CSS is my first love. But in a couple of years when the CSS calc() spec finally gets submitted into recommendation status by WHATWG and the W3C. Then we will see better behavior cross browser. But for now we have to deal with the wild west or feudal Japan behavior of CSS calc(). :ph34r:

 

:)

  • Like 3
Link to post
Share on other sites

Jonathan - is your brain hard-wired to every browser information source?  :-P

 

Great stuff sir!

 

:)

  • Like 1
Link to post
Share on other sites

No, just countless years and sleepless nights of banging my head on my keyboard testing various browsers! :)

  • Like 2
Link to post
Share on other sites

The reason my layout is so finnicky is because I'm trying to make this work (partly for academic reasons) without hard-coding their positions based on nth-child. Since the links are relative positioned inside a fixed position container they can stack naturally while still being stuck to the bottom of the window. If the links were fixed position I would need to manually position each one, which I'd like to avoid.

 

The following are known bugs and issues with CSS calc() cross browser.

  • IE11 is reported to not support calc() correctly in generated content
  • IE11 is reported to have trouble with calc() with nested expressions, e.g. width: calc((100% - 10px) / 3); (i.e. it rounds differently)
  • Firefox does not support calc() inside the line-height, stroke-width, stroke-dashoffset, and stroke-dasharray properties. Bug report
  • Safari & iOS Safari (both 6 and 7) does not support viewport units (vw, vh, etc) in calc().
  • IE & Edge are reported to not support calc inside a 'flex'. (Not tested on older versions)
    This example does not work: flex: 1 1 calc(50% - 20px);
  • IE10 and IE11 don't support using calc() inside a transform. Bug report
  • IE 9 - 11 don't render box-shadow when calc() is used for any of the values
  • IE11 does not support transitioning values set with calc()
  • IE10 crashes when a div with a property using calc() has a child with same property with inherit.

 

While that's a pretty long list of bugs, most of them are specific to IE and none of them are really debilitating when you're using its most basic functionality which is why they haven't scared me off. I've used it pretty liberally in layouts and haven't run into an issue..

 

Regarding window.innerHeight .. There should be no bottleneck for getting the window height In JavaScript, since it is a very common thing, and is very straight forward.

 

The bottleneck I was referring to was destroying/recreating the Tween every time the window was vertically resized - there's a lot more processing involved. I'm going to give that a shot regardless and see how it feels - I wouldn't want the position of the thing to be too jumpy during/after a resize.

Link to post
Share on other sites

Oh, and if window.innerHeight changes, you just create a new tween the overwrites the old one (or you could manually kill() it). It's pretty trivial to set up a "resize" listener to trigger that. 

 

This ended up being the best solution. There's not too much overhead removing and recreating the tween. Simple is best!

 

Huge thanks everyone for taking so much time to discuss and explore this issue! I'm relatively new to GSAP and this is a pretty fantastic community.

  • Like 1
Link to post
Share on other sites

I understand. .. but if you choose to use calc(), then you are not making your code cross browser to reach a wide range of users. And then your limiting your animations to only those browsers that have partial support for calc().

 

IE11 is the only version of Internet Explorer for Windows 7, since MS Edge is only available on Windows 10.

 

My motto is to make work everything cross browser, otherwise your limiting yourself to a CSS property that has limited support cross browser. thats is whats great about javascript and GSAP. You can make your code cross browser and bridge the gap where CSS just doesn't cut it, due to browser support for various CSS properties.

 

Regarding killing your tweens on resize. You don't have to kill the tween, and recreate it, since you can just invalidate() those recorded values so GSAP can get those new values after resize.

 

So you could invalidate() the tween and simply restart() the tween

 

Resources:

TweenMax.invalidate() : http://greensock.com/docs/#/HTML5/GSAP/TweenMax/invalidate/

TimelineMax.invalidate() : http://greensock.com/docs/#/HTML5/GSAP/TimelineMax/invalidate/

TimelineMax restart() : http://greensock.com/docs/#/HTML5/GSAP/TimelineMax/restart/

 

:)

  • Like 1
Link to post
Share on other sites

I didn't know about the invalidate() function - that looks perfect!

 

I understand. .. but if you choose to use calc(), then you are not making your code cross browser to reach a wide range of users. And then your limiting your animations to only those browsers that have partial support for calc().

 

IE11 is the only version of Internet Explorer for Windows 7, since MS Edge is only available on Windows 10.

 

As long as you're sticking to the basics it was originally intended for (my example from before - stretching a content area next to a width-width sidebar), calc() is actually supported all the way back to IE9. Most of the bugs in that list are weird edge cases that wouldn't normally come up in styling a normal website. I've used it on a bunch of sites in the last few years and have never run into an issue.

 

In this specific case I'm working on an internal tool and I know everyone will be using the most recent versions of Chrome/Firefox/Safari so that's not a concern.

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×