需求

之前在工作中遇到的需求。色块里面放了具体内容,有文字或文字加图片。要求并排的3个色块在鼠标往下滚动的过程中慢慢从下往上浮现,当滚动到一定距离后,色块内容更改,新的并排色块重新从下往上浮现,脱离动效后如果屏幕向上滚动,色块以相反效果展示。具体效果如下GIF所示,黑色部分是页面外的调试界面。

jq实时监控滚动高度 监控视频怎么滚动画面_css3

问题分析

上面的需求已经做了一层分析,将UI的想法抽象出来,用程序员的语言描述一遍,但这层分析还远远还不够。

DOM结构设计

难点在于整个动效DOM的结构设计。

首先我们需要理解,页面在向下滚动的过程中,高度会不断增大,我们肉眼看到动效没有下移,但实际上动效在父结点中一直在往下掉。我们不仅要考虑并排色块,同时要考虑上面的紫色和粉色色块,因为在向下滚动过程中,它们是静止不动的,并没有按照正常的逻辑往下滚,所以它们的静止本身也是一种动效。所以当鼠标往下滚动时,我们需要考虑两种动效,一种是并排色块逐渐浮现消失的动效,另一种是紫色色块和粉色色块(下面合二为一改称静止色块)的静止动效。

在动效外,需要再包一层div作为动效的父元素,动效在这个父元素里面滚动。举个例子,就像一个滑梯,有个小女孩在上面往下滑,我们的视角(屏幕窗口)只定格在孩子以及周围一小块环境身上,我们能感受到孩子在往下滑,但是看不到完整的滑梯。

说到动效,我们自然而然会想到定位属性position,通过上面的分析,一开始我尝试的是绝对定位和固定定位。鼠标往下滚动时,监控屏幕高度,当滚动到一定的高度时,用固定定位将静止色块固定在屏幕的一个位置,计算高度,不断修改并排色块的css参数,其中包含绝对定位(通过不断刷新这些参数达到浮现效果,在下面会详细说明)。可惜这个方案不够完美,具体表现在,当我们滚动时,在将静止色块的定位属性值从默认变成fixed的那一刻,整个元素会卡一下,而并排色块由于不断刷新绝对定位且屏幕同时往下滚动,会出现明显的抖动。总之,这种方案的表现非常不流畅,影响观感,不信的朋友大可尝试一番。

尝试使用粘性定位

既然使用粘性定位,那么以什么内容作为对象呢?在上面列举的小女孩滑滑梯的例子中,我们可以想到,对象应该就是小女孩。小女孩即是两种动效的结合,因为我们的视角跟随着小女孩,所以在我们的视角中,有些元素即是自然而然静止的。我们可以将例子继续生动一些,假如小女孩在下滑的过程中,不断向我们招手,其他身体部位则保持不动。那么手臂即可看做并排色块,其他部位看做静止色块。此刻思路逐渐清晰了,dom结构设计大概如下:

<div class="滑梯">
    <div class="小女孩">
        <div class="粉色色块"></div>
        <div class="紫色色块"></div>
        <div class="并排色块">
            ...
        </div>
    </div>
</div>

我们的视角在小女孩身上,所以应该对小女孩使用粘性布局:

.小女孩 {
	height: 100vh; // 整个元素高度占满一个屏幕
	position: sticky; // 粘性定位
    top: 0; // 整屏黏在顶部,不会展示小女孩以外的内容,直到滚出滑梯
	...
}

滑梯要足够长:

.滑梯 {
    height: 300vh; // 设计长度为3屏高
}

至此,从原理分析了整个动效DOM的设计,我认为这一层相对重要也更难一些。

并排色块动效详细设计

我们继续引用上述小女孩滑滑梯的例子。

具体思路:监听屏幕滚动高度,当高度到达滑梯顶部时,动效开始(接下来说的动效仅指并排色块动效),计算小女孩与滑梯顶部的距离,分别更改并排色块与父元素的顶内边距(padding-top)以及色块的透明度(opacity)。另外,因为色块在中途会更换,所以其实是有6个不同的色块,分成2组,均匀变化。需要再添加一个控制参数,控制每组色块的显隐,当高度达到某一点时,一组色块立即隐藏,换另一组色块逐渐浮现。

jq实时监控滚动高度 监控视频怎么滚动画面_css3_02

所以并排色块内部DOM结构应该如下:

<div class="并排色块">
    <div class="第1组">
        <div class="色块1"></div>
        <div class="色块2"></div>
        <div class="色块3"></div>
    </div>
    <div class="第二组">
        <div class="色块4"></div>
        <div class="色块5"></div>
        <div class="色块6"></div>
    </div>
</div>

通过以上分析,我们需要监控高度差,实时更新2个参数:opacity、padding-top以及整组元素的显隐状态,这里需要使用JS函数实现,具体实现看代码,值得一提的是,并排色块向上提升的快慢,透明度变化的速率,都可以自己调节。比如增加一个额外的参数作为变化速率,实时更改透明度时加上这个速率。

我的设计是滑梯占3屏高,原始状态占一屏高,第一组色块动效变化占一屏高,第二组色块动效占一屏高。

具体实现

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>一种滚动动效</title>
  <style>
    .head {
      width: 100%;
      height: 400px;
      background-color: aqua;
    }

    .footer {
      width: 100%;
      height: 1000px;
      background-color: brown;
    }

    .body {
      width: 100%;
      height: 300vh;
    }

    .body-full-screen {
      height: 100vh;
      padding-top: 100px;
      position: sticky;
      top: 0;
      text-align: center;
    }

    .body-content {
      text-align: center;

    }

    .body-title {
      width: 800px;
      height: 100px;
      background-color: pink;
      margin: 0 auto;
      margin-bottom: 60px;
    }

    .body-des {
      width: 1200px;
      height: 200px;
      background-color: purple;
      margin: 0 auto;
      margin-bottom: 80px;
    }

    .body-num-first,
    .body-num-second {
      display: flex;
      justify-content: space-between;
      width: 1500px;
      margin: 0 auto;
      padding-top: 120px;
    }

    .body-num-item {
      width: 200px;
      height: 200px;
    }

    .body-num-1 {
      background-color: red;
    }

    .body-num-2 {
      background-color: green;
    }

    .body-num-3 {
      background-color: blue;
    }

    .body-num-4 {
      background-color: salmon;
    }

    .body-num-5 {
      background-color: khaki;
    }

    .body-num-6 {
      background-color: peru;
    }

    .hide {
      display: none;
    }

    .opacity-zero {
      opacity: 0;
    }
  </style>
</head>

<body>
  <div>
    <div class="head"></div>
    <div class="body">
      <div class="body-full-screen">
        <div class="body-title"></div>
        <div class="body-des"></div>
        <div class="body-num-first opacity-zero">
          <div class="body-num-1 body-num-item"></div>
          <div class="body-num-2 body-num-item"></div>
          <div class="body-num-3 body-num-item"></div>
        </div>
        <div class="body-num-second opacity-zero hide">
          <div class="body-num-4 body-num-item"></div>
          <div class="body-num-5 body-num-item"></div>
          <div class="body-num-6 body-num-item"></div>
        </div>
      </div>
    </div>
    <div class="footer"></div>
  </div>
  <script src="https://code.jquery.com/jquery-3.3.1.min.js"
    integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
  <script>
    let screenHeight = $(window).height()
    let bodyTop = $('.body').offset().top
    $(window).scroll(function () {
      let currentTop = $(window).scrollTop()
      if (currentTop > bodyTop && currentTop < (bodyTop + screenHeight)) {
        $('.body-num-first').css('display', 'flex')
        $('.body-num-second').css('display', 'none')
        let firstOpacityValue = (currentTop - bodyTop) / screenHeight
        let firstPaddingTopValue = (1 - firstOpacityValue) * 120 + 'px'
        $('.body-num-first').css({
          'opacity': firstOpacityValue,
          'padding-top': firstPaddingTopValue
        })
      } else if (currentTop >= (bodyTop + screenHeight) && currentTop < (bodyTop + 2 * screenHeight)) {
        $('.body-num-first').css('display', 'none')
        $('.body-num-second').css('display', 'flex')
        let secondOpacityValue = (currentTop - bodyTop - screenHeight) / screenHeight
        let secondPaddingTopValue = (1 - secondOpacityValue) * 120 + 'px'
        $('.body-num-second').css({
          'opacity': secondOpacityValue,
          'padding-top': secondPaddingTopValue
        })
      }
    })
  </script>
</body>

</html>