🥰Giải thích dễ hiểu onEnter, onLeave, sử dụng start, end như nào FULL Phần 2 (ok)
Example 1: Sử dụng Absolute Cards
— Hiện tại với cấu hình này chưa có gì sảy ra vẫn như bình thường các khối Card 1, Card 2, Cart 3 đang bị ẩn dưới wrapper
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodePen - Loop Absolute Cards</title>
<link rel='stylesheet' href='https://codepen.io/GreenSock/pen/xxmzBrw.css'>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.panel,
.wrapper {
position: relative;
width: 100%;
min-height: unset;
height: 500px;
}
.card {
position: absolute;
left: 50%;
bottom: 0;
visibility: hidden;
font-size: 30px;
width: 100vw;
text-align: center;
}
.card:nth-child(1) {
background-color: red;
}
.card:nth-child(2) {
background-color: blueviolet;
}
.card:nth-child(3) {
background-color: cadetblue;
}
.card p {
margin: 0;
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<main>
<div class="panel gradient-green"></div>
<div class="wrapper">
<div class="card">
<p>Card 1</p>
</div>
<div class="card">
<p>Card 2</p>
</div>
<div class="card">
<p>Card 3</p>
</div>
</div>
<div class="panel gradient-blue"></div>
</main>
<!-- partial -->
<script src='https://unpkg.com/gsap@3/dist/gsap.min.js'></script>
<script src='https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js'></script>
<script>
console.clear();
gsap.registerPlugin(ScrollTrigger);
const cards = gsap.utils.toArray(".card");
gsap.set(cards, {
xPercent: -50,
yPercent: 100,
autoAlpha: 1
});
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: "power1.inOut"
},
scrollTrigger: {
trigger: ".wrapper",
start: "top top",
end: "+=100%",
pin: "main",
scrub: true,
markers: true
}
});
</script>
</body>
</html>


Chú ý 1: Nếu sử dụng pin: true thì nó sẽ pin lên .wrapper

Chú ý 2: Nếu không sử dụng scrub: true thì các card chạy một mạch hết luôn (dường như để nó kích hoạt các hiệu ứng timeline được định nghĩa theo một cách tuần tự)
— Sử dụng cấu hình sau tất cả các card đều hiệu ứng như nhau không có khác biệt gì. (do đó ở trường hợp sau chúng ta sẽ tìm cách chỉ áp dụng lần lượt cho từng phần tử)

console.clear();
gsap.registerPlugin(ScrollTrigger);
const cards = gsap.utils.toArray(".card");
gsap.set(cards, {
xPercent: -50,
yPercent: 100,
autoAlpha: 1
});
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: "power1.inOut"
},
scrollTrigger: {
trigger: ".wrapper",
start: "top top",
end: "+=100%",
pin: "main",
scrub: true,
markers: true
}
});
cards.forEach((card, i) => {
tl.to(
card,
{
y: -window.innerHeight * 0.5,
yPercent: 0
},
"<"
);
});
— Với cấu hình sau thì chỉ áp dụng cho một phần tử duy nhất
console.clear();
gsap.registerPlugin(ScrollTrigger);
const cards = gsap.utils.toArray(".card");
gsap.set(cards, {
xPercent: -50,
yPercent: 100,
autoAlpha: 1
});
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: "power1.inOut"
},
scrollTrigger: {
trigger: ".wrapper",
start: "top top",
end: "+=100%",
pin: "main",
scrub: true,
markers: true
}
});
cards.forEach((card, i) => {
tl.to(
card,
{
y: -window.innerHeight * 0.5,
yPercent: 0
},
"<"
).to(card, {
autoAlpha: 0,
duration: 0.5
});
});

Toàn bộ code ở đây
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodePen - Loop Absolute Cards</title>
<link rel='stylesheet' href='https://codepen.io/GreenSock/pen/xxmzBrw.css'>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.panel,
.wrapper {
position: relative;
width: 100%;
min-height: unset;
height: 500px;
}
.card {
position: absolute;
left: 50%;
bottom: 0;
visibility: hidden;
font-size: 30px;
width: 100vw;
text-align: center;
}
.card:nth-child(1) {
background-color: red;
}
.card:nth-child(2) {
background-color: blueviolet;
}
.card:nth-child(3) {
background-color: cadetblue;
}
.card p {
margin: 0;
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<main>
<div class="panel gradient-green"></div>
<div class="wrapper">
<div class="card">
<p>Card 1</p>
</div>
<div class="card">
<p>Card 2</p>
</div>
<div class="card">
<p>Card 3</p>
</div>
</div>
<div class="panel gradient-blue"></div>
</main>
<!-- partial -->
<script src='https://unpkg.com/gsap@3/dist/gsap.min.js'></script>
<script src='https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js'></script>
<script>
console.clear();
gsap.registerPlugin(ScrollTrigger);
const cards = gsap.utils.toArray(".card");
gsap.set(cards, {
xPercent: -50,
yPercent: 100,
autoAlpha: 1
});
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: "power1.inOut"
},
scrollTrigger: {
trigger: ".wrapper",
start: "top top",
end: "+=100%",
pin: "main",
scrub: true,
markers: true
}
});
cards.forEach((card, i) => {
tl.to(
card,
{
y: -window.innerHeight * 0.5,
yPercent: 0
},
"<"
).to(card, {
autoAlpha: 0,
duration: 0.5
});
});
</script>
</body>
</html>
Example 2: Xếp trồng layout 👌
Việc sử dụng hết sức đơn giản chỉ cần sử dụng
ScrollTrigger.create({
trigger: ".hero",
start: "top top",
pin: true,
pinSpacing: false
});
ScrollTrigger.create({
trigger: ".first",
start: "top top",
pin: true,
pinSpacing: false
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodePen - A Pen by Rodrigo Hernando</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
<link rel='stylesheet' href='https://codepen.io/GreenSock/pen/xxmzBrw.css'>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.hero,
.panel {
position: relative;
min-height: unset;
height: 400px;
}
.content {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.content h3 {
width: 100%;
text-align: center;
color: var(--dark);
margin: 75px 0;
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<div class="hero panel center">
<h1>HERO</h1>
</div>
<div class="first panel gradient-blue">
<div class="content">
<h3>Lorem, ipsum dolor.</h3>
<h3>Earum, ad quidem!</h3>
<h3>Iusto, minus quis?</h3>
<h3>Aut, quae quidem!</h3>
</div>
</div>
<div class="second panel gradient-blue">
<div class="content">
<h3>Lorem, ipsum dolor.</h3>
<h3>Earum, ad quidem!</h3>
<h3>Iusto, minus quis?</h3>
<h3>Aut, quae quidem!</h3>
</div>
</div>
<div class="panel gradient-green"></div>
<!-- partial -->
<script src='https://unpkg.com/gsap@3/dist/gsap.min.js'></script>
<script src='https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js'></script>
<script>
console.clear();
gsap.registerPlugin(ScrollTrigger);
ScrollTrigger.create({
trigger: ".hero",
start: "top top",
pin: true,
pinSpacing: false
});
ScrollTrigger.create({
trigger: ".first",
start: "top top",
pin: true,
pinSpacing: false
});
</script>
</body>
</html>

Example 3: Xếp trồng layout dạn Accordion
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodePen - GSAP Accordion - KK</title>
<style>
body {
margin: 0;
background: #fff;
font-family: sans-serif;
background: linear-gradient(120deg, #5c2fa6 13.57%, #5a36c0 98.38%);
scroll-behavior: none;
}
.title {
font-size: max(2vw, 24px);
line-height: 1.1;
padding-bottom: 0.4em;
color: rgb(255, 255, 255);
text-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.text {
font-size: max(1vw, 15px);
line-height: 1.4;
overflow: hidden;
padding-bottom: 20px;
color: rgba(255, 255, 255, 0.7);
}
.accordions {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 20vh;
}
.accordion {
overflow: hidden;
background: linear-gradient(200deg,
rgb(29, 145, 252) 13.57%,
rgb(90, 54, 192) 98.38%);
width: max(50vw, 280px);
padding: 0 25px;
box-shadow: 0 30px 30px -10px rgba(0, 0, 0, 0.3);
&:nth-child(2) {
background: linear-gradient(200deg,
rgb(242, 136, 133) 13.57%,
rgb(233, 79, 102) 98.38%);
}
&:nth-child(3) {
background: linear-gradient(200deg,
rgb(101, 187, 118) 13.57%,
rgb(70, 111, 171) 98.38%);
}
&:nth-child(4) {
background: linear-gradient(200deg, #c215d1 13.57%, #9813a1 98.38%);
}
}
.spacer {
height: 70vh;
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/ScrollTrigger.min.js"></script>
<div id="wrapper">
<div id="content">
<div class="spacer"></div>
<div class="accordions">
<div class="title">Scroll Accordions</div>
<div class="accordion">
<div class="title">All-screen design.</div>
<div class="text">
Lets you immerse yourself in whatever you’re reading, watching, or
creating. The 10.9-inch Liquid Retina display features advanced
technologies like True Tone, P3 wide color, and an antireflective
coating.1
</div>
</div>
<div class="accordion">
<div class="title">Beauty all around.</div>
<div class="text">
The breakthrough M1 chip is now in Air. An 8-core CPU delivers up
to 60 percent faster performance than the previous generation,
making Air a creative and mobile gaming powerhouse. Multitask
smoothly between powerful apps and play graphics-intensive games.
And with M1, you can go even further with your creativity with
apps like SketchUp.
</div>
</div>
<div class="accordion">
<div class="title">Take Center Stage.</div>
<div class="text">
The 12MP Ultra Wide front camera enables Center Stage, making
video calls more natural and content creation more fun. As you
move around, the camera automatically pans to keep you centered in
the shot. When others join or leave the frame, the view expands or
zooms in.
</div>
</div>
<div class="accordion">
<div class="title">Pretty everywhere.</div>
<div class="text">
Join superfast 5G wireless networks when you’re on the go.
Download files, play multiplayer games, stream movies, check in
with friends, and more.
</div>
</div>
</div>
<div class="spacer"></div>
</div>
</div>
</body>
<!-- partial -->
<script>
gsap.registerPlugin(ScrollTrigger);
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".accordions",
pin: true,
start: "top top",
end: "bottom top",
scrub: 1,
ease: "linear"
}
});
tl.to(".accordion", {
height: 0,
opacity: 1,
stagger: 0.5
});
</script>
</body>
</html>

Chú ý
Toàn bộ code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodePen - Right Column Callbacks</title>
<link rel='stylesheet' href='https://codepen.io/GreenSock/pen/xxmzBrw.css'>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.section {
width: 100%;
height: 100vh;
}
.column-wrapper {
width: 100%;
display: flex;
}
.column {
flex: 1 0 50%;
min-height: 100vh;
}
.column.right {
height: 100vh;
overflow: hidden;
}
.column-content {
width: 100%;
height: 100vh;
font-size: 40px;
display: flex;
justify-content: center;
align-items: center;
color: var(--dark);
}
.column-content.image img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<section class="section gradient-green"></section>
<section class="column-wrapper">
<div class="column left gradient-purple">
<div class="column-content">
<h1>H1 Element 1</h1>
</div>
<div class="column-content">
<h1>H1 Element 2</h1>
</div>
<div class="column-content">
<h1>H1 Element 3</h1>
</div>
<div class="column-content">
<h1>H1 Element 4</h1>
</div>
<div class="column-content">
<h1>H1 Element 5</h1>
</div>
<div class="column-content">
<h1>H1 Element 6</h1>
</div>
</div>
<div class="column right gradient-orange">
<div class="column-content image">
<img
src="https://images.unsplash.com/photo-1535730480175-8f43dbb6f894?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="">
</div>
<div class="column-content image">
<img
src="https://images.unsplash.com/photo-1472791108553-c9405341e398?q=80&w=2137&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="">
</div>
<div class="column-content image">
<img
src="https://images.unsplash.com/photo-1455156218388-5e61b526818b?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="">
</div>
<div class="column-content image">
<img
src="https://images.unsplash.com/photo-1611252871536-305c663777ab?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="">
</div>
<div class="column-content image">
<img
src="https://images.unsplash.com/photo-1503614472-8c93d56e92ce?q=80&w=2011&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="">
</div>
<div class="column-content image">
<img
src="https://images.unsplash.com/photo-1715604723676-7e7fb8607478?q=80&w=2072&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="">
</div>
</div>
</section>
<section class="section gradient-blue"></section>
<!-- partial -->
<script src='https://unpkg.com/gsap@3/dist/gsap.min.js'></script>
<script src='https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js'></script>
<script>
console.clear();
gsap.registerPlugin(ScrollTrigger);
const titleContainers = gsap.utils.toArray(".column.left .column-content");
const images = gsap.utils.toArray(".column-content.image");
ScrollTrigger.create({
trigger: ".column-wrapper",
start: "top top",
end: "+=" + (images.length - 1) * 100 + "%",
pin: true,
markers: true
});
</script>
</body>
</html>
— Chú ý 1: Nếu có class pin-spacer thì khi scroll chưa vượt ra khỏi khung Scroller Start và Scroller End thì nội dung trong pin-spacer không dịch chuyển, trong trường hợp này pin: true do đó .column-wrapper không di chuyển trong khung

console.clear();
gsap.registerPlugin(ScrollTrigger);
const titleContainers = gsap.utils.toArray(".column.left .column-content");
const images = gsap.utils.toArray(".column-content.image");
ScrollTrigger.create({
trigger: ".column-wrapper",
start: "top top",
end: "+=" + (images.length - 1) * 100 + "%",
pin: true,
markers: true
});
— Chú ý 2: Nếu sử dụng pin: ".column.right" khi đó .column.right không di chuyển khi chưa vượt qua khung nhìn, còn .column.left vẫn di chuyển theo mặc định
console.clear();
gsap.registerPlugin(ScrollTrigger);
const titleContainers = gsap.utils.toArray(".column.left .column-content");
const images = gsap.utils.toArray(".column-content.image");
ScrollTrigger.create({
trigger: ".column-wrapper",
start: "top top",
end: "+=" + (images.length - 1) * 100 + "%",
pin: ".column.right",
markers: true
});

Chú ý 3: Khi scroll lên trên scroller start và scroller end thì sẽ là onEnter, ngược lại sẽ là onEnterBack
console.clear();
gsap.registerPlugin(ScrollTrigger);
const titleContainers = gsap.utils.toArray(".column.left .column-content");
const images = gsap.utils.toArray(".column-content.image");
ScrollTrigger.create({
trigger: ".column-wrapper",
start: "top top",
end: "+=" + (images.length - 1) * 100 + "%",
pin: ".column.right",
markers: true
});
titleContainers.forEach((title, i) => {
ScrollTrigger.create({
trigger: title,
start: "top 60%",
end: "top 60%",
markers: {
indent: 150 * (i + 1)
},
id: i + 1,
onEnter: () => {
console.log("onEnter");
if (i) {
gsap.to(images, {
yPercent: -100 * i,
ease: "power1.inOut",
});
}
},
onEnterBack: () => {
console.log("onEnterBack");
if (i) {
gsap.to(images, {
yPercent: -100 * (i - 1),
ease: "power1.inOut"
});
}
}
});
});

PreviousGiải thích dễ hiểu onEnter, onLeave, sử dụng start, end như nào FULL Phần 1 (ok)NextPhương thức onUpdate sử dụng như nào, có sử dụng tương tự onEnter, onLeave ?
Last updated