img-polaroid
Today we’d like to show you how to create a very simple intro effect similar to the one seen on the takeit website where a stack of Polaroids gets animated to a grid when clicking on a button or when scrolling. On their site, the grid continues to get animated throughout the page but we want to show you in detail on how to achieve that one specific Polaroid-to-grid effect with all its trickiness.
今天,我们想向您展示如何创建一个非常简单的介绍性效果,类似于在Takeit网站上看到的那种效果,在该效果中,当单击按钮或滚动时,一堆宝丽来动画成一个网格。 在他们的网站上,网格继续在整个页面上变得生动起来,但是我们想向您详细介绍如何实现一种特定的“拍立得到网格”效果及其所有技巧。
So, let’s get started!
所以,让我们开始吧!
规划效果 (Planning the effect)
Check out the demo and get familiar with all the elements present and with the effect that happens if you click the arrow button on the phone or if you scroll.
查看演示,并熟悉所有存在的元素以及单击电话上的箭头按钮或滚动时会发生的效果。
We have an intro “page” that contains a big headline, the Polaroid stack and a device with a button. We also have a header with some links. What we want to do is to animate the Polaroid stack to a grid layout and make all the other elements, except the header links, disappear with a neat animation. This will be triggered either by scrolling down or by clicking the arrow.
我们有一个介绍性“页面”,其中包含一个大标题,宝丽来堆栈和一个带按钮的设备。 我们也有带有一些链接的标题。 我们要做的是将宝丽来堆栈动画化为网格布局,并使除标题链接之外的所有其他元素消失,并带有整洁的动画。 这将通过向下滚动或单击箭头来触发。
As with many effects, the best way is to think of the end state as default state. So, we won’t take a stack of Polaroids and calculate their positions in the final grid state but instead the grid state is their default state and the initial scattered position is something we set them to in the beginning. We’ll use the first six images and move them to a common center point and then scatter them a bit with the help of a factor that adds the illusion of randomness. Do we need to move the images up to some previous section? No, actually we don’t. We can use a fixed intro section that we’ll instead animate up, outside of the viewport, giving the illusion as if we are moving (the grid) down.
与许多效果一样,最好的方法是将结束状态视为默认状态。 因此,我们不会堆叠宝丽来来计算它们在最终网格状态下的位置,而是将网格状态作为它们的默认状态,而初始分散位置是我们在开始时设置的位置。 我们将使用前六个图像并将它们移动到一个公共的中心点,然后借助增加随机性错觉的因素将其散布一些。 我们是否需要将图像移至上一部分? 不,实际上我们不知道。 我们可以使用固定的介绍部分,而将其设置为在视口之外进行动画处理,从而给人一种幻象,就像我们向下移动(网格)一样。
Since we will have many animations happening, we want to have solid, individual control over them. A library like dynamic.js will help us animate the elements, so we will control the crucial animations in our script.
由于我们将发生许多动画,因此我们希望对其进行可靠的单独控制。 像dynamic.js这样的库将帮助我们对元素进行动画处理,因此我们将控制脚本中的关键动画。
Most of the layout will be powered by flexbox. Read this guide by Sara Soueidan from the CSS Reference to understand all its great properties.
大多数布局将由flexbox提供支持。 阅读CSS参考手册中Sara Soueidan撰写的本指南,以了解其所有出色的属性。
The images we are going to use in the demo are from Unsplash.com.
我们将在演示中使用的图像来自Unsplash.com 。
Let’s start writing the markup!
让我们开始编写标记!
标记 (The Markup)
How are we going to make our effect look as if we are scrolling down the page and moving around the elements without actually scrolling? We can simply trick the user’s eye by moving a fixed background outside of the viewport, creating the illusion that we moved down in the document. For that we will create a section with the class page and the “modifier” page–mover. Inside we’ll only have a loader. Check out more CSS loaders on Load Awesome by Daniel Cardoso.
我们如何使效果看起来像是向下滚动页面并在元素之间移动而实际上却没有滚动? 我们可以通过将固定背景移到视口之外来创建用户在文档中向下移动的错觉,从而简单地欺骗用户的视线。 为此,我们将创建一个包含类页面和“修饰符”页面移动器的部分。 里面只有一个装载机。 在Daniel Cardoso的Load Awesome上查看更多CSS加载器。
The main title will not go inside of that page section because we want to animate it differently. If we’d put it inside we’d have to consider the parent’s movement and we don’t want that. So we create a own division for the main title with the class title-wrap.
主标题不会进入该页面部分,因为我们希望对其进行不同的动画处理。 如果我们把它放进去,我们将不得不考虑父母的运动,而我们不希望那样。 因此,我们使用class title-wrap类为主标题创建了自己的分区。
Then we define the section for the grid which will also be a page, but with the modifier page static. There we’ll also have a title wrapper and an unordered list as our grid. The last element in our static page is a button for loading more items (this we won’t implement but it’s ready for a dynamic solution).
然后,为网格定义部分,该部分也将是页面,但修饰符页面为static 。 在这里,我们还将有一个标题包装器和一个无序列表作为我们的网格。 静态页面中的最后一个元素是用于加载更多项目的按钮(我们不会实现,但已为动态解决方案做好了准备)。
The last two elements in our view container are the device and the arrow button to trigger the animation and show the grid.
视图容器中的最后两个元素是设备和触发动画并显示网格的箭头按钮。
<div class="view">
<header class="header">
<!-- some links -->
</header>
<section class="page page--mover">
<div class="la-square-loader"><div></div></div>
</section>
<div class="title-wrap">
<h1 class="title title--main">OH<em>!</em>SNAP</h1>
<p class="title title--sub">Smart Auto-Filtering for your shots.</p>
</div>
<section class="page page--static">
<div class="page__title">
<h2 class="page__title-main">Polaroid Stack to Grid Intro Animation</h2>
<p class="page__title-sub">Recreating the effect seen on the <a href="http://www.takeitapp.co/en">takeit</a> website</p>
</div>
<ul class="grid">
<li class="grid__item">
<a class="grid__link" href="#">
<img class="grid__img" src="img/photos/1.jpg" alt="Some image" />
<h3 class="grid__item-title">Natural saturation effects</h3>
</a>
</li>
<li class="grid__item"><!-- ... --></li>
<li class="grid__item"><!-- ... --></li>
<li class="grid__item"><!-- ... --></li>
<!-- ... -->
</ul>
<button class="button button--load" aria-label="Load more images">
<svg class="polaroid" width="100%" height="100%" viewBox="0 0 220 243" preserveAspectRatio="xMidYMid meet">
<rect class="polaroid__base" x="0" y="0" width="220" height="243" rx="5"></rect>
<rect class="polaroid__inner" x="16" y="20" width="189" height="149"></rect>
<g class="polaroid__loader">
<circle cx="61.5" cy="94.5" r="17.5"></circle>
<circle cx="110.5" cy="94.5" r="17.5"></circle>
<circle cx="159.5" cy="94.5" r="17.5"></circle>
</g>
</svg>
<span class="button__text">Load more</span>
</button>
</section>
<div class="device">
<div class="device__screen"></div>
</div>
<button id="showgrid" class="button button--view" aria-label="Show me more">
<svg width="100%" height="100%" viewBox="0 0 310 177" preserveAspectRatio="xMidYMid meet">
<path fill="#FFFFFF" d="M159.875,174.481L306.945,27.41c2.93-2.929,2.93-7.678,0-10.606L292.803,2.661c-1.406-1.407-3.314-2.197-5.303-2.197c-1.989,0-3.896,0.79-5.303,2.197L154.572,130.287L26.946,2.661c-1.406-1.407-3.314-2.197-5.303-2.197c-1.989,0-3.897,0.79-5.303,2.197L2.197,16.804C0.733,18.269,0,20.188,0,22.107s0.732,3.839,2.197,5.303l147.071,147.071C152.197,177.411,156.945,177.411,159.875,174.481L159.875,174.481z" />
</svg>
</button>
</div><!-- /view -->
That’s all the markup, let’s move on to the styles!
这就是所有的标记,让我们继续进行样式!
CSS (The CSS)
Note that we use the necessary -webkit- prefixes for better mobile support and Safari.
请注意,我们使用必要的-webkit-前缀以获得更好的移动支持和Safari。
First, we’ll reset the box-sizing for all elements to border-box:
首先,我们将所有元素的框大小重置为border-box :
*,
*::after,
*::before {
box-sizing: border-box;
}
For the body, we’ll set some typography and colors:
对于身体,我们将设置一些字体和颜色:
body {
font-family: 'Avenir Next', Avenir, 'Helvetica Neue', Helvetica, Arial, sans-serif;
overflow-x: hidden;
color: #a5aeb5;
background: #e9ecef;
}
We will need a helper class that prohibits initial scrolling (while the JavaScript is still loading; then we take care of it in our script):
我们将需要一个帮助程序类,该类禁止初始滚动(在JavaScript仍在加载时;然后在脚本中进行处理):
.js body {
overflow: hidden;
}
.js body.overflow {
overflow: auto;
}
The header element with the links will be positioned absolutely and we’ll use flexbox to lay out the elements inside:
带有链接的header元素将绝对放置,我们将使用flexbox布置内部的元素:
.header {
position: absolute;
z-index: 1000;
display: -webkit-flex;
display: flex;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-align-items: flex-start;
align-items: flex-start;
width: 100%;
padding: 2.5em;
pointer-events: none;
}
With pointer-events set to none you don’t make this element clickable. Inner elements like links will have pointer-events set to auto. This technique can be useful for fixed or absolute elements that should be on top of everything but that are not obtrusive in the parts that are not “needed” to be clickable.
将指针事件设置为none时,您不会使该元素可单击。 链接之类的内部元素会将指针事件设置为auto 。 此技术对于固定或绝对元素很有用,这些元素应该位于所有内容之上,但在不需要“可点击”的部分中不会引起干扰。
Now, let’s define the styles for the movable page. Since this is going to be our illusion artist, we set it to a fixed position and size it to the whole screen. The background is set to a dark color:
现在,让我们定义可移动页面的样式。 由于这将是我们的幻觉艺术家,因此我们将其设置为固定位置并将其大小调整为整个屏幕。 背景设置为深色:
.page--mover {
position: fixed;
width: 100%;
height: 100vh;
pointer-events: none;
background: #2d323c;
}
The title wrap will be positioned at the top part of the page:
标题换行将位于页面顶部:
.title-wrap {
position: absolute;
z-index: 101;
width: 100%;
margin: 10vh 0 0 0;
text-align: center;
pointer-events: none;
}
The typographic styles for the inner elements are as follows:
内部元素的印刷样式如下:
.title {
line-height: 1;
position: relative;
text-indent: 0.2em;
letter-spacing: 0.2em;
text-transform: uppercase;
}
.title--main {
font-size: 5.75em;
margin: 0 auto;
color: #df2d70;
}
.title--sub {
font-size: 1.15em;
font-weight: 700;
display: block;
margin: 0;
color: #565f73;
}
With a media query for larger screens, we ensure that the title elements are sized relatively to the viewport:
对于较大屏幕的媒体查询,我们确保标题元素的尺寸相对于视口:
@media screen and (min-width: 100em) {
.title--main {
font-size: 7vw;
}
.title--sub {
font-size: 1.35vw;
}
}
The static page will have a flexbox column layout that makes sure that everything is neatly centered. We’ll add a maximium allowed width which will be enough space for three full images in a row:
静态页面将具有flexbox列布局,以确保所有内容均居中。 我们将添加最大允许宽度,该宽度将足以容纳连续三张完整图像:
.page--static {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-align-items: center;
align-items: center;
max-width: 1220px;
margin: 0 auto;
padding: 2em 0 0;
text-align: center;
}
The page title elements have the following styles:
页面标题元素具有以下样式:
.page__title {
padding: 0 2em;
}
.page__title-main {
font-size: 2em;
margin: 0 auto;
padding: 3em 0 0;
color: #03a9f4;
}
.page__title-sub {
font-size: 1.05em;
margin: 0.5em 0 4em;
}
Now, let’s style the device. We get the Sketch device from the Facebook devices collection and export the SVG. That will serve as the background image of our device division. This division is fixed with a z-index that will lay it on top of everything. We set the dimension to a square that is relative to the viewport height. This ensures that it never becomes bigger than half of the screen. We center it with the negative margin technique:
现在,让我们对设备进行样式设置。 我们从Facebook设备集合中获取Sketch设备并导出SVG。 这将作为我们设备部门的背景图片。 该分区由z-index固定,它将放置在所有内容之上。 我们将尺寸设置为相对于视口高度的正方形。 这样可以确保它永远不会超过屏幕的一半。 我们以负边距技术为中心:
.device {
position: fixed;
z-index: 1000;
bottom: 0;
left: 50%;
width: 45vh;
height: 45vh;
margin: 0 0 0 -22.5vh;
background: url(../img/device.svg) no-repeat 50% 0%;
background-size: 100%;
}
The screen of the device will contain an app screenshot and its sizes are percentage based, relative to the device:
该设备的屏幕将包含一个应用屏幕截图,并且其大小是基于百分比的,相对于该设备:
.device__screen {
position: absolute;
top: 25.5%;
left: 8.5%;
width: 83%;
height: 100%;
background: url(../img/screen.jpg) no-repeat 50% 0%;
background-size: 100%;
}
Next, let’s style the two buttons, the arrow button and the “load more” button. Since they both have some style resets in common, we define the following styles:
接下来,让我们设置两个按钮的样式,箭头按钮和“加载更多”按钮。 由于它们都有一些共同的样式重置,因此我们定义以下样式:
.button {
margin: 0;
padding: 0;
border: none;
background: none;
}
The view button will be positioned absolutely and we’ll give it a little movement so that it stands out as trigger. We’ll also add some hover transition for the fill:
视图按钮将绝对定位,我们将对其稍加移动,使其脱颖而出。 我们还将为填充添加一些悬停过渡:
.button--view {
position: absolute;
z-index: 1000;
top: 100vh;
left: 50%;
width: 3em;
height: 1.75em;
margin: -3em 0 0 -1.5em;
-webkit-animation: pointDown 0.6s 0.6s ease-in infinite alternate forwards;
animation: pointDown 0.6s 0.6s ease-in infinite alternate forwards;
fill: #fff;
}
@-webkit-keyframes pointDown {
from {
-webkit-transform: translate3d(0,-10px,0);
transform: translate3d(0,-10px,0);
}
to {
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
}
@keyframes pointDown {
from {
-webkit-transform: translate3d(0,-10px,0);
transform: translate3d(0,-10px,0);
}
to {
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
}
.button--view path {
-webkit-transition: fill 0.3s;
transition: fill 0.3s;
fill: inherit;
}
.button--view:hover,
.button--view:focus {
outline: none;
fill: #03a9f4;
}
The load more button also has some text:
加载更多按钮也有一些文本:
.button--load {
width: 7em;
color: #a3b0bd;
}
.button--load svg {
width: 2.5em;
}
.button__text {
font-size: 0.65em;
font-weight: bold;
display: block;
margin: 0.85em 0 0 0;
text-indent: 3px;
letter-spacing: 3px;
text-transform: uppercase;
color: inherit;
-webkit-transition: color 0.3s;
transition: color 0.3s;
}
Besides a hover transition, the little Polaroid will be ready for a loading state that we trigger with the class button–loading. The three little circles will be animated to indicated that images are being loaded:
除了悬停过渡之外,小宝丽来也将为我们通过class button-loading触发的加载状态做好准备。 三个小圆圈将显示为动画,指示正在加载图像:
.polaroid__base,
.polaroid__loader {
-webkit-transition: fill 0.3s;
transition: fill 0.3s;
fill: #a3b0bd;
}
.button--load:hover,
.button--load:focus {
color: #03a9f4;
outline: none;
}
.button:hover .polaroid__base,
.button:focus .polaroid__base,
.button:hover .polaroid__loader,
.button:focus .polaroid__loader {
fill: #03a9f4;
}
.polaroid__inner {
fill: #e9ecef;
}
.button--loading .polaroid__loader circle {
-webkit-animation: fadeInOut 0.3s ease-in infinite alternate forwards;
animation: fadeInOut 0.3s ease-in infinite alternate forwards;
}
.button--loading .polaroid__loader circle:nth-child(2) {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.button--loading .polaroid__loader circle:nth-child(3) {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
@-webkit-keyframes fadeInOut {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInOut {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
Then we will want to hide the button (that’s for when there are no more images to be loaded in your real case scenario):
然后,我们将要隐藏按钮(这是在您的实际案例中没有更多图像要加载时):
.button--hidden {
pointer-events: none;
opacity: 0;
-webkit-transition: opacity 0.3s;
transition: opacity 0.3s;
}
The grid will also be powered by flexbox. The z-index is 100 just so that its underneath the device and the main title:
网格也将由flexbox驱动。 z-index为100,因此它位于设备和主标题下方:
.grid {
position: relative;
z-index: 100;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-justify-content: center;
justify-content: center;
-webkit-align-items: center;
align-items: center;
max-width: 100%;
margin: 0 auto;
padding: 0 0 6em;
list-style: none;
}
We want to control the size of our grid items so they don’t need to be flexible. The width should be one third of the parent’s width. A padding of 10px will create a gutter for the Polaroid images:
我们想要控制网格项目的大小,因此它们不需要灵活。 宽度应为父母宽度的三分之一。 10px的填充将为宝丽来图像创建装订线:
.grid__item {
display: block;
-webkit-flex: none;
flex: none;
width: 33.33%;
padding: 10px;
}
When we insert new items into the grid, we need to control the visibility of the grid items. For this we will create the following helper class:
将新项目插入网格时,需要控制网格项目的可见性。 为此,我们将创建以下帮助程序类:
.grid__item--hidden {
opacity: 0;
}
The link element will be the one with the border decoration, so we add a padding of 13px for all sides except for the bottom side. This part will be styled in the title element.
link元素将是带有边框装饰的元素,因此我们为除底面之外的所有面添加13px的填充。 这部分将在标题元素中设置样式。
.grid__link {
display: block;
padding: 13px 13px 0 13px;
background: #fff;
}
The image will fill all available space:
该图像将填充所有可用空间:
.grid__img {
display: block;
width: 100%;
}
The Polaroid titles will have a nice script font to look like they have been written on:
“宝丽来”标题将具有漂亮的脚本字体,看起来像是这样写的:
.grid__item-title {
font-family: 'Caveat', cursive;
line-height: 1;
position: relative;
overflow: hidden;
margin: 0;
padding: 1em 0.5em;
text-align: left;
white-space: nowrap;
text-overflow: ellipsis;
color: #698b8d;
background: #fff;
}
Note that we have inserted the link to the Google Web font in the head of our HTML.
请注意,我们已经在HTML的标题中插入了指向Google Web字体的链接。
Since we need to preload our images, let’s show a little animated loader that actually reminds of a Polaroid. We’ve used a CSS loader from Load Awesome and adjusted some styles:
由于我们需要预加载图像,因此让我们展示一个实际上使人联想起宝丽来的小动画加载器。 我们使用了Load AwesomeCSS加载器,并调整了一些样式:
/* Loader (styles defined in square-loader.min.css) */
.la-square-loader {
position: absolute;
top: 60%;
left: 50%;
color: #565f73;
opacity: 0;
-webkit-transform: translate3d(-50%,-50%,0) scale3d(1.35,1.35,1);
transform: translate3d(-50%,-50%,0) scale3d(1.35,1.35,1);
}
.la-square-loader > div {
border-radius: 2px;
}
We should never hide anything assuming that JS is available but think about how everything looks/works without it. So, while we are actually preloading the images, we don’t want some elements to be shown. Then, when the images are loaded, we’ll want to show the grid, the device and the arrow button:
我们绝不应该假设JS可用就隐藏任何东西,而要想想如果没有它,一切看起来/如何工作。 因此,尽管实际上是在预加载图像,但我们不想显示某些元素。 然后,在加载图像后,我们将要显示网格,设备和箭头按钮:
.js .la-square-loader {
opacity: 1;
-webkit-transition: opacity 0.3s;
transition: opacity 0.3s;
}
.view--loaded .la-square-loader {
opacity: 0;
}
.js .grid,
.js .device,
.js .button--view {
opacity: 0;
}
.view--loaded .grid,
.view--loaded .device,
.view--loaded .button--view {
opacity: 1;
}
.view--loaded .button--view {
-webkit-transition: opacity 0.3s;
transition: opacity 0.3s;
}
The same holds for the pointer events of the static page:
静态页面的指针事件也是如此:
.js .page--static {
pointer-events: none;
}
.view--grid .page--static {
pointer-events: auto;
}
Finally, we have to tweak our layout a bit for smaller screens:
最后,我们必须对较小的屏幕进行一些调整:
@media screen and (max-width: 56em) {
.header {
padding: 1em;
}
.title-wrap {
font-size: 53%;
margin: 85px 0 0 0;
}
.page__title-main {
font-size: 1.3em;
}
.page__title-sub {
margin-bottom: 1em;
}
.grid__item {
width: 50%;
max-width: none;
}
}
And that are all the styles!
这就是所有样式!
Now, let’s write the magic spells for this dead bird to come to life!
现在,让我们写出这只死鸟复活的魔咒!
JavaScript (The JavaScript)
First, let’s define and initialize some variables:
首先,让我们定义和初始化一些变量:
// main page container
var mainContainer = document.querySelector('.view'),
// the grid element
gridEl = mainContainer.querySelector('.grid'),
// grid items
gridItems = [].slice.call(gridEl.querySelectorAll('.grid__item')),
// main title element
titleEl = mainContainer.querySelector('.title-wrap > .title--main'),
// main subtitle element
subtitleEl = mainContainer.querySelector('.title-wrap > .title--sub'),
// the fullscreen element/division that will slide up, giving the illusion the items will fall down
pagemover = mainContainer.querySelector('.page--mover'),
// the loading element shown while the images are loaded
loadingStatusEl = pagemover.querySelector('.la-square-loader'),
// window sizes (width and height)
winsize = {width: window.innerWidth, height: window.innerHeight},
// translation values (x and y): percentages of the item´s width and height; scale value; rotation (z) value
// these are the values that the 6 initial images will have
introPositions = [
{tx: -.6, ty:-.3, s:1.1, r:-20},
{tx: .2, ty:-.7, s:1.4, r:1},
{tx: .5, ty:-.5, s:1.3, r:15},
{tx: -.2, ty:-.4, s:1.4, r:-17},
{tx: -.15, ty:-.4, s:1.2, r:-5},
{tx: .7, ty:-.2, s:1.1, r:15}
],
// the device
deviceEl = mainContainer.querySelector('.device'),
// the animated button that triggers the effect when clicked
showGridCtrl = document.getElementById('showgrid'),
// the title and subtitle shown on top of the grid
pageTitleEl = mainContainer.querySelector('.page__title > .page__title-main'),
pageSubTitleEl = mainContainer.querySelector('.page__title > .page__title-sub'),
// the grid´s load more button
loadMoreCtrl = mainContainer.querySelector('button.button--load'),
// true if the animation is currently running
isAnimating,
// true if the user scrolls (rather than clicking the down arrow)
scrolled,
// current view: stack | grid
view = 'stack';
introPositions is the array where we can define the positions of each of the six images that are part of the stack. Initially, the images will be positioned on the center of the screen (at the bottom) and then each one will have a specific transform applied based on introPositions. The “tx” and “ty” values are percentage based and define how much of the item’s width/height will be added to the item’s translation. The “s” and “r” represent the scale and rotationZ respectively. If we’d want the stack to appear differently we’ll just need to adjust these array values.
introPositions是一个数组,我们可以在其中定义堆栈中的六个图像中每个图像的位置。 最初,图像将放置在屏幕中央(在底部),然后每个图像都将基于introPositions进行特定的转换。 “ tx”和“ ty”值是基于百分比的,它们定义了将多少项的宽度/高度添加到项的平移中。 “ s”和“ r”分别表示比例和旋转Z。 如果我们希望堆栈以不同的方式出现,我们只需要调整这些数组值即可。
Next, let’s define our init function:
接下来,让我们定义init函数:
function init() {
// disable scroll while loading images
classie.add(document.body, 'overflow');
disableScroll();
// preload images
imagesLoaded(gridEl, function() {
// enable page scroll again
enableScroll();
// controls the visibility of the grid items. Adding this class will make them visible.
classie.add(mainContainer, 'view--loaded');
// show initial view
showIntro();
// bind events
initEvents();
});
}
We’ll preload all the images inside the grid, and only then show the stack of the six first images behind the device. While the images are being loaded we disallow the user to scroll the page, since the act of scrolling will also make the grid appear.
我们将所有图像预加载到网格内,然后才显示设备后面的前六个图像的堆栈。 在加载图像时,我们不允许用户滚动页面,因为滚动操作也会使网格出现。
The showIntro function will position the six images behind the device. The images are first positioned in the center of the page (bottom) and the translation, scale and rotation values set in introPositions are applied to each image. We want that both the images and the device element slide from bottom up once all images are loaded, so we need to position these elements first and then animate them to their final state:
showIntro函数会将六个图像放置在设备后面。 首先将图像放置在页面中心(底部),然后将introPositions中设置的平移,缩放和旋转值应用于每个图像。 加载完所有图像后,我们希望图像和设备元素都从下往上滑动,因此我们需要首先放置这些元素,然后将它们设置为最终状态动画:
function showIntro() {
// display the first set of 6 grid items behind the phone
gridItems.slice(0,6).forEach(function(item, pos) {
// first we position all the 6 items on the bottom of the page
// (item´s center is positioned on the middle of the page bottom)
// then we move them up and to the sides (extra values)
// and also apply a scale and rotation
var itemOffset = item.getBoundingClientRect(),
settings = introPositions[pos],
center = {
x : winsize.width/2 - (itemOffset.left + item.offsetWidth/2),
y : winsize.height - (itemOffset.top + item.offsetHeight/2)
}
// first position the items behind the phone
dynamics.css(item, {
opacity: 1,
translateX: center.x,
translateY: center.y,
scale: 0.5
});
// now animate each item to its final position
dynamics.animate(item, {
translateX: center.x + settings.tx*item.offsetWidth,
translateY: center.y + settings.ty*item.offsetWidth,
scale : settings.s,
rotateZ: settings.r
}, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 1000,
delay: pos * 80
});
});
// also animate/slide the device in:
// first, push it slightly down; to make it disappear completely outside
// of the viewport we´d need to set the translateY to
// winsize.height * 0.45 --> 45vh)
dynamics.css(deviceEl, { translateY: winsize.height * 0.25 } );
// now animate it up
dynamics.animate(deviceEl, { translateY: 0 }, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 1000
});
}
The bind/init events function looks as follows:
bind / init事件函数如下所示:
function initEvents() {
// show the grid when the showGridCtrl is clicked
showGridCtrl.addEventListener('click', showGrid);
// show the grid when the user scrolls the page
var scrollfn = function() {
scrolled = true;
showGrid();
window.removeEventListener('scroll', scrollfn);
};
window.addEventListener('scroll', scrollfn);
// show/load more grid items;
// this is just a simple dummy function that
// simulates the loading of more items into the grid
loadMoreCtrl.addEventListener('click', loadNextItems);
// window resize: recalculate window sizes and reposition
// the 6 grid items behind the device (if the grid view is not yet shown)
window.addEventListener('resize', debounce(function(ev) {
// reset window sizes
winsize = {width: window.innerWidth, height: window.innerHeight};
if( view === 'stack' ) {
gridItems.slice(0,6).forEach(function(item, pos) {
// first reset all items
dynamics.css(item, { scale: 1, translateX: 0, translateY: 0, rotateZ: 0 });
// now, recalculate..
var itemOffset = item.getBoundingClientRect(),
settings = introPositions[pos];
dynamics.css(item, {
translateX: winsize.width/2 - (itemOffset.left + item.offsetWidth/2) + settings.tx*item.offsetWidth,
translateY: winsize.height - (itemOffset.top + item.offsetHeight/2) + settings.ty*item.offsetWidth,
scale : settings.s,
rotateZ: settings.r
});
});
}
}, 10));
}
We will need to define the events for when we click the “show grid” control button (arrow), the page scrolling, the window resize and the loading of more grid items.
单击“显示网格”控制按钮(箭头),页面滚动,窗口调整大小以及加载更多网格项目时,我们需要定义事件。
Now let’s define all the animations we need for showing the grid. Once again, we’ll be using the dynamics.js library which will make this process much easier:
现在,让我们定义显示网格所需的所有动画。 再一次,我们将使用dynamics.js库,它将使此过程变得更加容易:
function showGrid() {
// return if currently animating
if( isAnimating ) return;
isAnimating = true;
// hide the showGrid ctrl
dynamics.css(showGridCtrl, {display: 'none'});
// main title animation
dynamics.animate(titleEl, { translateY: -winsize.height/2, opacity: 0 }, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.7,"y":0}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 600
});
// main subtitle animation
dynamics.animate(subtitleEl, { translateY: -winsize.height/2, opacity: 0 }, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.7,"y":0}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 600,
delay: 100
});
// device animation
dynamics.animate(deviceEl, { translateY: 500, opacity: 0 }, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.7,"y":0}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 600
});
// pagemover animation
dynamics.animate(pagemover, { translateY: -winsize.height}, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.7,"y":0}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 600,
delay: scrolled ? 0 : 120,
complete: function(el) {
// hide the pagemover
dynamics.css(el, { opacity: 0 });
// view is now ´grid´
view = 'grid';
classie.add(mainContainer, 'view--grid');
}
});
// items animation
gridItems.slice(0,6).forEach(function(item, pos) {
dynamics.stop(item);
dynamics.animate(item, { scale: 1, translateX: 0, translateY: 0, rotateZ: 0 }, {
type: dynamics.easeInOut,
duration: 600,
delay: scrolled ? 0 : 120
});
});
// page title animation
dynamics.css(pageTitleEl, { translateY: 200, opacity: 0 });
dynamics.animate(pageTitleEl, { translateY: 0, opacity: 1 }, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 800,
delay: 400
});
// page subtitle animation
dynamics.css(pageSubTitleEl, { translateY: 150, opacity: 0 });
dynamics.animate(pageSubTitleEl, { translateY: 0, opacity: 1 }, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 800,
delay: 500
});
// the remaining grid items
gridItems.slice(6).forEach(function(item) {
dynamics.css(item, { scale: 0, opacity: 0 });
dynamics.animate(item, { scale: 1, opacity: 1 }, {
type: dynamics.bezier,
points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
duration: 800,
delay: randomIntFromInterval(100,400)
});
});
}
We start by hiding the “show grid” control (arrow) and then we animate the following elements: the main title and subtitle, the device, the page–mover division (fullscreen element that slides up giving us the illusion the stack items will fall down), each stack item, the page title and subtitle (the title/subtitle before the grid) and all the other grid elements. Note that these animations follow a specific order; for each one we’ll need to define a specific duration and a delay. Also, if the grid is shown as a result of a page scroll then the delays can be different.
我们首先隐藏“显示网格”控件(箭头),然后为以下元素设置动画:主标题和副标题,设备,页面移动器分区(向上滑动的全屏元素,给我们错觉,就是堆叠项将掉落向下),每个堆栈项目,页面标题和副标题(网格之前的标题/副标题)以及所有其他网格元素。 请注意,这些动画遵循特定的顺序。 对于每一个,我们需要定义一个特定的持续时间和一个延迟。 同样,如果网格是作为页面滚动的结果显示的,则延迟可能会有所不同。
And we are in the end! Don’t forget to have a look at both demos and see how we can tune the effect of the stacked grid items moving to their grid position!
而我们到最后! 不要忘了看两个演示,看看我们如何调整堆叠的网格项目移动到其网格位置的效果!
We hope you enjoyed this tutorial and find it useful!
我们希望您喜欢本教程并发现它有用!
Browser Support: 浏览器支持:ChromeSupported
支援Chrome
FirefoxSupported
Firefox支持
Internet ExplorerSupported from version 11+
11+版本开始支持Internet Explorer
SafariSupported
Safari支持
OperaSupported
歌剧支持
翻译自: https://tympanus.net/codrops/2016/02/17/polaroid-stack-to-grid-intro-animation/
img-polaroid