javascript 滚动_使用JavaScript实现基于滚动的动画

本文档介绍了如何使用纯JavaScript从头开始实现基于滚动的动画,特别关注性能优化,确保即使在移动设备上也能流畅运行。教程涵盖了HTML结构、CSS样式、JavaScript实现动画的详细步骤,包括初始化动画、解决移动设备上的跳转问题,以及实现自定义滚动行为。
摘要由CSDN通过智能技术生成

javascript 滚动

There is a kind of animations that has not stopped increasing its presence in the most modern and original websites: the animations based on the scroll event of Javascript. This trend literally exploded when the parallax effects appeared, and since then its use has become more frequent.

在最现代和原始的网站中,有一种动画一直没有停止增加其存在:基于Java scroll事件的动画。 当视差效应出现时,这种趋势从字面上爆炸了,此后它的使用变得更加频繁。

But the truth is that you must be very careful when implementing an animation based on scroll, since it can seriously affect the performance of the website, especially on mobile devices.

但事实是,在实现基于滚动的动画时必须非常小心,因为它会严重影响网站的性能,尤其是在移动设备上。

That's why we invite you to continue reading the tutorial, where we will implement from scratch and with vanilla Javascript, this beautiful animation based on scroll, also keeping good performance even on mobile devices:

因此,我们邀请您继续阅读该教程,在该教程中我们将从头开始使用Vanilla Javascript进行实现,这种基于滚动的漂亮动画甚至在移动设备上也能保持良好的性能:

Let's start!

开始吧!

HTML结构 ( HTML Structure )

We will use a simple HTML structure, where each image in the design will actually be a div element in the HTML code, and the images will be defined and positioned with CSS, which will facilitate this task:

我们将使用简单HTML结构,其中设计中的每个图像实际上都是HTML代码中的div元素,并且将使用CSS定义和定位图像,这将有助于完成此任务:

<!-- The `.container` element will contain all the images -->
<!-- It will be used also to perform the custom scroll behavior -->
<div class="container">
  <!-- Each following `div` correspond to one image -->
  <!-- The images will be set using CSS backgrounds -->
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
  <div class="image"></div>
</div>

Now let's look at the CSS styles needed to achieve the desired design.

现在,让我们看一下实现所需设计所需CSS样式。

应用CSS样式 ( Applying CSS styles )

First let's start by creating the layout. This time we will use a CSS Grid, taking advantage of the fact that this technology is already supported in all modern browsers.

首先,让我们开始创建布局。 这次,我们将利用CSS网格,充分利用所有现代浏览器都已支持该技术这一事实。

// The container for all images
.container {
  // 2 columns grid
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 0 10%;
  justify-items: end; // This will align all items (images) to the right

  // Fixed positioned, so it won't be affected by default scroll
  // It will be moved using `transform`, to achieve a custom scroll behavior
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
}

It is important to note that, in addition to the CSS grid, we are giving the .container element a fixed position. This will make this element not affected by the default scroll behavior, allowing to perform custom transforms with Javascript.

重要的是要注意,除了CSS网格外,我们还为.container元素提供了fixed位置。 这将使此元素不受默认滚动行为的影响,从而允许使用Javascript执行自定义转换。

Now let's see how to define the styles associated with the images. See the comments for a brief explanation of each part:

现在,让我们看看如何定义与图像关联的样式。 请参阅注释以获取每个部分的简要说明:

// Styles for image elements
// Mainly positioning and background styles
.image {
  position: relative;
  width: 300px;
  height: 100vh;
  background-repeat: no-repeat;
  background-position: center;

  // This will align all even images to the left
  // For getting centered positioned images, respect to the viewport
  &:nth-child(2n) {
    justify-self: start;
  }

  // Set each `background-image` using a SCSS `for` loop
  @for $i from 1 through 10 {
    &:nth-child(#{$i}) {
      background-image: url('../img/image#{$i}.jpg');
    }
  }
}

Now let's make some adjustments for small screens, since there we should have a column instead of two.

现在,让我们对小屏幕进行一些调整,因为在那里我们应该有一列而不是两列。

// Adjusting layout for small screens
@media screen and (max-width: 760px) {
  .container {
    // 1 column grid
    grid-template-columns: 1fr;
    // Fix image centering
    justify-items: center;
  }

  // Fix image centering
  .image:nth-child(2n) {
    justify-self: center;
  }
}

And this way we have our design almost ready, we just need to add the background to the body, which we will not explain so as not to extend the tutorial with trivial details.

这样,我们的设计就差不多准备好了,我们只需要在正文中添加背景,我们将不做解释,以免在教程中增加一些琐碎的细节。

Note also that for now you will not be able to scroll, since we have given a fixed position to the container element. Next we will solve this problem and bring our design to life :)

还请注意,由于我们已将容器元素指定在固定位置,因此目前您将无法滚动。 接下来,我们将解决此问题并将设计付诸实践:)

用Java脚本实现动画 ( Implementing Animations with Javascript )

Now let's see how to implement, from scratch and using vanilla Javascript, a custom scroll movement, smoother and suitable for the animations planned. All this we will achieve without trying to reimplement all the work associated with the scroll that the web browser does. Instead, we will keep the native scroll functionality, at the same time that we will have a custom scroll behavior. Sounds good, huh? Let's see how to do it!

现在,让我们看看如何从头开始并使用香草Javascript实现自定义滚动运动,使之更平滑并适合计划的动画。 我们将实现所有这些目标,而无需尝试重​​新实现与Web浏览器所做的滚动相关的所有工作。 相反,我们将保留本机滚动功能,同时我们将具有自定义滚动行为。 听起来不错吧? 让我们来看看如何做!

有用的函数和变量 (Useful Functions and Variables)

First let's look at some useful functions that we will be using. Lean on the comments for a better understanding:

首先,让我们看一下将要使用的一些有用的功能。 依靠评论以获得更好的理解:

// Easing function used for `translateX` animation
// From: https://gist.github.com/gre/1650294
function easeOutQuad (t) {
  return t *_ (2 - t)
}

// Returns a random number (integer) between __`min`__ and __`max`__
function random (min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min_
}

// Returns a random number as well, but it could be negative also
function randomPositiveOrNegative (min, max) {
  return random(min, max) * (Math.random() > 0.5 ? 1 : -1)
}

// Set CSS `tranform` property for an element
function setTransform (el, transform) {
  el.style.transform = transform
  el.style.WebkitTransform = transform
}

And these are the variables that we will be using also, described briefly to understand much better the code that we will present below:

这些也是我们还将使用的变量,为了更好地理解下面将要介绍的代码,将对其进行简要描述:

// Current scroll position
var current = 0
// Target scroll position
var target = 0
// Ease or speed for moving from `current` to `target`
var ease = 0.075
// Utility variables for `requestAnimationFrame`
var rafId = undefined
var rafActive = false
// Container element
var container = document.querySelector('.container')
// Array with `.image` elements
var images = Array.prototype.slice.call(document.querySelectorAll('.image'))
// Variables for storing dimmensions
var windowWidth, containerHeight, imageHeight

// Variables for specifying transform parameters (max limits)
var rotateXMaxList = []
var rotateYMaxList = []
var translateXMax = -200

// Popullating the `rotateXMaxList` and `rotateYMaxList` with random values
images.forEach(function () {
  rotateXMaxList.push(randomPositiveOrNegative(20, 40))
  rotateYMaxList.push(randomPositiveOrNegative(20, 60))
})

With all this ready, let's see how to implement our custom scroll behavior.

准备好所有这些之后,让我们看看如何实现自定义滚动行为。

实现自定义滚动行为 (Implementing the Custom Scroll Behavior)

To make our webpage scrollable, we will add a new div element to the body dynamically, to which we will set the same height of our container element, in such a way that the scrollable area will be the same.

为了使我们的网页可滚动,我们将在body动态添加一个新的div元素,并为其设置相同height的容器元素,以使可滚动区域相同。

// The `fakeScroll` is an element to make the page scrollable
// Here we are creating it and appending it to the `body`
var fakeScroll = document.createElement('div')
fakeScroll.className = 'fake-scroll'
document.body.appendChild(fakeScroll)
// In the `setupAnimation` function (below) we will set the `height` properly

We also need a bit of CSS styles so that our .fake-scroll element makes the page scrollable, without interfering with the layout and the other elements:

我们还需要一些CSS样式,以便我们的.fake-scroll元素使页面可滚动,而不会干扰布局和其他元素:

// The styles for a `div` element (inserted with Javascript)
// Used to make the page scrollable
// Will be setted a proper `height` value using Javascript
.fake-scroll {
  position: absolute;
  top: 0;
  width: 1px;
}

Now let's see the function responsible for calculating all the necessary dimensions, and preparing the ground for the animations:

现在,让我们看一下负责计算所有必要尺寸并为动画准备地面的函数:

// Geeting dimmensions and setting up all for animation
function setupAnimation () {
  // Updating dimmensions
  windowWidth = window.innerWidth
  containerHeight = container.getBoundingClientRect().height
  imageHeight = containerHeight / (windowWidth > 760 ? images.length / 2 : images.length)
  // Set `height` for the fake scroll element
  fakeScroll.style.height = containerHeight + 'px'
  // Start the animation, if it is not running already
  startAnimation()
}

Once the setupAnimation function is called, the page will be scrollable, and everything will be ready to start listening to the scroll event and run the animation.

调用setupAnimation函数后,页面将可以滚动,并且一切准备就绪,可以开始收听scroll事件并运行动画。

So let's see what we will do when the scroll event is triggered:

因此,让我们看看触发scroll事件时我们将做什么:

// Update scroll `target`, and start the animation if it is not running already
function updateScroll () {
  target = window.scrollY || window.pageYOffset
  startAnimation()
}

// Listen for `scroll` event to update `target` scroll position
window.addEventListener('scroll', updateScroll)

Easy! Each time the scroll event is triggered, you simply update the target variable with the new position, and call the startAnimation function, which does nothing but start the animation if it is not active yet. Here is the code:

简单! 每次触发scroll事件时,您只需使用新位置更新目标变量,然后调用startAnimation函数,该函数仅在动画尚未激活时启动动画。 这是代码:

// Start the animation, if it is not running already
function startAnimation () {
  if (!rafActive) {
    rafActive = true
    rafId = requestAnimationFrame(updateAnimation)
  }
}

Now let's see the internal behavior for the updateAnimation function, which is the one that actually performs all calculations and transformations in each frame, to achieve the desired animation. Please follow the comments for a better understanding of the code:

现在让我们看一下updateAnimation函数的内部行为,该函数实际上在每一帧中执行所有计算和转换,以实现所需的动画。 请遵循注释以更好地理解代码:

// Do calculations and apply CSS `transform`s accordingly
function updateAnimation () {
  // Difference between `target` and `current` scroll position
  var diff = target - current
  // `delta` is the value for adding to the `current` scroll position
  // If `diff < 0.1`, make `delta = 0`, so the animation would not be endless
  var delta = Math.abs(diff) < 0.1 ? 0 : diff * ease

  if (delta) { // If `delta !== 0`
    // Update `current` scroll position
    current += delta
    // Round value for better performance
    current = parseFloat(current.toFixed(2))
    // Call `update` again, using `requestAnimationFrame`
    rafId = requestAnimationFrame(updateAnimation)
  } else { // If `delta === 0`
    // Update `current`, and finish the animation loop
    current = target
    rafActive = false
    cancelAnimationFrame(rafId)
  }

  // Update images (explained below)
  updateAnimationImages()

  // Set the CSS `transform` corresponding to the custom scroll effect
  setTransform(container, 'translateY('+ -current +'px)')
}

And our custom scroll behavior is ready! After calling the function setupAnimation, you could scroll as you normally would, and the .container element would be moved in correspondence, but with a very smooth and pleasant effect :)

我们的自定义滚动行为已准备就绪! 调用函数setupAnimation ,您可以像往常一样滚动,并且.container元素将以对应的方式移动,但是效果非常流畅和愉悦:)

Then we only have to animate the images in correspondence with the position in which they are with respect to the viewport. Let's see how to do it!

然后,我们只需要根据图像相对于视口的位置对图像进行动画处理即可。 让我们来看看如何做!

滚动时动画显示 (Animating Images While Scrolling)

To animate the images we will use the current position of the fake scroll (current), and we will calculate the intersectionRatio (similar to the value from the IntersectionObserver API) between each image and the viewport. Then, we just have to apply the transformations that we want depending on that ratio, and we will obtain the desired animation.

动画图像,我们将使用假滚动(的当前位置current ),并且我们将计算intersectionRatio (类似于从值IntersectionObserver API的每个图像和视口之间)。 然后,我们只需要根据该比率应用所需的变换即可获得所需的动画。

The idea is to show the image without any transformation when it is in the center of the screen (intersectionRatio = 1), and to increase the transformations as the image moves towards the ends of the screen (intersectionRatio = 0).

其想法是在图像位于屏幕中央时显示图像而没有任何变换( intersectionRatio = 1 ),并随着图像向屏幕末端移动( intersectionRatio = 0 )而增加变换。

Pay close attention to the code shown below, especially the part where the intersectionRatio for each image is calculated. This value is essential to then apply the appropriate CSS transformations. Please follow the comments for a better understanding:

请密切注意下面显示的代码,尤其是计算每个图像的intersectionRatio的部分。 此值对于随后应用适当CSS转换至关重要。 请按照注释进行进一步了解:

// Calculate the CSS `transform` values for each `image`, given the `current` scroll position
function updateAnimationImages () {
  // This value is the `ratio` between `current` scroll position and images `height`
  var ratio = current / imageHeight
  // Some variables for using in the loop
  var intersectionRatioIndex, intersectionRatioValue, intersectionRatio
  var rotateX, rotateXMax, rotateY, rotateYMax, translateX

  // For each `image` element, make calculations and set CSS `transform` accordingly
  images.forEach(function (image, index) {
    // Calculating the `intersectionRatio`, similar to the value provided by
    // the IntersectionObserver API
    intersectionRatioIndex = windowWidth > 760 ? parseInt(index / 2) : index
    intersectionRatioValue = ratio - intersectionRatioIndex
    intersectionRatio = Math.max(0, 1 - Math.abs(intersectionRatioValue))
    // Calculate the `rotateX` value for the current `image`
    rotateXMax = rotateXMaxList[index]
    rotateX = rotateXMax - (rotateXMax _ intersectionRatio)
    rotateX = rotateX.toFixed(2)
    // Calculate the _`rotateY`_ value for the current _`image`_
    rotateYMax = rotateYMaxList_[_index]
    rotateY = rotateYMax - (rotateYMax _ intersectionRatio)
    rotateY = rotateY.toFixed(2)
    // Calculate the `translateX` value for the current `image`
    if (windowWidth > 760) {
      translateX = translateXMax - (translateXMax * easeOutQuad(intersectionRatio))
      translateX = translateX.toFixed(2)
    } else {
      translateX = 0
    }
    // Invert `rotateX` and `rotateY` values in case the image is below the center of the viewport
    // Also update `translateX` value, to achieve an alternating effect
    if (intersectionRatioValue < 0) {
      rotateX = -rotateX
      rotateY = -rotateY
      translateX = index % 2 ? -translateX : 0
    } else {
      translateX = index % 2 ? 0 : translateX
    }
    // Set the CSS `transform`, using calculated values
    setTransform(image, 'perspective(500px) translateX('+ translateX +'px) rotateX('+ rotateX +'deg) rotateY('+ rotateY +'deg)')
  })
}

初始化动画 (Init Animation)

And we are almost ready to enjoy our animation. We only need to make the initial call to the setupAnimation function, in addition to updating the dimensions in case the resize event is triggered:

我们几乎准备好欣赏动画了。 除了在触发resize事件的情况下更新尺寸外,我们只需要对setupAnimation函数进行初始调用:

// Listen for `resize` event to recalculate dimmensions
window.addEventListener('resize', setupAnimation)

// Initial setup
setupAnimation()

Scroll! Scroll! Scroll!

滚动! 滚动! 滚动!

解决移动设备的跳转问题 ( Fixing the Jump Issue for Mobile Devices )

So far, everything should work perfectly in Desktop, but the story is very different if we try the animation on mobile devices.

到目前为止,一切都可以在Desktop上正常运行,但是如果我们在移动设备上尝试动画,则情况会大不相同。

The problem occurs when the address bar (and the navigation bar in the footer of some browsers) hides when it is scrolled down, and is shown again when scrolling upwards. This is not a bug, but a feature. The problem appears when we use the CSS unit vh, since in this process that unit is recalculated, resulting in an unwanted jump in our animation.

向下滚动时地址栏(以及某些浏览器的页脚中的导航栏)隐藏时,会出现问题,而向上滚动时会再次显示。 这不是错误,而是功能。 当我们使用CSS单位vh ,会出现问题,因为在此过程中将重新计算该单位,从而导致动画发生不必要的跳跃。

The workaround that we have implemented is to use the small library vh-fix, which defines, for each element with the class .vh-fix, a static height based on their vh value and the viewport height. In this way, we should no longer have unwanted jumps :)

我们采取的解决方法是使用小型图书馆VH修复 ,它定义为与类的每个元素.vh-fix ,静态height根据自己的vh值和视口height 。 这样,我们就不再会有不必要的跳跃了:)

结论 ( Conclusions )

And we have finished implementing this beautiful scroll based animation:

至此,我们已经完成了这个精美的基于滚动的动画的实现:

You can check the live demo, play with the code in Codepen, or check the full code in the repository in Github.

您可以查看现场演示在Codepen中使用代码在Github中的存储库中查看完整代码。

Please keep in mind that the objective of this tutorial is essentially academic and to be used as inspiration. To use this demo in production other aspects must be taken into account, such as accessibility, browser support, the use of some debounce function for the scroll and resize events, etc.

请记住,本教程的目的本质上是学术性的,可以作为启发。 要在生产环境中使用此演示,必须考虑其他方面,例如可访问性,浏览器支持,对scrollresize事件使用某些debounce功能等。

Without further ado, we hope you enjoyed it and it has been useful!

事不宜迟,我们希望您喜欢它,它对您有所帮助!

学分 ( Credits )

翻译自: https://scotch.io/tutorials/implementing-a-scroll-based-animation-with-javascript

javascript 滚动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值