WebIsFun
WritingSnippet

Scroll-driven Animations

以前如果要实现基于滚动条的动画需要使用第三方库,现在CSS原生支持了这个功能,只需要几行代码就可以实现。

2026-01-11


在我以前的工作中,如果要实现一个和滚动相关的动画一般情况下都是使用像 GSAP 这种第三方 JavaScript 库来完成。无法通过纯 CSS 实现。

使用 JavaScript 来实现我觉得有一些潜在的问题:

  • 代码复杂度增加,因为引入的是第三方库每个库都有自己的实现方法。有些可能也不兼容你使用的特定框架

  • 动画流可能会卡顿,在基于 JavaScript 中的动画是在主线程执行计算动画,当主线程繁忙时会导致动画卡顿无法达到 60fps

现在新的滚动驱动动画接口已经可以实现在 CSS 中实现滚动动画了,现在一起来认识一下它吧。

目标受众

这篇博客是为各种经验水平的 Web 开发人员准备的,但是我假设你已经了解了 keyframes 动画基础知识。

如果你对 keyframes 不是特别了解,我有另外一篇文章可以帮助你:

下面使用了 CSS 的动画,无论怎么滚动都非常流畅。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      /* 设置动画 */
      .card {
        animation: stack linear both;
        animation-timeline: view();
      }
      /* 动画关键帧 */
      @keyframes stack {
        entry 0% {
          transform: translateZ(1000px);
          opacity: 0;
        }
        entry 200% {
          opacity: 0;
        }
        exit -200% {
          opacity: 1;
        }
        exit 100% {
          transform: translateZ(-1000px);
          opacity: 0;
        }
      }
    </style>
  </head>
  <body>
    <div id="container"></div>
    <script>
      const container = document.getElementById("container");

      for (let i = 0; i < 200; i++) {
        const elem = document.createElement("div");
        elem.classList.add("card");
        elem.innerText = i;
        container.appendChild(elem);
      }
    </script>
  </body>
</html>

什么是滚动驱动动画?

滚动驱动动画是指可以让我们的动画映射为滚动条的滚动进度而执行 keyframes 的动画,可以将其行为理解为动画第一帧对应滚动开始位置,最后一帧对应滚动结束位置。而中间的帧则 CSS 会自动帮我计算插值。

这与传统的基于时间的动画不同,基于时间的动画有一个时间范围比如:300ms,开始帧对应对应 0ms,结束帧对应 300ms。而滚动驱动动画则是没有时间范围的,它是基于滚动条的位置来推进动画。

下图是展示滚动距离和动画进度的关系:

滚动距离

动画进度

通过使用原生的 CSS 实现可以让我们获取流程的动画效果、无须依赖 JavaScript 或其他第三方库、脱离主线程且是 GPU 加速的动画。常见的使用场景有:网页滚动进度、视差效果、元素渐显/渐隐。

animation-timeline

animation-timeline 属性将动画和时间轴绑定在一起,这样就是基于滚动条进度去执行对应的动画。它可以指定一个或者多个值分别对应的是 animation 上的多个动画。常用的两个值是 scroll()view() 函数。

在 CSS 中我们可以这样使用:

.box {
  animation: grow-progress auto linear;
  animation-timeline: scroll();
}

scroll() 函数

在上面的示例代码中,我们为 .box 元素定义了 animation-timeline: scroll() 属性,scroll() 默认情况下等于: scroll(block)。意思就是引用它祖先可滚动元素的滚动轴

这里的滚动轴我是为了更好理解这样写。实际的意思是:在 CSS 的逻辑属性和书写模式(writing-mode)语境中,“块维度”(block dimension)指块级元素(如段落)逐个堆叠的方向。该轴在水平书写模式(horizontal writing modes,如英文或中文横排)中对应垂直方向,在垂直书写模式(vertical writing modes,如传统日文或蒙文竖排)中对应水平方向。

上面的解释有点太书面了,现在我们来实际看一个例子,就明白具体是怎么回事了。

下面的例子在顶部有一个进度条,当我们滚动条在最上面时进度条为0,当我们滚动到最底部时进度条为100。你可以在下面的预览中滚动右边的预览网页查看顶部进度条效果。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Scroll Page Example</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      @keyframes grow-progress {
        from {
          transform: scaleX(0);
        }
        to {
          transform: scaleX(1);
        }
      }

      .progress {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 8px;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      }

      .progress {
        transform-origin: 0 50%;
        animation: grow-progress auto linear;
        animation-timeline: scroll();
      }
    </style>
  </head>
  <body>
    <div class="progress"></div>
    <div>
      <h3>The standard Lorem Ipsum passage, used since the 1500s</h3>
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
      tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
      veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
      commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
      velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
      occaecat cupidatat non proident, sunt in culpa qui officia deserunt
      mollit anim id est laborum."
      <h3>
        Section 1.10.32 of "de Finibus Bonorum et Malorum", written by Cicero
        in 45 BC
      </h3>
      "Sed ut perspiciatis unde omnis iste natus error sit voluptatem
      accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab
      illo inventore veritatis et quasi architecto beatae vitae dicta sunt
      explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut
      odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
      voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum
      quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam
      eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat
      voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam
      corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
      Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse
      quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo
      voluptas nulla pariatur?"
      <h3>1914 translation by H. Rackham</h3>
      "But I must explain to you how all this mistaken idea of denouncing
      pleasure and praising pain was born and I will give you a complete
      account of the system, and expound the actual teachings of the great
      explorer of the truth, the master-builder of human happiness. No one
      rejects, dislikes, or avoids pleasure itself, because it is pleasure,
      but because those who do not know how to pursue pleasure rationally
      encounter consequences that are extremely painful. Nor again is there
      anyone who loves or pursues or desires to obtain pain of itself, because
      it is pain, but because occasionally circumstances occur in which toil
      and pain can procure him some great pleasure. To take a trivial example,
      which of us ever undertakes laborious physical exercise, except to
      obtain some advantage from it? But who has any right to find fault with
      a man who chooses to enjoy a pleasure that has no annoying consequences,
      or one who avoids a pain that produces no resultant pleasure?"
      <h3>
        Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero
        in 45 BC
      </h3>
      "At vero eos et accusamus et iusto odio dignissimos ducimus qui
      blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
      et quas molestias excepturi sint occaecati cupiditate non provident,
      similique sunt in culpa qui officia deserunt mollitia animi, id est
      laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
      distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
      cumque nihil impedit quo minus id quod maxime placeat facere possimus,
      omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem
      quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet
      ut et voluptates repudiandae sint et molestiae non recusandae. Itaque
      earum rerum hic tenetur a sapiente delectus, ut aut reiciendis
      voluptatibus maiores alias consequatur aut perferendis doloribus
      asperiores repellat."
      <h3>1914 translation by H. Rackham</h3>
      "On the other hand, we denounce with righteous indignation and dislike
      men who are so beguiled and demoralized by the charms of pleasure of the
      moment, so blinded by desire, that they cannot foresee the pain and
      trouble that are bound to ensue; and equal blame belongs to those who
      fail in their duty through weakness of will, which is the same as saying
      through shrinking from toil and pain. These cases are perfectly simple
      and easy to distinguish. In a free hour, when our power of choice is
      untrammelled and when nothing prevents our being able to do what we like
      best, every pleasure is to be welcomed and every pain avoided. But in
      certain circumstances and owing to the claims of duty or the obligations
      of business it will frequently occur that pleasures have to be
      repudiated and annoyances accepted. The wise man therefore always holds
      in these matters to this principle of selection: he rejects pleasures to
      secure other greater pleasures, or else he endures pains to avoid worse
      pains."
    </div>
  </body>
</html>

在上面示例 CSS 代码中使用的是 animation-timeline: scroll(); 可以起作用是因为我们的网页有且只有一个滚动条那就是网页根元素(html)的滚动条。如果我们想附加动画的元素父级有多个滚动条但是我们又想绑定到指定的滚动条上,这时我们需要使用自定义滚动条。

html {
  scroll-timeline: --page-scroll block;
}
#progress {
  transform-origin: 0 50%;
  animation: grow-progress auto linear;
  animation-timeline: --page-scroll;
}

scroll-timeline 属性可以定义一个命名的滚动进度时间线,就像定义一个变量一样。然后我们可以在 animation-timeline 属性中引用这个定义的名称 --page-scroll

下面的例子展示了如果有多个滚动进度条的情况,我们可以手动切换将滚动进度绑定到不同的滚动条上。

import { useState } from 'react';
import './App.css';

function App() {
  const [scrollType, setScrollType] = useState('parent');

  const handleRoot = () => {
    setScrollType('root');
  };

  const handleParent = () => {
    setScrollType('parent');
  };

  return (
    <div>
      <div className="box">
        <div className={`progress ${scrollType}`}></div>
        <h3>The standard Lorem Ipsum passage, used since the 1500s</h3>
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
        veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
        commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
        velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
        occaecat cupidatat non proident, sunt in culpa qui officia deserunt
        mollit anim id est laborum."
        <h3>
          Section 1.10.32 of "de Finibus Bonorum et Malorum", written by Cicero
          in 45 BC
        </h3>
        "Sed ut perspiciatis unde omnis iste natus error sit voluptatem
        accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab
        illo inventore veritatis et quasi architecto beatae vitae dicta sunt
        explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut
        odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
        voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum
        quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam
        eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat
        voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam
        corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
        Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse
        quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo
        voluptas nulla pariatur?"
        <h3>1914 translation by H. Rackham</h3>
        "But I must explain to you how all this mistaken idea of denouncing
        pleasure and praising pain was born and I will give you a complete
        account of the system, and expound the actual teachings of the great
        explorer of the truth, the master-builder of human happiness. No one
        rejects, dislikes, or avoids pleasure itself, because it is pleasure,
        but because those who do not know how to pursue pleasure rationally
        encounter consequences that are extremely painful. Nor again is there
        anyone who loves or pursues or desires to obtain pain of itself, because
        it is pain, but because occasionally circumstances occur in which toil
        and pain can procure him some great pleasure. To take a trivial example,
        which of us ever undertakes laborious physical exercise, except to
        obtain some advantage from it? But who has any right to find fault with
        a man who chooses to enjoy a pleasure that has no annoying consequences,
        or one who avoids a pain that produces no resultant pleasure?"
        <h3>
          Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero
          in 45 BC
        </h3>
        "At vero eos et accusamus et iusto odio dignissimos ducimus qui
        blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
        et quas molestias excepturi sint occaecati cupiditate non provident,
        similique sunt in culpa qui officia deserunt mollitia animi, id est
        laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
        distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
        cumque nihil impedit quo minus id quod maxime placeat facere possimus,
        omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem
        quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet
        ut et voluptates repudiandae sint et molestiae non recusandae. Itaque
        earum rerum hic tenetur a sapiente delectus, ut aut reiciendis
        voluptatibus maiores alias consequatur aut perferendis doloribus
        asperiores repellat."
        <h3>1914 translation by H. Rackham</h3>
        "On the other hand, we denounce with righteous indignation and dislike
        men who are so beguiled and demoralized by the charms of pleasure of the
        moment, so blinded by desire, that they cannot foresee the pain and
        trouble that are bound to ensue; and equal blame belongs to those who
        fail in their duty through weakness of will, which is the same as saying
        through shrinking from toil and pain. These cases are perfectly simple
        and easy to distinguish. In a free hour, when our power of choice is
        untrammelled and when nothing prevents our being able to do what we like
        best, every pleasure is to be welcomed and every pain avoided. But in
        certain circumstances and owing to the claims of duty or the obligations
        of business it will frequently occur that pleasures have to be
        repudiated and annoyances accepted. The wise man therefore always holds
        in these matters to this principle of selection: he rejects pleasures to
        secure other greater pleasures, or else he endures pains to avoid worse
        pains."
      </div>
      <div style={{ height: 800 }} />
      <p>结束文本</p>
      <div style={{ height: 100 }} />

      <div className="toolbar">
        <button onClick={handleRoot}>根元素滚动</button>
        <button onClick={handleParent}>父元素滚动</button>
      </div>
    </div>
  );
}

export default App;

上面例子中,动画所绑定的滚动条默认为父元素。只有当你滚动中间的文字时进度条才会展示。

当点击根元素按钮后,页面进度就绑定为 html 的滚动条,再滚动中间的文字进度条不会产生动画。只有滚动 html 的滚动条时,进度条才会进行动画。这就是自定义滚动条scroll-timeline 的使用方法。

view() 函数

在上面我们了解了 scroll() 函数对于垂直滚动的用法,现在来看看另外一个函数 view()。它与 scroll() 不同的是 view() 的作用是将元素在其最近的滚动容器中进入视野和离开视野的滚动时间线映射到 animation keyframes 的 from 和 to 上。

举个例子:元素刚达到滚动元素的底部,它就会执行 keyframes 的第一个动画帧,并且在滚动到底部的时候就会离开视野。当元素达到滚动元素的顶部时,它就会执行 keyframes 最后一个动画帧。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear forwards;
        animation-timeline: view(30% 0);
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 10%);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 10%);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
      <h2>Salmo (Atlantic salmon)</h2>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/Salmo_salar.png/248px-Salmo_salar.png" />
      <h2>Oncorhynchus (Pacific salmon)</h2>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Humpback_Salmon_Adult_Male.jpg/250px-Humpback_Salmon_Adult_Male.jpg" />
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Humpback_Salmon_Breeding_Male.jpg/250px-Humpback_Salmon_Breeding_Male.jpg" />
      <p>The extinct Eosalmo driftwoodensis, the oldest known Salmoninae fish in the fossil record, helps scientists figure how the different species of salmon diverged from a common ancestor. The Eocene salmon's fossil from British Columbia provides evidence that the divergence between Pacific and Atlantic salmon had not yet occurred 40 million years ago. Both the fossil record and analysis of mitochondrial DNA suggest the divergence occurred 10 to 20 million years ago during the Miocene. This independent evidence from DNA analysis and the fossil record indicate that salmon divergence occurred long before the Quaternary glaciation began the cycle of glacial advance and retreat.</p>
      <h2>Non-salmon species of "salmon"</h2>
      <p>There are several other species of fish which are colloquially called "salmon" but are not true salmon. Of those listed below, the Danube salmon or huchen is a large freshwater salmonid closely related (from the same subfamily) to the seven species of salmon above, but others are fishes of unrelated orders, given the common name "salmon" simply due to similar shapes, behaviors and niches occupied</p>
    </div>
  </body>
</html>

view() 函数如果不设置参数的情况下是元素刚进入滚动元素的视口就开始执行动画,元素完全离开滚动元素的视口结束动画。

如果我们设置一个参数 view(20%) 元素会在进入20%后开始执行动画,然后距离结束还差20%的时候停止动画。

如果我们设置两个参数 view(30% 0) 元素会在刚进入的时候执行动画,然后在距离结束还差30%的时候停止动画。

除了通过 view() 这种方式控制进入和离开之外,我们还可以使用 animation-range 属性更加灵活的控制进入和离开的方式。

view() 函数的第一个参数定义滚动视口的起始位置对应滚动元素的顶部,第二个参数定义滚动视口的结束位置对应滚动元素的底部。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear forwards;
        animation-timeline: view(30% 10%);
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 0%);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 0%);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
      <h2>Salmo (Atlantic salmon)</h2>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/Salmo_salar.png/248px-Salmo_salar.png" />
      <h2>Oncorhynchus (Pacific salmon)</h2>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Humpback_Salmon_Adult_Male.jpg/250px-Humpback_Salmon_Adult_Male.jpg" />
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Humpback_Salmon_Breeding_Male.jpg/250px-Humpback_Salmon_Breeding_Male.jpg" />
      <p>The extinct Eosalmo driftwoodensis, the oldest known Salmoninae fish in the fossil record, helps scientists figure how the different species of salmon diverged from a common ancestor. The Eocene salmon's fossil from British Columbia provides evidence that the divergence between Pacific and Atlantic salmon had not yet occurred 40 million years ago. Both the fossil record and analysis of mitochondrial DNA suggest the divergence occurred 10 to 20 million years ago during the Miocene. This independent evidence from DNA analysis and the fossil record indicate that salmon divergence occurred long before the Quaternary glaciation began the cycle of glacial advance and retreat.</p>
      <h2>Non-salmon species of "salmon"</h2>
      <p>There are several other species of fish which are colloquially called "salmon" but are not true salmon. Of those listed below, the Danube salmon or huchen is a large freshwater salmonid closely related (from the same subfamily) to the seven species of salmon above, but others are fishes of unrelated orders, given the common name "salmon" simply due to similar shapes, behaviors and niches occupied</p>
      <div class="overlay start">inset start 30%</div>
      <div class="overlay end">inset end 10%</div>
    </div>
  </body>
</html>

注意

这里有一个注意事项,如果定义 view(30% 10%),30% 是 start 内边距也就是滚动顶部,10% 是 end 内边距也就是滚动底部。我刚学习的时候就搞反了,动画老是不对。

animation-range

animation-range 属性是 animation-range-startanimation-range-end 的简写,它用来定义我们动画在滚动时间轴上的起始和结束位置。可以将我们的动画开始和结束位置进行偏移,从而控制动画的进入和离开时机。

normal / cover 参数

animation-range 的默认参数是 normal 等同于 animation-range: cover 或者 animation-range: 0% 100%,它就是滚动元素的开始和结束位置。在下面我画了一个图展示这种情况:

下图是animation-range: normal 的情况:

img {
  animation: shapeIn linear both;
  animation-timeline: view();
  animation-range: normal;
}
Viewport Start End cover 0% cover 100%

注意

这里有一个注意事项,animation-range 的开始和结束位置分别对应滚动元素的底部顶部view() 函数的开始结束位置分别对应滚动元素的顶部底部。不知道为什么它们没有统一起来。

下面实例中你可以滚动右边预览页面看看效果。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear both;
        animation-timeline: view();
        animation-range: normal;
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 6px);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 6px);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
    </div>
  </body>
</html>

contain 参数

表示元素完全进入滚动元素适口内开始动画,并且在开始退出前结束动画。

下图是animation-range: contain 的情况:

img {
  animation: shapeIn linear both;
  animation-timeline: view();
  animation-range: contain;
}
Viewport Start End contain 0% contain 100%

下面示例你可以滚动右边预览页面看看设置为 contain 的效果。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear both;
        animation-timeline: view();
        animation-range: contain;
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 6px);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 6px);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
    </div>
  </body>
</html>

entry 参数

如果你想让整个动画(从头到尾)完全跟随元素进入视口的过程。照片第一个像素出现时动画开始,最后一个像素进入视口时动画结束——这就是 entry 的用途。

下图是animation-range: entry 的情况:

img {
  animation: shapeIn linear both;
  animation-timeline: view();
  animation-range: entry;
}
entry 100% Viewport Start End entry 0%

下面示例你可以滚动右边预览页面看看设置为 entry 的效果。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear both;
        animation-timeline: view();
        animation-range: entry;
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 6px);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 6px);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
    </div>
  </body>
</html>

entry-crossing 参数

entry-crossing 参数和 entry 类似,但是它会在元素进入视口时开始动画,并且在元素离开视口时结束动画。但是如果我们的元素如果大于滚动元素的高度会发生什么呢?

如果使用 entry 时当元素顶部到达结束位置时就会达到时间线的尽头也就是 100%,并且动画就会立刻结束。如果你使用 entry-crossing,只有元素最后一个像素进入适口时才会达到 100%。

下图是animation-range: entry-crossing 的情况:

img {
  animation: shapeIn linear both;
  animation-timeline: view();
  animation-range: entry-crossing;
}
Viewport Start End entry crossing 0% entry crossing 100%

你可以通过拖动下面两个预览图的滚动条查看 entryentry-crossing 的区别。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear both;
        animation-timeline: view();
        animation-range: entry;
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 6px);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 6px);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
    </div>
  </body>
</html>

animation-range: entry

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear both;
        animation-timeline: view();
        animation-range: entry-crossing;
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 6px);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 6px);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
    </div>
  </body>
</html>

animation-range: entry-crossing

exit 参数

exit 参数和 entry 在概念上是一样的,只是 entry 是在元素进入视口时开始动画,而 exit 是在元素离开视口时开始动画。

下图是animation-range: exit 的情况:

img {
  animation: shapeIn linear both;
  animation-timeline: view();
  animation-range: exit;
}
Viewport Start End exit 100% exit 0%

下面示例你可以滚动右边预览页面看看设置为 exit 的效果。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear both;
        animation-timeline: view();
        animation-range: exit;
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 6px);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 6px);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
    </div>
  </body>
</html>

exit-crossing 参数

exit-crossing 参数和 entry-crossing 交叉原理相同,当元素高于视口时 0% 会在元素第一个离开视口时开始,100% 会在最后一个像素消失在视口是。

如下图所示:

img {
  animation: shapeIn linear both;
  animation-timeline: view();
  animation-range: exit-crossing;
}
exit crossing 0% Viewport Start End exit crossing 100%

下面示例你可以滚动右边预览页面看看设置为 exit-crossing 的效果。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>View Example1</title>
    <link rel="stylesheet" href="/style.css" />
    <style>
      img {
        animation: shapeIn linear both;
        animation-timeline: view();
        animation-range: exit-crossing;
      }
      @keyframes shapeIn {
        from {
          clip-path: rect(0px 0% 100% 0px round 6px);
        }
        to {
          clip-path: rect(0px 100% 100% 0px round 6px);
        }
      }
    </style>
  </head>
  <body>
    <div>
      <h2>Salmon</h2>
      <p>Salmon (/ˈsæmən/; pl.: salmon) are any of several commercially important species of euryhaline ray-finned fish from the genera Salmo and Oncorhynchus of the family Salmonidae, native to tributaries of the North Atlantic (Salmo) and North Pacific (Oncorhynchus) basins. Salmon is a colloquial or common name used for fish in this group, but is not a scientific name. Other closely related fish in the same family include trout, char, grayling, whitefish, lenok and taimen, all coldwater fish of the subarctic and cooler temperate regions with some sporadic endorheic populations in Central Asia.</p>
      <p>Salmon are typically anadromous: they hatch in the shallow gravel beds of freshwater headstreams and spend their juvenile years in rivers, lakes and freshwater wetlands, migrate to the ocean as adults and live like sea fish, then return to their freshwater birthplace to reproduce. However, populations of several species are restricted to fresh waters (i.e. landlocked) throughout their lives. Folklore has it that the fish return to the exact stream where they themselves hatched to spawn, and tracking studies have shown this to be mostly true. A portion of a returning salmon run may stray and spawn in different freshwater systems; the percent of straying depends on the species of salmon.[1] Homing behavior has been shown to depend on olfactory memory.</p>
      <p>Salmon are important food fish and are intensively farmed in many parts of the world,[4] with Norway being the world's largest producer of farmed salmon, followed by Chile.[5] They are also highly prized game fish for recreational fishing, by both freshwater and saltwater anglers. Many species of salmon have since been introduced and naturalized into non-native environments such as the Great Lakes of North America, Patagonia in South America and South Island of New Zealand</p>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Salmo_salar.jpg/1599px-Salmo_salar.jpg" />
      <h2>Name and etymology</h2>
      <p>The Modern English term salmon is derived from Middle English: samoun, samon and saumon, which in turn are from Anglo-Norman: saumon, from Old French: saumon, and from Latin: salmō (which in turn might have originated from salire, meaning "to leap".[7]). The unpronounced "l" absent from Middle English was later added as a Latinisation to make the word closer to its Latin root. The term salmon has mostly displaced its now dialectal synonym lax, in turn from Middle English: lax, from Old English: leax, from Proto-Germanic: *lahsaz from Proto-Indo-European: *lakso-.</p>
      <h2>Species</h2>
      <p>The seven commercially important species of salmon occur in two genera of the subfamily Salmoninae. The genus Salmo contains the Atlantic salmon, found in both sides of the North Atlantic, as well as more than 40 other species commonly named as trout. The genus Oncorhynchus contains 12 recognised species which occur naturally only in the North Pacific, six of which are known as Pacific salmon while the remainder are considered trout. Outside their native habitats, Chinook salmon have been successfully introduced in New Zealand and Patagonia, while coho, sockeye and Atlantic salmon have been established in Patagonia, as well.</p>
    </div>
  </body>
</html>

animation-range 参数基本上就是这些,利用这个参数我们可以灵活的设置元素进入和推出的偏移量这为我们的动画提供了很大的灵活性。

可视化工具

下面的工具是 Chrome for Developers 创建的可视化工具,你可以可视化 animation-range 的参数使用,调整它们试试效果。Output 对应者的是最终的 CSS 代码

<!DOCTYPE html>
<html lang="en" data-loading>

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>View Progress Timeline: Ranges and Animation Progress Visualizer</title>
	<link rel="stylesheet" href="/shared/styles.css">
	<script type="module">
		const sync = true;

		// Get params from URL
		const urlParams = new URLSearchParams(window.location.hash.replace('#',''));

		// Set values based on params, falling back to an initial value.
		let animation_range_start_name = urlParams.get('range-start-name') ?? 'cover';
		let animation_range_start_percentage = urlParams.get('range-start-percentage') ?? 0;
		let animation_range_end_name = urlParams.get('range-end-name') ?? 'cover';
		let animation_range_end_percentage = urlParams.get('range-end-percentage') ?? 100;
		let view_timeline_axis = 'block';
		let view_timeline_inset = urlParams.get('view-timeline-inset') ?? 0;
		let subject_size = urlParams.get('subject-size') ?? 'smaller';
		let progress = 0;
		let subject_animation = urlParams.get('subject-animation') ?? 'reveal';
		let interactivity = 'clicktodrag';
		let show_areas = ((urlParams.get('show-areas') ?? 'yes') === 'yes') ? true : false;
		let show_fromto = ((urlParams.get('show-fromto') ?? 'yes') === 'yes') ? true : false;
		let show_labels = ((urlParams.get('show-labels') ?? 'yes') === 'yes') ? true : false;

		const $animationLine = document.getElementById('animation-line');
		const $subject = document.getElementById('subject');
		const $subjectInner = document.getElementById('subject-inner');
		const $progress = document.getElementById('progress');
		const $scrollport = document.getElementById('scrollport');
		const $scrollbar = document.querySelector('#scrollbar');
		const $thumb = document.querySelector('#scrollbar #thumb');
		const $from = document.getElementById('from');
		const $to = document.getElementById('to');
		const $fromLabel = $from.querySelector('span');
		const $toLabel = $to.querySelector('span');
		const $pageContent = document.querySelector('.page-content');

		const $boxEndEdgeOutside = document.querySelector('.box[data-zone="end-edge-outside"]');
		const $boxEndEdgeInside = document.querySelector('.box[data-zone="end-edge-inside"]');
		const $boxStartEdgeOutside = document.querySelector('.box[data-zone="start-edge-outside"]');
		const $boxStartEdgeInside = document.querySelector('.box[data-zone="start-edge-inside"]');

		const render = () => {
			// Calculate positions for all ranges
			const rangePositions = {
				cover: {
					start: $boxEndEdgeOutside.getBoundingClientRect().y,
					end: $boxStartEdgeOutside.getBoundingClientRect().y,
				},
				contain: {
					start: subject_size == 'smaller' ? $boxEndEdgeInside.getBoundingClientRect().y : $boxStartEdgeInside.getBoundingClientRect().y,
					end: subject_size == 'smaller' ? $boxStartEdgeInside.getBoundingClientRect().y : $boxEndEdgeInside.getBoundingClientRect().y,
				},
				'entry-crossing': {
					start: $boxEndEdgeOutside.getBoundingClientRect().y,
					end: $boxEndEdgeInside.getBoundingClientRect().y,
				},
				'exit-crossing': {
					start: $boxStartEdgeInside.getBoundingClientRect().y,
					end: $boxStartEdgeOutside.getBoundingClientRect().y,
				},
				entry: {
					start: $boxEndEdgeOutside.getBoundingClientRect().y,
					end: subject_size == 'smaller' ? $boxEndEdgeInside.getBoundingClientRect().y : $boxStartEdgeInside.getBoundingClientRect().y,
				},
				exit: {
					start: subject_size == 'smaller' ? $boxStartEdgeInside.getBoundingClientRect().y : $boxEndEdgeInside.getBoundingClientRect().y,
					end: $boxStartEdgeOutside.getBoundingClientRect().y,
				}
			};

			// Position #from
			const startRangePosition = rangePositions[animation_range_start_name];
			$from.style.top = `${document.documentElement.scrollTop + startRangePosition.start + ((startRangePosition.end - startRangePosition.start) * animation_range_start_percentage) / 100}px`;

			// Position #to
			const endRangePosition = rangePositions[animation_range_end_name];
			$to.style.top = `${document.documentElement.scrollTop + endRangePosition.start + ((endRangePosition.end - endRangePosition.start) * animation_range_end_percentage) / 100}px`;

			// Position #animation-line
			$animationLine.style.top = `${$to.style.top}`;
			$animationLine.style.height = `${parseInt($from.style.top) + parseInt(getComputedStyle($from).height) - parseInt($to.style.top)}px`;

			// Sync some values
			document.documentElement.setAttribute('subject-size', subject_size);
			document.documentElement.setAttribute('subject-animation', subject_animation);
			document.documentElement.setAttribute('show-areas', show_areas ? 'yes' : 'no');
			document.documentElement.setAttribute('show-fromto', show_fromto ? 'yes' : 'no');
			document.documentElement.setAttribute('show-labels', show_labels ? 'yes' : 'no');
			document.documentElement.setAttribute('interactivity', interactivity);

			// Labels
			$fromLabel.innerText = `${animation_range_start_name} ${animation_range_start_percentage}%`;
			$toLabel.innerText = `${animation_range_end_name} ${animation_range_end_percentage}%`;

			// URL

			const output = [];
			output.push('#subject {');
			output.push(`  animation: ${subject_animation} linear both;`);
			output.push(`  animation-timeline: view(${view_timeline_axis});`);
			output.push(`  animation-range: ${animation_range_start_name} ${animation_range_start_percentage}% ${animation_range_end_name} ${animation_range_end_percentage}%;`);
			if (view_timeline_inset != 0) output.push(`  view-timeline-inset: ${view_timeline_inset}%;`);
			output.push(`}`);
			document.getElementById('output').textContent = output.join('\n');
			document.getElementById('output').setAttribute('data-lines', output.length);

			document.getElementById('range').innerText = `animation-range: ${animation_range_start_name} ${animation_range_start_percentage}% ${animation_range_end_name} ${animation_range_end_percentage}%;`;
		}

		const waitForTransitionAndRender = () => {
			$scrollport.addEventListener('transitionend', function() {
				setTimeout(render, 0);
			}, { once: true});
		}

		document.getElementById('animation-range-start-name').addEventListener('change', function (e) {
			animation_range_start_name = this.options[this.selectedIndex].value;
			if (document.getElementById('animation-range-end-name').selectedIndex == 0) animation_range_end_name = animation_range_start_name;
			render();
		});
		document.getElementById('animation-range-start-percentage').addEventListener('change', function (e) {
			animation_range_start_percentage = this.valueAsNumber;
			render();
		});
		document.getElementById('animation-range-end-name').addEventListener('change', function (e) {
			if (this.selectedIndex == 0) {
				animation_range_end_name = animation_range_start_name;
			} else {
				animation_range_end_name = this.options[this.selectedIndex].value;
			}
			render();
		});
		document.getElementById('animation-range-end-percentage').addEventListener('change', function (e) {
			animation_range_end_percentage = this.valueAsNumber;
			render();
		});
		document.getElementById('view-timeline-axis').addEventListener('change', function (e) {
			view_timeline_axis = this.options[this.selectedIndex].value;
			render();
		});
		document.getElementById('view-timeline-inset').addEventListener('change', function (e) {
			view_timeline_inset = this.valueAsNumber;
			document.documentElement.style.setProperty('--view-timeline-inset', view_timeline_inset); // CSS will take this into account
			waitForTransitionAndRender();
		});
		document.getElementById('subject-size-taller').addEventListener('change', function (e) {
			if (!this.checked) return;
			subject_size = this.value;
			document.documentElement.setAttribute('subject-size', subject_size); // CSS will take this into account
			waitForTransitionAndRender();
		});
		document.getElementById('subject-size-smaller').addEventListener('change', function (e) {
			if (!this.checked) return;
			subject_size = this.value;
			document.documentElement.setAttribute('subject-size', subject_size); // CSS will take this into account
			waitForTransitionAndRender();
		});
		// document.getElementById('interactivity-autoplay').addEventListener('change', function (e) {
		// 	if (!this.checked) return;
		// 	interactivity = this.value;
		// 	render();
		// });
		// document.getElementById('interactivity-clicktodrag').addEventListener('change', function (e) {
		// 	if (!this.checked) return;
		// 	interactivity = this.value;
		// 	render();
		// });
		document.getElementById('show-fromto').addEventListener('change', function (e) {
			show_fromto = this.checked;
			render();
		});
		document.getElementById('show-areas').addEventListener('change', function (e) {
			show_areas = this.checked;
			render();
		});
		document.getElementById('show-labels').addEventListener('change', function (e) {
			show_labels = this.checked;
			render();
		});
		document.getElementById('subject-animation').addEventListener('change', function (e) {
			subject_animation = this.options[this.selectedIndex].value;
			document.documentElement.style.setProperty('--subject-animation', `subject-animation--${subject_animation}`); // CSS will take this into account
			setTimeout(function() {
				$subjectInner.getAnimations()[0].currentTime = Math.min((progress * 10).toFixed(5), 1000);
			}, 0);
			render();
		});
		document.getElementById('reset').addEventListener('click', function (e) {
			if (!confirm('Are you sure?')) return;

			resetValues();
			syncValuesToDOM();
			setTimeout(() => {
				document.documentElement.removeAttribute('data-loading');
				render();
			}, 250); // @TODO: Find some nicer way to do this …
		});

		const resetValues = () => {
			animation_range_start_name = 'cover';
			animation_range_start_percentage = 0;
			animation_range_end_name = 'cover';
			animation_range_end_percentage = 100;
			view_timeline_axis = 'block';
			view_timeline_inset = 0;
			subject_size = 'smaller';
			progress = 0;
			subject_animation = 'reveal';
			interactivity = 'clicktodrag';
			show_areas = true;
			show_fromto = true;
			show_labels = true;
		}

		const syncValuesToDOM = () => {
			// Sync up controls to reflect the actual values
			document.querySelector('#animation-range-start-name').value = animation_range_start_name;
			document.querySelector('#animation-range-start-percentage').value = animation_range_start_percentage;
			document.querySelector('#animation-range-end-name').value = animation_range_end_name;
			document.querySelector('#animation-range-end-percentage').value = animation_range_end_percentage;
			document.querySelector('#view-timeline-axis').value = view_timeline_axis;
			document.querySelector('#view-timeline-inset').value = view_timeline_inset;
			document.querySelector('#subject-animation').value = subject_animation;
			document.querySelector('#subject-size-taller').checked = subject_size === 'taller';
			document.querySelector('#subject-size-smaller').checked = subject_size === 'smaller';
			document.querySelector('#interactivity-autoplay').checked = interactivity === 'autoplay';
			document.querySelector('#interactivity-clicktodrag').checked = interactivity === 'clicktodrag';
			document.querySelector('#show-fromto').checked = show_fromto;
			document.querySelector('#show-areas').checked = show_areas;
			document.querySelector('#show-labels').checked = show_labels;

			// Sync up DOM to reflect actual values
			document.documentElement.setAttribute('subject-size', subject_size);
			document.documentElement.setAttribute('subject-animation', subject_animation);
			document.documentElement.setAttribute('show-areas', show_areas ? 'yes' : 'no');
			document.documentElement.setAttribute('show-fromto', show_fromto ? 'yes' : 'no');
			document.documentElement.setAttribute('show-labels', show_labels ? 'yes' : 'no');
			document.documentElement.setAttribute('interactivity', interactivity);
			document.documentElement.style.setProperty('--view-timeline-inset', view_timeline_inset);
			document.documentElement.style.setProperty('--subject-animation', `subject-animation--${subject_animation}`);
		}

		window.addEventListener('resize', (e) => { setTimeout(render, 500); }); // @TODO: Make this nicer or fix the CSS transition
		window.addEventListener('orientationchange', (e) => { setTimeout(render, 500); }); // @TODO: Make this nicer or fix the CSS transition

		const updateProgress = (e) => {
			const animatedBoundingRect = $subject.getBoundingClientRect();
			const animationLineBoundingRect = $animationLine.getBoundingClientRect();

			let newProgress = 0;

			if ((animatedBoundingRect.y >= animationLineBoundingRect.y) && (animatedBoundingRect.y <= (animationLineBoundingRect.y + animationLineBoundingRect.height - animatedBoundingRect.height))) {
				$subject.classList.add('intersecting');
				newProgress = (1 - (animatedBoundingRect.y - animationLineBoundingRect.y) / (animationLineBoundingRect.height - animatedBoundingRect.height)) * 100;
			} else {
				if ((animatedBoundingRect.y >= animationLineBoundingRect.y)) {
					newProgress = 0;
				} else if((animatedBoundingRect.y <= (animationLineBoundingRect.y + animationLineBoundingRect.height - animatedBoundingRect.height))) {
					newProgress = 100;
				}
				$subject.classList.remove('intersecting');
			}
			if (newProgress !== progress) {
				$progress.innerText = `${newProgress.toFixed(5)}%`;
				progress = newProgress;
				$subjectInner.getAnimations()[0].currentTime = Math.min((newProgress * 10).toFixed(5), 1000);
			}
			requestAnimationFrame(updateProgress);
		};
		requestAnimationFrame(updateProgress); // @TODO: Use an IntersectionObserver for this.

		// Make scrollbar usable
		$thumb.addEventListener('pointerdown', (e) => {
			if (interactivity != 'clicktodrag') return;

			const duration = 10000;
			const scrollbarBox = $scrollbar.getBoundingClientRect();
			const thumbBox = $thumb.getBoundingClientRect();
			const minY = scrollbarBox.top;
			const maxY = scrollbarBox.top + scrollbarBox.height - thumbBox.height;
			const offsetInThumb = e.offsetY;

			const onMove = (e) => {
				const dragProgress = (e.clientY - offsetInThumb - minY) / (maxY - minY);
				$thumb.getAnimations()[0].currentTime = Math.max(0, Math.min(dragProgress, 1)) * duration;
				$pageContent.getAnimations()[0].currentTime = Math.max(0, Math.min(dragProgress, 1)) * duration;
			}

			const removeEventListener = () => {
				document.removeEventListener('pointermove', onMove);
			}
			document.addEventListener('pointermove', onMove);
			document.addEventListener('pointerup', removeEventListener);
		});

		syncValuesToDOM();
		setTimeout(() => {
			document.documentElement.removeAttribute('data-loading');
			render();
		}, 250); // @TODO: Find some nicer way to do this …

		document.documentElement.addEventListener('pointerdown', (e) => {
			document.documentElement.classList.add('interacted');
		}, { once: true });

		const $toggleControls = document.querySelector('#toggle-controls');
		$toggleControls.addEventListener('click', (e) => {
			const value = $toggleControls.getAttribute('aria-expanded');
			$toggleControls.setAttribute('aria-expanded', value === 'true' ? 'false' : 'true');
		});

		if (!document.documentElement.classList.contains('is-embed')) $toggleControls.setAttribute('aria-expanded', true);

		document.getElementById('range').addEventListener('click', (e) => {
			window.alert('Use the controls at the top right to change these values');
		})
	</script>
	<style>
		:root {
			--scrollbox-border-size: 1em;
			--scrollbox-height: 40vmin;
			--scrollbox-width: calc(var(--scrollbox-height) * 16 / 9);

			--box-height: 10vmin;
			--box-border-size: 0.25rem;

			--thumb-size: 15vmin;
			--scrollbar-width: 0.75rem;

			--shaded-size: 2px;
			--shaded-color: rgba(0 0 0 / 0.12);

			--animation-line-width: 4px;
			--animation-line-color: limegreen;

			--view-timeline-inset: 0;
			--visual-inset: calc(var(--scrollbox-height) * var(--view-timeline-inset) / 100);

			--content-height: calc(var(--scrollbox-height) * 3.25);
			--subject-animation: subject-animation--reveal;
		}

		:root[subject-size="taller"] {
			--box-height: calc(var(--scrollbox-height) * 1.1);
			--content-height: calc(var(--scrollbox-height) * 4.5);
		}

		#browser *,
		#browser *::after,
		#browser *::before,
		#animation-line,
		#from,
		#to {
			transition: all 0.25s ease-in-out;
		}

		html {
			background: white;
			color: black;
		}

		html,
		body {
			width: 100%;
			height: 100%;
			margin: 0;
			padding: 0;
			overflow: hidden;
		}

		body {
			display: grid;
			place-content: safe center;
		}

		#browser {
			width: var(--scrollbox-width);
			height: var(--scrollbox-height);
			border: max(1em, var(--scrollbox-border-size)) solid lightblue; /* This border is just to reserve some space*/
			border-radius: 0.5em;

			display: grid;
			grid-template-columns: 1fr var(--scrollbar-width);
			grid-template-rows: 1fr;
			grid-template-areas: "content scrollbar";

			position: relative;
			z-index: 1;

			position: fixed;
			left: 50%;
			top: 50%;
			transform: translate(-50%, -50%);
		}

		/* Inject the border on top of the browser, so that the content slides underneath */
		#browser::before {
			content: "";
			top: calc(var(--scrollbox-border-size) * -1);
			left: calc(var(--scrollbox-border-size) * -1);
			width: var(--scrollbox-width);
			height: var(--scrollbox-height);
			border: max(1em, var(--scrollbox-border-size)) solid lightblue;
			border-left-color: transparent;
			border-right-color: transparent;

			position: absolute;
			z-index: 2;
			pointer-events: none;
			box-shadow: 0px 0px 1em 0px rgb(0 0 0 / 0.5);
			border-radius: 0.5em;
		}

		#browser * {
			margin: 0;
			padding: 0;
			-webkit-user-select: none;
			user-select: none;
		}

		main {
			position: relative;
			background: transparent;
			z-index: 3;
			width: calc(var(--scrollbox-width) - var(--scrollbar-width));
			box-shadow: inset 0px 0px 1em 0px rgb(0 0 0 / 0.5);
			pointer-events: none;
		}

		main::before,
		main::after {
			white-space: nowrap;
			display: inline-block;
			position: absolute;
			left: -1rem;
			text-transform: uppercase;
			font-size: 0.8rem;
		}

		main::before {
			content: 'start edge + inset';
			top: 0;
			transform: translate3d(calc((1em + var(--scrollbox-border-size) + 100%) * -1), calc(0% - (var(--scrollbox-border-size) / 2) + var(--visual-inset)), 0);
		}

		main::after {
			content: 'end edge + inset';
			bottom: 0;
			transform: translate3d(calc((1em + var(--scrollbox-border-size) + 100%) * -1), calc(0% + (var(--scrollbox-border-size) / 2) - var(--visual-inset)), 0);
		}

		#scrollbar {
			grid-area: scrollbar;
			height: 100%;
			align-self: start;

			background: #ccc;
			padding: 0 1px;
		}

		#scrollbar #thumb {
			margin-top: 0;
			height: var(--thumb-size);
			width: 100%;
			background: #555;
			border-radius: 9999px;
		}

		#scrollbar #thumb::after {
			content: 'Drag Me ➡';
			color: red;
			position: absolute;
			right: calc(100% + 0.5rem);
			top: 2rem;
			display: block;
			white-space: nowrap;
			font-size: 2em;
			font-style: italic;
			transform: rotate(-2.5deg);
			transform-origin: 50% 50%;
		}

		.interacted #scrollbar #thumb::after {
			opacity: 0;
		}

		.box {
			height: var(--box-height);
			position: absolute;
			left: 0;
			right: 0;
			z-index: 1;
		}

		:root[data-loading] :is(.box, #subject) {
			opacity: 0 !important;
		}

		.box::after {
			text-transform: uppercase;
			font-size: 0.8rem;
			display: inline-block;
			position: absolute;
			left: -1rem;
			top: 50%;
			display: inline-block;
			content: attr(data-zone);
			transform: translate3d(calc((1em + var(--scrollbox-border-size) + 100%) * -1), calc(0% - (var(--scrollbox-border-size) / 2)), 0);
		}

		.box[data-zone] {
			background: repeating-linear-gradient(-45deg,
					var(--shaded-color),
					var(--shaded-color) var(--shaded-size),
					transparent var(--shaded-size),
					transparent calc(var(--shaded-size) * 5));
			opacity: 0.5;
			pointer-events: all;
		}

		.box[data-zone]:hover {
			opacity: 1;
		}

		:root[show-areas="no"] .box[data-zone] {
			opacity: 0;
		}

		:is(:root[show-labels="no"], :root.is-embed) main::before,
		:is(:root[show-labels="no"], :root.is-embed) main::after,
		:is(:root[show-labels="no"], :root.is-embed) .box[data-zone]::after {
			opacity: 0;
		}

		.box[data-zone]::after {
			content: attr(data-title-small);
			display: block;
		}

		.box[data-zone="end-edge-outside"] {
			transform: translateY(calc(var(--scrollbox-height) - var(--visual-inset)));
			border-top: var(--box-border-size) dashed #333;
		}

		.box[data-zone="end-edge-inside"] {
			transform: translateY(calc(var(--scrollbox-height) - var(--box-height) - var(--box-border-size) - var(--visual-inset)));
			border-bottom: var(--box-border-size) dashed #333;
		}

		.box[data-zone="start-edge-inside"] {
			transform: translateY(var(--visual-inset));
			border-top: var(--box-border-size) dashed #333;
		}

		.box[data-zone="start-edge-outside"] {
			transform: translateY(calc(-1 * (var(--box-height) + var(--box-border-size) - var(--visual-inset))));
			border-bottom: var(--box-border-size) dashed #333;
		}

		#base-line,
		#animation-line {
			width: 0;
			height: auto;

			border: 0.5em solid transparent;
			position: absolute;
			left: calc(50% - (var(--scrollbar-width) / 2));
			top: 50%;
			bottom: 50%;
			opacity: 0.8;
		}

		#animation-line {
			z-index: 0;
			left: calc(50% - ((var(--scrollbar-width) + var(--animation-line-width)) / 2));

			border-top: 0;
			border-bottom: 0;
			top: 50vh;
			height: 0;
			width: var(--animation-line-width);
			background: var(--animation-line-color);
			background-clip: content-box;
			opacity: 0;
		}

		#base-line {
			top: 0;
			bottom: 0;
			border-color: #ccc;
			min-height: calc(var(--scrollbox-height) + (var(--box-height) * 1.1 * 2) + (var(--scrollbox-border-size) * 2));
			display: none;
		}


		#from,
		#to,
		#subject {
			--shaded-color: #333;
			width: calc(var(--scrollbox-width) / 1.5);
			left: calc(50% - var(--scrollbox-width) / 3);

			height: calc(var(--box-height) + var(--box-border-size));

			background: repeating-linear-gradient(45deg,
					var(--shaded-color),
					var(--shaded-color) var(--shaded-size),
					transparent var(--shaded-size),
					transparent calc(var(--shaded-size) * 10));
			/* 	border-top: var(--box-border-size) solid lime; */

			position: absolute;

			outline: 2px dashed var(--shaded-color);

			display: grid;
			justify-content: start;
			align-items: start;
			font-size: 1.2em;
			-webkit-user-select: none;
			user-select: none;
		}
		#subject {
			justify-content: end;
		}

		#from,
		#to {
			opacity: 0.6;
		}

		:is(#from, #to, #subject) span {
			background-color: #FFF;
			padding: 0.25em;
		}

		:root[show-fromto="no"] :is(#from, #to) {
			opacity: 0;
		}

		#controls {
			position: fixed;
			right: 0;
			top: 0;
			background-color: #fff;
			padding: 1em;
			border-left: 0.5em solid #ccc;
			border-bottom: 0.5em solid #ccc;
			border-bottom-left-radius: 0.5em;
			z-index: 100;
		}

		body > h1 { /* sr-only */
			position: absolute;
			width: 1px;
			height: 1px;
			padding: 0;
			margin: -1px;
			overflow: hidden;
			clip: rect(0, 0, 0, 0);
			white-space: nowrap;
			border-width: 0;
		}

		@keyframes slide {
			to {
				translate: 0 calc((100% - var(--scrollbox-height)) * -1);
			}
		}

		:is(.page-content, #scrollbar #thumb) {
			animation: slide 10s linear 0s infinite alternate forwards;
			pointer-events: all;
			transition: none;
		}

		html[interactivity="clicktodrag"] :is(.page-content, #scrollbar #thumb) {
			animation-play-state: paused;
		}
		html[interactivity="clicktodrag"] #scrollbar #thumb {
			cursor: grab;
		}
		html[interactivity="clicktodrag"] #scrollbar #thumb:active,
		html[interactivity="clicktodrag"]:has(#scrollbar #thumb:active) {
			cursor: grabbing;
		}

		.page-content {
			height: var(--content-height);
			width: calc(var(--scrollbox-width) - var(--scrollbar-width));
			position: absolute;
			top: 0;
			left: 0;
			background-color: aliceblue;
			background-image: linear-gradient(to right, rgb(0 0 0 / 0.05) 1px, transparent 1px), linear-gradient(to bottom, rgb(0 0 0 / 0.05) 1px, transparent 1px);
			background-size: 10vh 10vh;
			z-index: -1;
		}

		#subject {
			top: calc(var(--scrollbox-height) * 1.5);
			left: calc(50% - (var(--scrollbox-width) / 3) + (var(--scrollbar-width) / 2));
			outline-color: rgba(255, 0, 0, 1);
			background: transparent;
		}

		#subject.intersecting {
			outline-color: rgba(50, 205, 50, 1);
		}

		#toggle-controls {
			font-size: .8em;
			margin-left: 100%;
			transform: translateX(-100%);
		}
		#toggle-controls[aria-expanded="true"]::after {
			content: 'hide controls';
			white-space: nowrap;
		}
		#toggle-controls[aria-expanded="false"]::after {
			content: 'show controls';
			white-space: nowrap;
		}

		#controls:has(#toggle-controls[aria-expanded="false"]) > *:not(#toggle-controls) {
			display: none;
		}

		fieldset:has(> #reset) {
			border: 0;
			text-align: center;
			padding: 14px 0 0 0;
		}

		@keyframes subject-animation--none /* Faked */ {
			from {
				opacity: 0;
			}
			to {
				opacity: 0;
			}
		}
		@keyframes subject-animation--scale-up {
			from {
				transform-origin: 50% 50%;
				scale: 0;
			}
			to {
				transform-origin: 50% 50%;
				scale: 1;
			}
		}
		@keyframes subject-animation--scale-down {
			from {
				transform-origin: 50% 50%;
				scale: 1;
			}
			to {
				transform-origin: 50% 50%;
				scale: 0;
			}
		}
		@keyframes subject-animation--reveal {
			from {
				opacity: 0;
				clip-path: inset(0% 60% 0% 50%);
			}
			to {
				opacity: 1;
				clip-path: inset(0% 0% 0% 0%);
			}
		}
		@keyframes subject-animation--fly-in {
			from {
				opacity: 0;
				translate: -100% 0 0;
			}
			to {
				opacity: 1;
				translate: 0 0 0;
			}
		}
		@keyframes subject-animation--fly-out {
			from {
				opacity: 1;
				translate: 0 0 0;
			}
			to {
				opacity: 0;
				translate: 100% 0 0;
			}
		}

		#subject-inner {
			position: absolute;
			height: 100%;
			width: 100%;
			z-index: -1;
			outline: 1px solid #333;
			animation: var(--subject-animation) 1s linear paused forwards;
			background-color: grey;
		}

		#range {
			position: absolute;
			width: max-content;
			text-align: center;
			top: 50%;
			left: 50%;
			transform: translate(-50%, -50%);
		}

		.is-embed .hide-when-embedded {
			display: none !important;
		}
		.hide {
			display: none !important;
		}

		body > *:not(#controls, #metabox) {
			-webkit-touch-callout: none;
		}

		#output {
			box-sizing: border-box;
			width: 100%;
			resize: none;
			height: calc(84px + 20px);
			padding: 10px;
			overflow: auto;
			font-family: "Monaspace", monospace;
			font-palette: --kung-fury;
			font-size: 12px;
			white-space: pre;
		}
		#output[data-lines="5"] {
			height: calc(72px + 20px);
		}
	</style>
	<link rel="stylesheet" href="/shared/styles.css">
	<script src="/shared/scripts.js"></script>
</head>

<body>
	<!-- Controls -->
	<div id="controls">
		<button id="toggle-controls" aria-expanded="false"></button>
		<fieldset>
			<legend>animation-range</legend>
			<label for="animation-range-start-name">animation-range-start <em>(name + %)</em></label>
			<div>
				<select name="animation-range-start-name" id="animation-range-start-name">
					<option value="cover">cover</option>
					<option value="contain">contain</option>
					<option value="entry-crossing">entry-crossing</option>
					<option value="entry">entry</option>
					<option value="exit-crossing">exit-crossing</option>
					<option value="exit">exit</option>
				</select>
				<input type="number" min="0" max="100" step="5" value="0" name="animation-range-start-percentage" id="animation-range-start-percentage">
			</div>
			<label for="animation-range-end-name">animation-range-end <em>(name + %)</em></label>
			<div>
				<select name="animation-range-end-name" id="animation-range-end-name">
					<option value="mirrored">(mirrored)</option>
					<option value="cover">cover</option>
					<option value="contain">contain</option>
					<option value="entry-crossing">entry-crossing</option>
					<option value="entry">entry</option>
					<option value="exit-crossing">exit-crossing</option>
					<option value="exit">exit</option>
				</select>
				<input type="number" min="0" max="100" step="5" value="100" name="animation-range-end-percentage" id="animation-range-end-percentage">
			</div>
		</fieldset>

		<fieldset>
			<legend>view-timeline</legend>
			<label for="view-timeline-axis">axis</label>
			<select name="view-timeline-axis" id="view-timeline-axis" disabled>
				<option value="block">block</option>
				<option value="inline">inline</option>
			</select>
			<label for="view-timeline-inset">inset <em>(%)</em></label>
			<input type="number" min="0" max="30" step="5" value="0" name="view-timeline-inset" id="view-timeline-inset">
		</fieldset>

		<fieldset>
			<legend>subject</legend>
			<label for="subject-animation">Animation</label>
			<div>
				<select name="subject-animation" id="subject-animation">
					<option value="none">(none)</option>
					<option value="scale-up">scale-up</option>
					<option value="scale-down">scale-down</option>
					<option value="reveal" selected>reveal</option>
					<option value="fly-in">fly-in</option>
					<option value="fly-out">fly-out</option>
				</select>
			</div>
			<label>Size</label>
			<div>
				<label><input type="radio" name="subject-size" id="subject-size-smaller" value="smaller" checked> Smaller than scrollport</label><br>
				<label><input type="radio" name="subject-size" id="subject-size-taller" value="taller"> Taller than scrollport</label>
			</div>
		</fieldset>

		<fieldset class="hide hide-when-embedded">
			<legend>Interactivity</legend>
			<div>
				<label><input type="radio" name="interactivity" id="interactivity-autoplay" value="autoplay"> Autoplay</label><br>
				<label><input type="radio" name="interactivity" id="interactivity-clicktodrag" value="clicktodrag" checked> Use scrollbar</label>
			</div>
		</fieldset>

		<fieldset class="hide-when-embedded">
			<legend>Visualization</legend>
			<label><input type="checkbox" name="show-fromto" id="show-fromto" checked> Show From/To Boxes</label><br>
			<label><input type="checkbox" name="show-areas" id="show-areas" checked> Show Areas</label><br>
			<label><input type="checkbox" name="show-labels" id="show-labels" checked> Show Labels</label>
		</fieldset>

		<fieldset>
			<legend>Output</legend>
			<textarea name="output" id="output" readonly></textarea>
		</fieldset>

		<fieldset>
			<button id="reset">Reset all values</button>
		</fieldset>
	</div>

	<!-- Visualization -->
	<h1><small>Scroll-driven Animations</small><br>View Progress Timeline<br>Ranges and Animation Progress Visualizer</h1>
	<div id="browser">
		<main id="scrollport" data-animation-range-start-name="cover" data-animation-range-start-percentage="0" data-animation-range-end-name="cover" data-animation-range-end-percentage="0" data-view-timeline-axis="block" data-view-timeline-inset="0">
			<!-- The four main zones around the edges -->
			<div class="box" data-zone="end-edge-outside" data-title-small="entry-crossing 0% – entry 0% – cover 0%"  data-title-tall="entry-crossing 0% – entry 0% – cover 0%"></div>
			<div class="box" data-zone="end-edge-inside" data-title-small="entry-crossing 100% – entry 100% – contain 0%"  data-title-tall="entry-crossing 100% – exit 0% – contain 100%"></div>
			<div class="box" data-zone="start-edge-inside" data-title-small="exit-crossing 0% – exit 0% – contain 100%"  data-title-tall="exit-crossing 0% – entry 100% – contain 0%"></div>
			<div class="box" data-zone="start-edge-outside" data-title-small="exit-crossing 100% – exit 100% – cover 100%" data-title-tall="exit-crossing 100% – exit 100% – cover 100%"></div>
		</main>
		<div class="page-content">
			<div id="subject">
				<div id="subject-inner"></div>
				<span id="progress">0.0000%</span>
				<span id="range">animation-range: cover 0% cover 100%;</span>
			</div>
		</div>
		<aside id="scrollbar">
			<div id="thumb"></div>
		</aside>
	</div>

	<!-- To from and to positions -->
	<div class="box" id="from"><span>cover 0%</span></div>
	<div class="box" id="to"><span>cover 100%</span></div>

	<!-- Line indicating the length of the animation -->
	<div id="animation-line"></div>
	<div id="base-line"></div>
	</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015" integrity="sha512-ZpsOmlRQV6y907TI0dKBHq9Md29nnaEIPlkf84rnaERnq6zvWvPUqr2ft8M1aS28oN72PdrCzSjY4U6VaAw1EQ==" data-cf-beacon='{"version":"2024.11.0","token":"4f175b1ac3204b9ca216125bb4bd6018","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>

</html>

总结

我们首先使用了 animation-timeline 属性来定义我们的动画时间轴,然后了解了 scroll()view() 函数的使用场景。然后可以利用 scroll-timeline 来控制基于那个滚动祖先元素做动画,最后详细介绍了 animation-range 属性的各种行为。

当遇到拿捏不准的时候可以利用 Chrome for Developers 工具去可视化它们,帮助我们生产 CSS 动画基础知识。