Skip to main content
  1. Posts/

开源库学习之Trigger JS

·2 分钟

简介 #

一直很好奇像苹果官网中,通过页面滚动实现的产品介绍效果是怎么做出来。

之前有简单了解过视觉差,主要是通过设置背景图来实现的。

今天了解到一个叫Trigger JS的库,在页面滚动时可以通过CSS变量取得动画所需要的值,而不需要额外写JS代码。

使用方法 #

Tigger JS中文文档 官方给的范例代码:

<div
      class="container"
      tg-name="size"
      tg-edge="inset"
      tg-from="14"
      tg-to="20"
      tg-bezier="0.23, 1.5, 0.32, 2"
    >
      <div class="container" id="content" tg-name="opacity" tg-from="0" tg-to="1">
        <div class="sticky">
          <span>Hello.</span>
        </div>
      </div>
    </div>
:root {
        font-family: Helvetica, sans-serif;
        font-size: 20px;
        --blur: 100;
        --opacity: 0;
      }

      body {
        padding: 0;
        margin: 0;
      }

      .container {
        height: 300vh;
      }

      .sticky {
        font-size: 10rem;
        font-weight: bold;
        letter-spacing: -0.03em;
        display: flex;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
        position: sticky;
        top: 0;
        font-size: calc(var(--size) * 1em);
      }

      span {
        opacity: var(--opacity);
      }

DOM元素上添加的tg-name属性的值就是页面滚动时需要改变的数值的CSS变量名。

divtag-name属性值设为size,css代码中就可以使用--size变量,上面的例子中,在设置font-size字体大小时读取了这个css变量,calc(var(--size) * 1em)相当于给变量设置了em作为单位。

div上还有tg-fromtg-to属性,代表的是size的值随着页面滚动由14变化到20tg-bezier是用来设置动画的贝塞尔曲线的。

而子div上的tag-name值为opacity,它的值变化是从01

我们当然也能通过监听元素的tag事件来获取到当前的值是多少:

document.querySelector('#content').addEventListener('tg', (e) => {
    console.log(e.detail); // {value: '1'}
  });

以上就是这个库的大致用法。

源码解析 #

源码结构
本文不会一行一行地解释源码,而是将重点放在实现逻辑上。

由上面的源码结构图可以看出我们需要着重解读的是trigger.ts中的代码。

index文件中调用了Trigger.start()方法,先来看看这个方法是什么:

const Trigger: TriggerType = {
  start() {
    if (!document.body) {
      console.warn(`Unable to initialise, document.body does not exist.`);
      return;
    }

    observeElements();
    eventListeners();
  },
};

方法里调用了两个私有函数:

// 初始化元素监听
function observeElements() {
    // observer是对IntersectionObserver API的封装,传入一个回调函数
  ob = observer((entries) => {
    entries.forEach((entry) => {
      // target:目标元素
      let { target } = entry;
      // 判断元素是否在当前视窗中
      if (entry.isIntersecting) {
        /*
            通过调用parseAttributes方法获取HTML上所有自定义属性的值,
            并返回一个带有原始el对象和所有属性值的对象
            {
                el: element,
                top,
                height,
                name,
                from,
                to,
                steps,
                step,
                mapping,
                filter,
                edge,
                range,
                increment,
                segments,
                decimals,
                multiplier,
                lastValue: null,
                bezier,
            }
            方便后续监听scroll事件时获取;
            把对象推入到一个监听数组中存起来
        */ 
        activeElements.push(parseAttributes(target as HTMLElement));
      } else {
        // 如果元素不在视窗中就从监听数组中移除
        activeElements = activeElements.filter(function (obj) {
          return obj.el !== target;
        });
      }
    });
  });
}

function eventListeners() {
  window.addEventListener('DOMContentLoaded', () => {
    /*
        bind()是对IntersectionObserver.observe()方法的封装,监听所有在视窗中的元素
        并添加两个钩子函数,before()和after(),在监听窗口resize事件时会使用到
    */ 
    bind(ob);

    setTimeout(() => {
      // 一开始就获取所有带有自定义属性的元素,并解析元素的属性值,保证页面滚动前可以获取到正确的样式
      let allElements = [
        ...document.querySelectorAll(`[${getPrefix()}name]`),
      ].map((element) => {
        return parseAttributes(element as HTMLElement);
      });
      /*
        parseValues是这个库最核心的计算方法,当监听到滚动事件时就会调用这个方法
        在这个方法中不仅会设置相应的style属性值
        并且当属性值发生变化的时候会将自定义tg事件通过el.dispatchEvent方法派发到监听的元素上
      */ 
      parseValues(allElements);
    });
  });

  // 当窗口大小发生变化时重新监听元素
  window.addEventListener('resize', () => {
    bind(ob, {
      // 调用 IntersectionObserver.observe()方法之前的钩子函数
      before: () => {
        // 解除所有监听数组中元素的监听
        activeElements.forEach((element) => {
          ob?.unobserve(element.el);
        });
        // 清除监听数组
        activeElements = [];
      },
    });
  });

  // 监听滚动事件,改变当前视窗内元素的属性值
  window.addEventListener('scroll', (e) => {
    parseValues(activeElements);
  });
}

实现逻辑 #

通过对trigger.ts代码的阅读,可以看出整个库最主要的实现原理就是通过IntersectionObserver对元素进行监听,然后使用parseValues()方法计算并设置元素的属性值。