Basics of scrollbound effects
Introduction
amp-position-observer combined with amp-animation is a powerful building block that can handle various uses-cases such as scrollbound animations, parallax effects and transitions as elements enter and exit the viewport.
In this tutorial, we will go through some of these use-cases in detail.
Setup
amp-position-observer is a functional component that monitors the position of an element within the viewport as a user scrolls, and dispatches enter
, exit
and scroll:<Position In Viewport As a Percentage>
events.
These events in return can be used to play
, pause
or seek
animation timelines defined by amp-animation to create scrollbound and visibility-based effects.
<script async custom-element="amp-position-observer" src="https://cdn.ampproject.org/v0/amp-position-observer-0.1.js"></script>
amp-animation is a UI component that relies on Web Animations API to define and run keyframe animations in AMP documents.
<script async custom-element="amp-animation" src="https://cdn.ampproject.org/v0/amp-animation-0.1.js"></script>
Styles
The CSS used for these samples are included here for reference.
These rules are simply needed to make the samples work but are not fundamental to the concepts covered here.
<style amp-custom>
/*
* Fidget Spinner styles
*/
.fidget-spinner-scene {
margin: 10px 20%;
}
/*
* Card transition styles
*/
.card {
margin: 10%;
position: relative;
}
.card-title {
padding: 5%;
padding-right: 40%;
font-size: 50%;
font-weight: bold;
color: #FAFAFA;
transform: translateX(-100%);
}
/*
* Parallax window styles
*/
.parallax-image-window {
overflow: hidden;
}
.parallax-image-window amp-img {
margin-bottom: -30%;
}
/*
* Carousel transition styles
*/
.carousel-component {
margin-right: -60%;
margin-left: 60%;
}
.carousel-parent {
overflow: hidden;
}
</style>
Scrollbound animations
Let's create a fidget spinner that rotates as user scrolls the page.
This sample showcases the core concept behind combining amp-position-observer
and amp-animation
: The ability to progress through a keyframe animation timeline as an element progresses through the viewport via scrolling.
Our fidget spinner scene is a div
with an image
. We add an amp-position-observer
element as child of the scene to monitor its progress through the viewport. Let's take a look at the details:
on:scroll
: This event is triggered by position observer as the position of the scene is changed when user scrolls. The event provides a percentage value (decimal between 0 and 1) representing how far the scene is between the start and end of its progress through the viewport.spinAnimation.seekTo(percent=event.percent)
: We will define anamp-animation
that will do the spinning in the next step, here we are couplingamp-position-observer
andamp-animation
by triggering aseekTo
action on the animation asscroll
events occurs. This is how we specify that we like to progress through the animation timeline as the scene progresses through the viewport via scrolling.intersection-ratios
: Defines how much of the scene should be visible in the viewport beforeamp-position-observer
triggers any of its events. Here, we like the spinning to happen only when fidget spinner is fully visible so we set it to1
.
<div class="fidget-spinner-scene">
<amp-position-observer on="scroll:spinAnimation.seekTo(percent=event.percent)" intersection-ratios="1" layout="nodisplay">
</amp-position-observer>
<amp-img id="fidgetSpinnerImage" width="1024" height="1114" layout="responsive" src="/static/samples/img/fidget.png" alt="Picture of a fidget spinner"></amp-img>
</div>
Now we need to define the animation itself:
Fairly straightforward in this case, we want fidgetSpinnerImage
to rotate 360 degrees, so we just define a "transform": "rotate(360deg)"
as the last (and only) keyframe. Let's take a look at the details:
id="spinAnimation"
: We need to give the animation an Id so we can reference it from postion observer."duration": "1"
: The value ofduration
is irrelevant in this case since we progress through the timeline via scrolling so we just set it to1
"direction": "reverse"
: This is needed due an iOS bug with Web Animations."animations"
: Here we can define one or more keyframe-based animations. In our case, we only need one."selector"
:"#fidgetSpinnerImage"
is the selector that targets the fidget spinner for the animation."keyframes":
We define a"transform": "rotate(360deg)"
as the last (and only) keyframe. Note thatamp-animation
automatically fills the first keyframe if not provided.
amp-animation
has plenty of other features, please see the API reference to learn more about amp-animations
.
<amp-animation id="spinAnimation" layout="nodisplay">
<script type="application/json">
{
"duration": "1",
"fill": "both",
"direction": "reverse",
"animations": [
{
"selector": "#fidgetSpinnerImage",
"keyframes": [
{ "transform": "rotate(360deg)" }
]
}
]
}
</script>
</amp-animation>
Fade & Slide Transitions
Now that we have learned the basic core concepts behind amp-position-observer
and amp-animation
, let's dive into more creative ways they can be combined to create interesting transitions.
In this sample, we will combine timebound and scrollbound transitions together to create an effect where the opacity of the card is tied to how much of it is visible in the viewport (scrollbound) and the title of the card animates in/out (timebound) as the card enters and exits the viewport.
Our card scene is simply composed of an image and an overlayed title. Here we define two position observers with different intersection-ratios
values:
- The first one will control the opacity of the image as user scrolls.
- The second one will run a timebound slide animation for the title when the scene becomes mostly visible (80%) and runs it again "in reverse" when scene exits the viewport a bit (20%).
<div class="card ampstart-card">
<amp-position-observer on="scroll:fadeTransition.seekTo(percent=event.percent)" intersection-ratios="0" layout="nodisplay">
</amp-position-observer>
<amp-position-observer on="enter:slideTransition.start; exit:slideTransition.start,slideTransition.reverse" intersection-ratios="0.8" layout="nodisplay">
</amp-position-observer>
<amp-fit-text layout="fill">
<div class="card-title">
Organic, fresh tomatoes and pasta!
</div>
</amp-fit-text>
<amp-img id="cardImage" width="1280" height="898" layout="responsive" src="/static/samples/img/food.jpg" alt="Picture of food table."></amp-img>
</div>
Let's define the keyframes for the scrollbound fade in/out transition.
We target the #cardImage
and define keyframes in a way that image gains full opacity within the first 40% of the timeline and starts fading out in the last 40% of the timeline.
Note that since the position observer controlling this animation has intersection-ratios
set to 0
, we go through the full timeline when user scrolls ViewportHeight + 2 * SceneHeight
amount of pixels.
<amp-animation id="fadeTransition" layout="nodisplay">
<script type="application/json">
{
"duration": "1",
"fill": "both",
"direction": "reverse",
"animations": [
{
"selector": "#cardImage",
"keyframes": [
{ "opacity": "0.3", "offset": 0 },
{ "opacity": "1", "offset": 0.4 },
{ "opacity": "1", "offset": 0.6 },
{ "opacity": "0.3", "offset": 1 }
]
}
]
}
</script>
</amp-animation>
For the slide in/out effect of the title, we just define a 500ms animation that will move the title along the X-axis.
This animation will simply be triggered (either in normal or reverse directions) via the second position observer when the scene is mostly visible/invisible.
<amp-animation id="slideTransition" layout="nodisplay">
<script type="application/json">
{
"duration": "500ms",
"fill": "both",
"easing": "ease-out",
"iterations": "1",
"animations": [
{
"selector": ".card-title",
"keyframes": [
{ "transform": "translateX(-100%)" },
{ "transform": "translateX(0)" }
]
}
]
}
</script>
</amp-animation>
Parallax Image Window
Parallax is another effect that is possible with combining amp-animation
and amp-position-observer
.
Parallax normally involves translating an element on the Y-axis as user scrolls.
Here we define a scene that has a smaller height than the image inside of it, as user scrolls, we move the image creating a parallax window into the image.
<div class="parallax-image-window">
<amp-position-observer on="scroll:parallaxTransition.seekTo(percent=event.percent)" intersection-ratios="0" layout="nodisplay">
</amp-position-observer>
<amp-img id="parallaxImage" width="1280" height="873" layout="responsive" src="/static/samples/img/elephant.jpg" alt="Picture of an elephant"></amp-img>
</div>
The animation itself simply moves the image up via "transform": "translateY(-30%)"
<amp-animation id="parallaxTransition" layout="nodisplay">
<script type="application/json">
{
"duration": "1",
"fill": "both",
"direction": "reverse",
"animations": [
{
"selector": "#parallaxImage",
"keyframes": [
{ "transform": "translateY(-30%)" }
]
}
]
}
</script>
</amp-animation>
Carousel Transition
We can also use these effects with other AMP components such as amp-carousel
.
In this sample, we have a carousel where the first item is pushed to the right and when carousel becomes visible, it snaps back into place providing a visual "hint" that the carousel is horizontally scrollable.
<div class="carousel-parent">
<amp-carousel class="carousel-component" height="300" layout="fixed-height" type="carousel">
<amp-position-observer on="enter:carouselTransition.start" intersection-ratios="0.8" layout="nodisplay">
</amp-position-observer>
<amp-img src="https://unsplash.it/800/600?image=1003" width="400" height="300" alt="a sample image"></amp-img>
<amp-img src="https://unsplash.it/800/600?image=1043" width="400" height="300" alt="another sample image"></amp-img>
<amp-img src="https://unsplash.it/800/600?image=1032" width="400" height="300" alt="and another sample image"></amp-img>
</amp-carousel>
</div>
Here we define our timebound animation with a delay of 200ms which will slide the carousel to the left within 500ms.
This animation will only be triggered once by the position observer as defined above.
<amp-animation id="carouselTransition" layout="nodisplay">
<script type="application/json">
{
"duration": "500ms",
"fill": "both",
"easing": "ease-in",
"delay": "200ms",
"animations": [
{
"selector": ".carousel-component",
"keyframes": [
{ "transform": "translateX(-60%)" }
]
}
]
}
</script>
</amp-animation>
如果此页面上的说明未能涵盖您的所有问题,欢迎与其他 AMP 用户取得联系,讨论您的具体用例。
前往 Stack Overflow 一项无法解释的功能?AMP 项目强烈鼓励您参与并做出贡献!我们希望您能成为我们开放源代码社区的持续参与者,但我们也欢迎您对所热衷问题做出一次性贡献。
编辑 GitHub 上的示例-
Written by @aghassemi