vue路由动画平滑滚动_如何将带有内部图像动画的平滑滚动添加到网页

vue路由动画平滑滚动

vue路由动画平滑滚动

ScrollAnimation_featured

Today we want to show you how to add smooth scrolling in HTML with some additional subtle animations on images. With “smooth scrolling” we don’t mean smoothly scrolling to an element, but rather a smoothly animated kind of scrolling behavior. There are many beautiful examples of such smooth scrolling behavior on some recent websites, like Elena Iv-skaya, or Ada Sokół and the stunning site of Rafal Bojar, and many many others. The latter also has a very nice image animation that is synced with the scrolling. This kind of “inner” image animation adds another interesting layer to the whole scroll movement.

今天,我们想向您展示如何在HTML上添加平滑滚动以及在图像上添加一些其他细微动画的功能。 对于“平滑滚动”,我们并不是指平滑地滚动到某个元素,而是平滑地动画化的滚动行为。 有这样的平滑滚动行为对最近的一些网站,如许多美丽的例子埃莱娜IV-skaya ,或阿达索科尔和令人惊叹的网站拉法尔Bojar ,和许多其他许多人。 后者还具有与滚动同步的非常漂亮的图像动画。 这种“内部”图像动画为整个滚动运动增加了另一个有趣的层。

Why is this kind of smooth scrolling something you’d like to add to a web page? If you have ever animated something on scroll, you might have experienced that browsers have difficulties in displaying the incoming content jank-free; especially images may show tiny abrupt jumps on scroll. It just feel easy on the eye. To avoid that, we can use the trick of animating the content itself by translating it up or down instead of using the “native” scroll.

为什么您希望将这种平滑滚动的内容添加到网页? 如果您曾经在滚动上播放过某些动画,则可能会遇到浏览器难以显示无垃圾的传入内容的麻烦; 特别是图像可能会显示滚动上的微小突变。 感觉很轻松。 为了避免这种情况,我们可以通过上下转换内容来使内容本身具有动画效果,而不是使用“本机”滚动。

平滑滚动 (Smooth Scrolling)

Jesper Landberg created some really great Codepen demos showcasing how smooth scrolling can be applied to different scenarios. The Smooth scroll with skew effect demo shows how to add a skew effect to images while (smooth) scrolling. Here you can also see how smooth scrolling with translating the content works: a content wrapper is set to position fixed with the overflow set to hidden so that its child can be moved. The body will get the height of the content set to it, so that we preserve the scroll bar. When we scroll, the fixed wrapper will stay in place while we animate the inner content. This trick makes a simple yet effective smooth scrolling behavior possible.

Jesper Landberg创建了一些非常出色的Codepen演示,展示了如何将平滑滚动应用于不同场景。 带有偏斜效果的平滑滚动”演示演示了如何在(平滑)滚动时向图像添加偏斜效果。 在这里,您还可以看到平滑地滚动内容的工作原理:将内容包装器设置为固定位置,而将溢出设置为隐藏,以便可以移动其子级。 主体将获得为其设置的内容的高度,以便我们保留滚动条。 滚动时,在对内部内容进行动画处理时,固定的包装将保留在原位。 此技巧使简单而有效的平滑滚动行为成为可能。

In our example we’ll use the following structure:

在我们的示例中,我们将使用以下结构:

<body class="loading">
	<main>
		<div data-scroll>
			<!-- ... --->
		</div>
	</main>
</body>

The main element will serve as fixed, or “sticky”, container while the [data-scroll] div will get translated.

主要元素将用作固定或“粘性”容器,而[data-scroll] div将被翻译。

内部图像动画 (Inner Image Animation)

For the inner image animation we need an image and a parent container that has its overflow set to “hidden”. The idea is to move the image up or down while we scroll. We will work with a background image on a div so that we can control the overflow size better. Mainly, we need to make sure that the image div is bigger than its parent. This is our markup:

对于内部图像动画,我们需要一个图像和一个其溢出设置为“隐藏”的父容器。 想法是在滚动时上下移动图像。 我们将在div上使用背景图片,以便我们可以更好地控制溢出大小。 主要是,我们需要确保image div大于其父级。 这是我们的标记:

<div class="item">
	<div class="item__img-wrap"><div class="item__img"></div></div>
	<!-- ... --->
</div>

Let’s set the styles for these elements. We will use a padding instead of a height so that we can set the right aspect ratio for the inner div which will have the image as background. For this, we use an aspect ratio variable so that we simply need to set the image width and height and leave the calculation to our stylesheet. Read more about this and other brilliant techniques in Apect Ratio Boxes on CSS-Tricks.

让我们为这些元素设置样式。 我们将使用填充而不是高度,以便我们可以为内部div设置正确的宽高比,该内部div将图像作为背景。 为此,我们使用宽高比变量,因此我们只需要设置图像的宽度和高度,并将计算留给样式表即可。 在CSS-Tricks的纵横比框中阅读有关此技术和其他出色技术的更多信息。

We set an image variable for the background image in the item__img-wrap class so that we don’t have to write too many rules. This does not need to be done like that, of course, especially if you’d like support for older browsers that don’t know what variables are. Set it to the item__img directly as background-image instead, if that’s the case.

我们在item__img-wrap类中为背景图像设置了图像变量,这样就不必编写太多规则。 当然,不需要那样做,尤其是当您希望支持不知道变量是什么的旧版浏览器时。 如果是这种情况,请将其直接设置为item__img作为背景图像。

.item__img-wrap {
	--aspect-ratio: 1/1.5;
	overflow: hidden;
	width: 500px;
	max-width: 100%;
	padding-bottom: calc(100% / (var(--aspect-ratio))); 
	will-change: transform;
}

.item:first-child .item__img-wrap {
	--aspect-ratio: 8/10;
	--image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/1.jpg);
}

.item:nth-child(2) .item__img-wrap {
	width: 1000px;
	--aspect-ratio: 120/76;
	--image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/2.jpg);
}

...

The div with the background image is the one we want to move up or down on scroll, so we need to make sure that it’s taller than its parent. For that, we define an “overflow” variable that we’ll use in a calculation for the height and the top. We set this variable because we want to be able to easily change it in some modified classes. This allows us to set a different overflow to each image which changes the visual effect subtly.

带有背景图片的div是我们要在滚动条上上下移动的div,因此我们需要确保其高度高于其父图片。 为此,我们定义了一个“溢出”变量,该变量将用于计算高度和顶部。 设置此变量是因为我们希望能够在某些已修改的类中轻松对其进行更改。 这使我们可以为每个图像设置不同的溢出,从而巧妙地改变视觉效果。

.item__img {
	--overflow: 40px;
	height: calc(100% + (2 * var(--overflow)));
	top: calc( -1 * var(--overflow));
	width: 100%;
	position: absolute;
	background-image: var(--image);
	background-size: cover;
	background-position: 50% 0%;
	will-change: transform;
}

.item__img--t1 {
	--overflow: 60px;
}

.item__img--t2 {
	--overflow: 80px;
}

.item__img--t3 {
	--overflow: 120px;
}

Now, let’s do the JavaScript part. Let’s start with some helper methods and variables.

现在,让我们来做JavaScript部分。 让我们从一些辅助方法和变量开始。

const MathUtils = {
    // map number x from range [a, b] to [c, d]
    map: (x, a, b, c, d) => (x - a) * (d - c) / (b - a) + c,
    // linear interpolation
    lerp: (a, b, n) => (1 - n) * a + n * b
};

const body = document.body;

We will need to get the window’s size, specifically it’s height, for later calculations.

我们将需要获取窗口的大小,特别是它的高度,以便以后进行计算。

let winsize;
const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight};
calcWinsize();

We will also need to recalculate this value on resize.

我们还需要在调整大小时重新计算该值。

window.addEventListener('resize', calcWinsize);

Also, we need to keep track of how much we scroll the page.

另外,我们需要跟踪滚动页面的数量。

let docScroll;
const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop;
window.addEventListener('scroll', getPageYScroll);

Now that we have these helper functions ready, let’s get to the main functionality. Let’s create a class for the smooth scrolling functionality.

现在我们已经准备好这些帮助程序功能,让我们开始主要功能。 让我们为平滑滚动功能创建一个类。

class SmoothScroll {
    constructor() {
        this.DOM = {main: document.querySelector('main')};
        this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]');
        this.items = [];
        [...this.DOM.main.querySelectorAll('.content > .item')].forEach(item => this.items.push(new Item(item)));
        
        ...
    }
}

new SmoothScroll();

So far we have a reference to the main element (the container that needs to become “sticky”) and the scrollable element (the one we will be translating to simulate the scroll).

到目前为止,我们已经引用了main元素(需要变得“粘滞”的容器)和可滚动元素(我们将要翻译的元素来模拟滚动)。

Also, we create an array of our item’s instances. We will get to that in a moment.

另外,我们创建项目实例的数组。 稍后我们将解决这个问题。

Now, we want to update the translateY value as we scroll but we might as well want to update other properties like the scale or rotation. Let’s create an object that stores this configuration. For now let’s just set up the translationY.

现在,我们想在滚动时更新translateY值,但也可能希望更新其他属性,例如缩放或旋转。 让我们创建一个存储此配置的对象。 现在,让我们设置translationY。

constructor() {
    ...

    this.renderedStyles = {
        translationY: {
            previous: 0, 
            current: 0, 
            ease: 0.1,
            setValue: () => docScroll
        }
    };
}

We will be using interpolation to achieve the smooth scrolling effect. The “previous” and “current” values are the values to interpolate. The current translationY will be a value between these two values at a specific increment. The “ease” is the amount to interpolate. The following formula calculates our current translation value:

我们将使用插值来实现平滑的滚动效果。 “上一个”和“当前”值是要插值的值。 当前translationY将是这两个值之间以特定增量的值。 “缓动”是要内插的数量。 以下公式计算我们当前的转换值:

previous = MathUtils.lerp(previous, current, ease)

The setValue function sets the current value, which in this case will be the current scroll position. Let’s go ahead and execute this initially on page load to set up the right translationY value.

setValue函数设置当前值,在这种情况下,它将是当前滚动位置。 让我们继续并首先在页面加载时执行此操作,以设置正确的translationY值。

constructor() {
    ...

    this.update();
}

update() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue();
    }   
    this.layout();
}

layout() {
    this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`;
}

We set both interpolation values to be the same, in this case the scroll value, so that the translation gets set immediately without an animation. We just want the animation happening when we scroll the page. After that, we call the layout function which will apply the transformation to our element. Note that the value will be negative since the element moves upwards.

我们将两个插值设置为相同,在这种情况下为滚动值,以便立即设置平移而无需动画。 我们只是希望动画在滚动页面时发生。 之后,我们调用layout函数,它将转换应用到我们的元素。 注意,由于元素向上移动,该值将为负。

As for the layout changes, we need to:

至于布局更改,我们需要:

  • set the position of the main element to fixed and the overflow to hidden so it sticks to the screen and doesn’t scroll.

    main元素的位置设置为固定,将溢出设置为隐藏,以便其粘贴在屏幕上并且不会滚动。

  • set the height of the body in order to keep the scrollbar on the page. It will be the same as the scrollable element’s height.

    设置主体的高度,以使滚动条保持在页面上。 它与可滚动元素的高度相同。
constructor() {
    ...

    this.setSize();
    this.style();
}

setSize() {
    body.style.height = this.DOM.scrollable.scrollHeight + 'px';
}

style() {
    this.DOM.main.style.position = 'fixed';
    this.DOM.main.style.width = this.DOM.main.style.height = '100%';
    this.DOM.main.style.top = this.DOM.main.style.left = 0;
    this.DOM.main.style.overflow = 'hidden';
}

We also need to reset the body’s height on resize:

我们还需要在调整大小时重置身体的高度:

constructor() {
    ...

    this.initEvents();
}

initEvents() {
    window.addEventListener('resize', () => this.setSize());
}

Now we start our loop function that updates the values as we scroll.

现在,我们开始循环功能,在滚动时更新值。

constructor() {
    ...

    requestAnimationFrame(() => this.render());
}

render() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].setValue();
        this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease);
    }
    this.layout();
    
    // for every item
    for (const item of this.items) {
        // if the item is inside the viewport call it's render function
        // this will update the item's inner image translation, based on the document scroll value and the item's position on the viewport
        if ( item.isVisible ) {
            item.render();
        }
    }
    
    // loop..
    requestAnimationFrame(() => this.render());
}

The only new thing here is the call to the item’s render function which is called for every item that is inside the viewport. This will update the translation of the item’s inner image as we will see ahead.

这里唯一的新事物是对项目的render函数的调用,该函数针对视口内的每个项目都被调用。 正如我们将在后面看到的,这将更新项目内部图像的翻译。

Since we rely on the scrollable element’s height, we need to preload the images so they get rendered and we get to calculate the right value for the height. We are using the imagesLoaded to achieve this:

由于我们依赖于可滚动元素的高度,因此我们需要预加载图像,以便对其进行渲染,然后计算出正确的高度值。 我们正在使用imagesLoaded实现此目的:

const preloadImages = () => {
    return new Promise((resolve, reject) => {
        imagesLoaded(document.querySelectorAll('.item__img'), {background: true}, resolve);
    });
};

After the images are loaded we remove our page loader, get the scroll position (this might not be zero if we scrolled the page before the last refresh) and initialize our SmoothScroll instance.

加载图像后,我们删除页面加载器,获取滚动位置(如果在上次刷新之前滚动页面,则滚动位置可能不为零)并初始化SmoothScroll实例。

preloadImages().then(() => {
    document.body.classList.remove('loading');
    // Get the scroll position
    getPageYScroll();
    // Initialize the Smooth Scrolling
    new SmoothScroll(document.querySelector('main'));
});

So now that the SmoothScroll is covered let’s create an Item class to represent each of the page items (the images).

因此,既然涵盖了SmoothScroll,让我们创建一个Item类来表示每个页面项(图像)。

class Item {
    constructor(el) {
        this.DOM = {el: el};
        this.DOM.image = this.DOM.el.querySelector('.item__img');
        
        this.renderedStyles = {
            innerTranslationY: {
                previous: 0, 
                current: 0, 
                ease: 0.1,
                maxValue: parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10),
                setValue: () => {
                    const maxValue = this.renderedStyles.innerTranslationY.maxValue;
                    const minValue = -1 * maxValue;
                    return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, minValue, maxValue), maxValue), minValue)
                }
            }
        };
    }
    ...
}

The logic here is identical to the SmoothScroll class. We create a renderedStyles object that contains the properties we want to update. In this case we will be translating the item’s inner image (this.DOM.image) on the y-axis. The only extra here is that we are defining a maximum value for the translation (maxValue). This value we’ve previously set in our CSS variable –overflow. Also, we assume the minimum value for the translation will be -1*maxVal.

这里的逻辑与SmoothScroll类相同。 我们创建一个renderStyleStyles对象,其中包含我们要更新的属性。 在这种情况下,我们将在y轴上平移项目的内部图像(this.DOM.image)。 这里唯一的额外之处是我们正在定义转换的最大值(maxValue)。 我们先前在CSS变量–overflow中设置了此值。 此外,我们假设转换的最小值为-1 * maxVal。

The setValue function works as follows:

setValue函数的工作方式如下:

  • When the item’s top value (relative to the viewport) equals the window’s height (item just came into the viewport) the translation is set to the minimum value.

    当项目的最高值(相对于视口)等于窗口的高度(项目刚进入视口)时,平移设置为最小值。
  • When the item’s top value (relative to the viewport) equals “-item’s height” (item just exited the viewport) the translation is set to the maximum value.

    当项目的最高值(相对于视口)等于“-项目的高度”(项目刚刚退出视口)时,平移将设置为最大值。

So basically we are mapping the item’s top value (relative to the viewport) from the range [window’s height, -item’s height] to [minVal, maxVal].

因此,基本上,我们是在[窗口的高度,-item的高度]到[minVal,maxVal]的范围内映射项目的最高值(相对于视口)。

Next thing to do is setting the initial values on load. We also calculate the item’s height and top since we’ll need those to apply the function described before.

接下来要做的是在加载时设置初始值。 我们还需要计算商品的高度和顶部,因为我们需要那些商品来应用上述功能。

constructor(el) {
    ...
    
    this.update();
}

update() {
    this.getSize();
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue();
    }
    this.layout();
}

layout() {
    this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`;
}

getSize() {
    const rect = this.DOM.el.getBoundingClientRect();
    this.props = {
        height: rect.height,
        top: docScroll + rect.top 
    }
}

We need the same for when the window gets resized:

调整窗口大小时,我们需要这样做:

initEvents() {
    window.addEventListener('resize', () => this.resize());
}
resize() {
    this.update();
}

Now we need to define the render function called inside the SmoothScroll render loop function (requestAnimationFrame):

现在我们需要定义在SmoothScroll渲染循环函数(requestAnimationFrame)内部调用的渲染函数:

render() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].setValue();
        this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease);
    }
    this.layout();
}

This, as mentioned before, is only executed for items that are inside of the viewport. We can achieve this by using the IntersectionObserver API:

如前所述,这仅对视口内部的项目执行。 我们可以使用IntersectionObserver API实现此目的:

constructor(el) {
    ...

    this.observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0);
    });
    this.observer.observe(this.DOM.el);

    ...
}

And that’s it!

就是这样!

We hope you enjoyed this tutorial and find it useful!

我们希望您喜欢本教程并发现它有用!

翻译自: https://tympanus.net/codrops/2019/07/10/how-to-add-smooth-scrolling-with-inner-image-animations-to-a-web-page/

vue路由动画平滑滚动

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值