学以致用是最快乐的事情
昨天我们认识了 IntersectionObserver,知道它可以侦测到元素进入画面的时机,而这个特性非常适合用来制作 Animation On Scroll 的卷动动画效果,像是 AOS 就是一个很经典的套件,虽然 AOS 其实不是用 IntersectionObserver 实践的,但我们可以尝试做出类似效果的工具。
1. 既然是工具,就代表要帮我们简化一些繁复的动作
在使用 IntersectionObserver 时,总是要进行的动作就是要建立实体然後注册目标元素,我希望可以只用一个指令就处理完这些事。这个需求应该可以包装一个「建构函式」来处理。
2. 这个工具需要侦测到元素进入可视窗口时,进行指定样式的变化
侦测元素的部分 IntersectionObserver 会帮我们处理,而元素的样式变化最快的方式就是利用 class
的增减了,但用 class
可能会有撞名的风险,所以我们改用 data-*
属性好了。而且改变属性这件事应该由工具处理,而不是我们都要写一遍。
3. 除了样式变化,还希望可以设定 Callback,让我们做一些额外的操作
由於在第一点已经决定设计一个「建构函式」来帮我们创建 IntersectionObserver 实体,那原本在创建时该传入的 Callback Function 和 options 物件就必须要先提供给「建构函式」,它才能帮我们建立实体。
整理完需求及机制後,就来开始动手吧,首先是为了让元素可以透过 data-*
属性的变化来转换样式,所以为想要有动画效果的元素加上 data-appear="hide"
,表示元素还没进入画面,未来只要把 data-appear
改成 show
,元素就会有样式的变化。
<div class="box" data-appear="hide"></div>
.box {
width: 500px;
height: 500px;
transition: 0.3s;
}
.box[data-appear="hide"] {
background: white;
}
.box[data-appear="show"] {
background: pink;
}
再来程序部分,先宣告一个叫做 Appear
建构函式,其中会有一个函式 init
,要用来创建 IntersectionObserver 实体以及注册要观察的元素,未来只要执行 appear.init()
一行就搞定了,而且因为已经确定只要有 data-appear
属性的元素就是要观察的对象,所以直接全部抓出来注册即可。
const Appear = function () {
this.init = function (callback, options) {
this.observer = new IntersectionObserver(callback, options);
const container = options.root || document;
const targetList = container.querySelectorAll("[data-appear]");
targetList.forEach((el) => {
this.observer.observe(el);
});
};
};
const appear = new Appear();
前面有说,工具需要自动改变元素的 data-appear
属性,而不是由我们手动写在 Callback 中,所以看来传入 init
的 callback
参数不能直接放到 IntersectionObserver 中,需要另外在包装一次。
包装後的 Callback 就可以在元素进出画面时进行 data-appear
属性的调整了,另外我们也将 entry.target
和改变後的状态传进 init
的 callback
中,让使用工具的人可以取得额外资讯。
const Appear = function () {
this.init = function (callback, options) {
// IntersectionObserver 要触发的 Callback Function
const obCallback = function (entries) {
entries.forEach((entry) => {
// 取得元素进入当下的状态
let state = entry.target.getAttribute("data-appear");
if (state === "hide" && entry.isIntersecting) {
// 从 hide 的状态下进到画面时...
state = "show";
entry.target.setAttribute("data-appear", state);
callback(entry.target, state);
} else if (state === "show" && !entry.isIntersecting) {
// 从 show 的状态下离开画面时...
state = "hide";
entry.target.setAttribute("data-appear", state);
callback(entry.target, state);
}
});
};
this.observer = new IntersectionObserver(obCallback, options);
const container = options.root || document;
const targetList = container.querySelectorAll("[data-appear]");
targetList.forEach((el) => {
this.observer.observe(el);
});
};
};
const appear = new Appear();
再来其实我觉得原本 IntersectionObserver 的 options
参数设定有点不是很容易懂,所以我们自己设计一个新的物件做为 init
的参数设定,以下是它的属性名称及预设值,顺便也把 callback
一起放进去了:
const defaultOptions = {
container: null,
offsetTop: 0,
offsetRight: 0,
offsetBottom: 0,
offsetLeft: 0,
threshold: 0,
callback: function () {},
};
这样在使用工具的人只要传一个参数到 init
中就好,而且属性名称也比较好了解,传进去後只要在 init
内部再转换成原本 IntersectionObserver 接受的格式即可。
const Appear = function () {
this.init = function (userOptions) {
const options = { ...defaultOptions, ...userOptions };
const obOptions = {
root: options.container,
rootMargin: [
`${options.offsetTop}px`,
`${options.offsetRight}px`,
`${options.offsetBottom}px`,
`${options.offsetLeft}px`,
].join(" "),
threshold: options.threshold,
};
//...其他省略
this.observer = new IntersectionObserver(obCallback, obOptions);
};
};
const appear = new Appear();
最後为了避免有人重复执行 init
,我们在最前面进行判断来阻挡,额外也可以再做一个关闭 IntersectionObserver 的功能:
const Appear = function () {
this.init = function (userOptions) {
if (this.observer) return;
//...其他省略
};
// 注销所有观察元素并释放 observer
this.destroy = function () {
if (!this.observer) return;
this.observer.disconnect();
this.observer = null;
};
};
const appear = new Appear();
以上的 JS 程序码我们可以包成一支 appear.js
档案,只要未来有专案需要使用时直接引入并且呼叫 init
就可以了:
<style>
.box {
width: 500px;
height: 500px;
margin: 30px auto;
transition: 0.3s;
}
.box[data-appear="hide"] {
background: white;
}
.box[data-appear="show"] {
background: pink;
}
</style>
<div class="box" data-appear="hide"></div>
<div class="box" data-appear="hide"></div>
<div class="box" data-appear="hide"></div>
<script src="appear.js"></script>
<script>
appear.init({
threshold: 0.5,
callback: function (target, state) {
console.log(target, state);
},
});
</script>
这样的工具是不是非常实用呢?而且能够自己做出一个以後也能够不断使用的小套件真的是非常有成就感,有兴趣的小夥伴们也可以发挥自己的创新,把这个小工具不断的扩充,增加新功能喔。完整原始码,我就放在 CodePen,文章中如果有不清楚的,可以再去看看。
<<: Day23 - 针对 Metasploitable 3 进行渗透测试(4) - 认识 Metasploit
>>: Leetcode: 99. Recover Binary Search Tree | 含C++笔记
人类沟通需要技巧,程序语言靠的是方法。 我们除了可以透过 Props 来让父元件传值给子元件外,也...
今天就来讲讲最基本的静态爬虫吧,这里要装的套件是bs4(BeautifulSoup4),它是用来做静...
T0814 Denial of Service 攻击者为了破坏设备的功能,使用阻断服务的攻击,会在短...
第七天 各位点进来的朋友,你们好阿 小的不才只能做这个系列的文章,但还是希望分享给点进来的朋友,知道...
前言 昨天我们使用预训练模型EfficientNet去提取一张表情的高阶特徵图(1280张特徵图),...