ALI GHASSEMI: Stories, they
are great, aren't they?
Everybody loves a good story.
And I'm here today to
tell you a story as well,
the story of how we brought
complex animations to AMP.
So here we go--
once upon a time, right before
lunch but after a munch,
just before a merge but after
a purge, Ali G. got a message.
This is cool.
Can we do it in AMP?
Of course we can,
said the little man.
Give me a minute,
I shall be back.
By the way, this is
the only cheesy part
of the presentation,
so do not worry.
Optimism is great, isn't it?
So one year later we were done.
But a lot more than just
parallax was done in that year.
A low-level
framework was created
to handle many complex
animations in AMP.
So let's take a step back
and look at that journey
to talk about the three
different approaches we took--
the complexities, the
tradeoffs, and of course,
the final results.
But let's start with
the basics first.
So what is parallax, anyway?
At its core, it's
just some contents
moving faster or slower than
the other content as the user
scrolls.
You have likely seen this in
one form or another on the web,
but why is it a hard problem?
Isn't CSS meant for
this sort of thing?
Well, like many other
things in the web platform,
there is no primitive
for parallax,
but there are many
other primitives
which can be hacked
together in innovative ways
to create new things.
And that's one of the
beauties of the web platform.
So let's talk about
the first hat we tried.
It has something to do
with 3D support in CSS.
Perspective is a
property in CSS that
creates a 3D space by moving the
view point out of the 2D pane
and into the third dimension.
The value of the property--
of course there will be cats.
The value of the
property is essentially
the distance that controls
the intensity of the effect.
So in this example--
that's not showing up--
it was showing the use
of the parallax, use
of the perspective
property in CSS.
So as the view point--
as you use the
perspective property,
as you change the viewpoint, and
as you get closer to 3D things,
you notice more 3Dness.
And as you get further
away, you notice less.
So what does that have
to do with parallax?
Bear with me for a
minute, and I will
explain how 3D CSS has
anything to do with parallax.
Another property of 3D
CSS is that browsers--
they're very serious about
this virtual world mimicking
the real world.
So just like in real
life, distant objects
seem to move slower
than the closer ones
as the viewpoint moves across.
The classic example
of this is looking out
of the window of a moving train.
The mountains seem to
move slower than the trees
and so on.
But whatever, how is this
related to parallax, again?
Well, scrolling is the
movement of the view
point on the y-axis.
So if we create multiple
layers on the z-axis
at different distances
from each other,
they will move slower or
faster relative to each other
as the user scrolls.
Of course, the distant objects
are also rendered smaller
than their intrinsic size.
Again, browsers are being
super-realistic here.
But what we can do
is to scale them
back to their original size
so the only varying speeds are
observed, therefore
creating the parallax effect
such as the one you see here.
All right, great, easy,
parallax done, right?
Well, WebKits on IOS cannot
do both momentum scrolling
and create a 3D space
at the same time.
It simply flattens
the 3D space as soon
as it sees overflow
scrolling touch.
And momentum scrolling
is the good kind.
That's the one you want.
It's somewhat a known bug.
It has been reported
a few times.
And unfortunately, there is
no fix in progress for it.
There are some work arounds,
but they are not perfect
and would require some markup
restrictions on the markup that
are not acceptable in AMP.
For instance, the scroller
element cannot be the body
element.
And the parallax element has
to have position of sticky.
So these are not things
we can do in AMP.
If you're still curious about
this approach and a genius
but very complicated
work around for Safari,
please see this
awesome blog post
by the Google Developer Group.
You can find it by just
googling performant parallax.
All right, so what's
the next trick to try?
Well what if you pull or push
an element as the user scrolls?
User scrolls 6 pixels.
We add three more pixels.
So it moves faster.
This can be done via
CSS's transform, which
is hardware-accelerated,
so that's great.
We still need to debalance
the scroll events,
and hit a good balance
for smoothness,
and handle some edge
cases, but it's doable.
Well, some of you may
notice we actually did this.
We launched an
experimental component
called amp-fx-parallax a while
back based on this approach.
So what was the problem?
Why didn't it launch?
Well, essentially,
it over-promised
and under-delivered.
It could not handle
complex parallax scenes.
And parallax is
just a small subset
of what developers wanted
to do with the scroll band
animations on their pages.
We needed a more generic
and powerful approach
that could handle
parallax, but a lot more.
In hindsight, we could
have polished and launched
the experimental component while
working on a more comprehensive
solution, but in
my defense, I also
thought the new solution would
only take five minutes to do.
So what did we end up doing?
So here's an idea--
keyframe animations can
create very complex effects.
So what if we sync the
timeline of an animation
with the position of an
element within the viewport,
essentially tick the animation
with a scroll instead of time.
Here's what I mean.
Here you have two examples.
They're using the exact
same animation code,
but they just are
ticked differently.
The first one goes through its
animation timeline with time.
That's how animations
normally work.
The other one goes through
its animation timeline
as the clock moves
across the viewport.
But how do you do parallax
with keyframe animations?
Well, if at the end of
the animation timeline
an element is requested
to be above or below where
it's supposed to be, it
must move faster or slower
to get there.
In this example, in
normal situation,
the rocket will be at
the top of the viewport
after the user scrolls one
viewport amount of scrolling.
That just makes sense.
You scrolled one
viewport amount,
and you will be on
top of the viewport.
But with the
animation definition,
we are asking it to be half
a viewport, the 50vh here,
more on top.
So it has to move fast to catch
up to get to its final state
that you're asking.
So that's how you do parallax
with keyframe animations.
We have launched this
solution under two
independent but
related components.
AMP animation can be used
to define your effect.
And AMP position
observer can be used
to sync an animation timeline
with a scroll, or more
precisely, with the position of
an element within the viewport,
because that's easier
to reason about than
at absolute ticks of value of
a scroll top, for instance.
But before we get
into the details
of how you can use
these components
and what their API
looks like, let's first
see what you can do with them.
Of course, you can
spin a fidget spinner.
That was the MVP.
But what else can you do?
Maybe you want to have
opacity of an image
to be tied with the scroll,
or maybe not a scroll,
but you want an
image to fade in when
it comes into the
viewport, or maybe
when it comes to the view in
the middle of the viewport,
for instance.
Or both-- you can mix
visibility-based, time-bound
animations with the
scroll-bound animations
to create effects like this.
How about an image
window, or more precisely,
a parallax image window?
You can use these
components on any elements
that are normal HTML elements
or AMP custom elements.
Here we have an
AMP carousel that
slides in the first
time it becomes
visible in the viewport.
And after that it just behaves
like a normal carousel.
It may not be obvious at first,
but these building blocks
can be used to create useful
features that are not just
some fancy visual effects.
For example, you can have
headers that shrink, or expand,
or hide and show when the user
scrolls a little bit off top
of the page.
Similarly, you can show
a scroll-to-top button
that only becomes visible
when the user has scrolled
past a certain threshold.
You can even implement a
snack bar overlaid with it.
This is not possible
with pure CSS.
We tried.
You can probably do it with
amp-bind and CSS, but, hey,
here's another way to do it.
And of course, you
can do all of this
without writing a single
line of JavaScript.
Now, let's get into the details
of the underlying components
that were used to
create these demos.
Amp-animation is a wrapper
for Web Animation API,
but it also adds many new
useful features with it.
For instance, it adds variables.
It adds conditional animations
based on media queries
or support matrix.
It allows for composition
of animations.
And it adds useful functions,
such as calc, random,
or ability to measure width
and height of other elements
and use those values in
your animation definition.
The component also exposes
actions, such as a start,
restart, pause,
and seek to, which
can be used to manipulate the
running state of the animation.
Amp-position-observer is
a functional component
that simply monitors the
position of an element
within the viewport.
And it dispatches enter,
scroll, and exit events
as an element moves
through the viewport.
These events are
considered low-trust,
which means you cannot run
many AMP actions as a result
of these events.
You can only run other
low-trust actions.
And the actions exposed
by AMP animation component
are also low-trust.
And a few actions exposed
by our video players
are also considered low-trust,
so you can run those.
The API is fairly similar to the
native Intersection Observer.
And attributes such
as intersection ratios
and viewport margins can be
used to change the behavior
of its functional components.
So now let's see how
these two work together
to spin a fidget spinner.
Well, first we
define our animation.
It's fairly simple.
We find the fidget
spinner image.
And we take it from 0
degrees through 360 degrees,
so that's the definition here.
Next, we have to define our
scene, our animation scene,
which is simply an image,
but also a position observer
to monitor the position of
the image within the viewport.
Here we are using
intersection ratio one,
which means the
effect only comes--
you only see the
effect when the fidget
spinner is fully visible.
Had we chosen to put
0.5 here, for instance,
the fidget spinner
would have started
spinning when it became
half-visible and would
have stopped when it
became half-invisible.
And this is really
where the magic happens.
This is where we sync
the animation timeline
with the scroll with the
position of the fidget spinner
within the viewport.
So at the beginning, when the
image of the fidget spinner
is at the bottom
of the viewport,
the scroll event
starts dispatching
the value, a number
between 0 and 100,
the percentage of where
it is within the timeline.
So it starts at 0,
goes up, 1%, 2%, 3%.
When it gets to the
middle of the viewport,
it would say I'm
at 50% progress.
I'm in the middle
of the viewport.
And as we get those events,
we tell the animation timeline
to also seek to its
position of 1%, 2%, or 50%.
And 50% happens to be
180 degrees, for example.
And then we get to the top.
They are both at 100%.
And animation will
be at its end.
And it would have
done a full rotation.
Abstraction based on simple
rules can be very powerful.
Allowing syncing of
animation timeline
with any other timeline
opens up other opportunities.
For example, you can
sync a video timeline
with an animation
timeline, creating
overlays that are a
perfectly timed-synced on top
of your videos.
And you can do this today.
And the idea is the same.
As the video goes through its
timeline, it reports progress.
I'm at 10%.
And we ask the
animation to be at 10%.
And the animation is
showing the overlays
at those positions
within its timeline.
And of course there are
other opportunities.
Basically, anything
that can progress,
anything that can
go from 0 to 100,
can be made to sync with
anything else that can
progress and go from 0 to 100.
For instance, we can start
syncing animation timeline
with an AJAX request with the
status of a network request.
Or we can actually try
to sync a video timeline
with a scroll, same way
we did with animations.
So we have a scrollbar
on animations,
why not have a
scrollbar on videos?
We actually tried this.
And unfortunately, all
browsers are very, very slow
at syncing to a particular
frame within the video,
so the overall
scrollbar on video
was not a smooth
experience at all.
A particularly exciting one that
we are planning to launch soon
is ability to sync an animation
timeline with the physical tilt
of a phone.
Imagine you could configure
what 0% and 100% marks
are based on a degree a phone
is tilted on a particular axis.
Now, there are three of them.
And you can have three
animations for each,
and effectively creating
complex visual effects
such as 3D parallax, so look
out for that in the near future.
So are we done?
Well, now we have the building
blocks that cover many use
cases, but they come at a cost.
For simple cases, they are
simply too verbose and not
very simple to use.
And ease of use is something
that we value in AMP,
therefore, we are bringing
some common visual effects.
Although they are already
possible with the existing
building blocks that
we talked about to AMP
under an easy-to-use API
under a new extension
called amp-fx-collection.
And the very first
effect that we
are adding to this collection
going forth full circle
is parallax, which
you can use today,
but we are planning to add
more things such as a slide
in, fade in, and so on.
Here you can see the new
parallax API in practice.
The behavior is similar to the
amp-fx-parallax, which is now
removed, but the
names and the syntax
are a bit different
to allow for expansion
and composition of the effects.
So here, essentially we are
saying, we have an AMP image.
And we want a
parallax effect on it.
We are controlling
how the effect works
using that parallax factor.
If the factor is
greater than 1, it's
going to move faster
as the user scrolls.
And if it's less than
1, it will move slower.
Now let's talk about
some of the boring stuff
for a bit about the
challenges we ran into
and the optimizations with it.
While building these features,
we have made sure that they are
not only very performant,
but in truest frame of AMP,
they cannot be used
in a slow way at all.
We all want 60
frames per second.
That means, when
using amp-animation,
we had to whitelist only
certain properties that
are hardware-accelerated and
blacklist all the other ones.
This means, when you
are using amp-animation,
you can essentially only
animate transform, opacity,
and visibility,
and nothing else,
because those are the ones
that are hardware-accelerated.
This may restrict you
on what you can do,
but at the end of the day,
your UX will be better,
and your users will be happier.
Viewport is another
challenge we ran into.
In the eyes of the browser,
there is no single viewport.
When things are in
an iframe, everything
is relative to that
sandbox window.
But we want the
position of an element
in the real viewport, the
one that the user is actually
looking at.
So we actually
collect information
from different layers
of these viewpoints.
And we coalesce them together
to get the final result
for the viewport that
the user is looking at.
And this is important
for AMP, because AMP
is a portable content format.
It can be in
cross-origin iframes,
same-origin iframes,
Shadow Dom, and of course,
as a top-level document.
Web Animation API itself
is a fairly new API
and not all the
browsers support it.
And the ones that do have
partial support a lot of times.
So we have researched
and found a good polyfill
for web animations
that's included
in the amp-animation components.
But finally, and
most importantly,
animations are not transitions.
Animations go from
a known-state,
from a known first keyframe
to a known last keyframe,
but transitions go from
the current state--
whatever that might be; it's
not known ahead of time--
to a final state.
So unlike web animations
and unlike CSS animations,
amp-animation does
not require you
to provide a first keyframe.
And if you don't, we will
calculate that for you.
This single feature
alone provides
significant
flexibility on what you
can do with
amp-animations in terms
of creating actual transitions.
Building this has been
a big team effort.
And on behalf of
everyone involved,
I hope you find these
features useful.
And we can't wait to see
what you build with them.
Thank you.
[APPLAUSE]
[MUSIC PLAYING]
