Based in London, we'd like to introduce the portfolio site of Jason Harvey, who works as a web designer and web developer. His portfolio site, "somefolk," is really cool.
While "somefolk" as a portfolio site is impressive on its own, what stands out across all of his showcased works are his excellent sense of animation and easing.
Let's learn from the techniques used in Jason Harvey's portfolio site while showing our respect to him.
Requirements definition
PC
◼︎ Momentum Scrolling
◼︎ Parallax Display in the Main Visual
◼︎ Partially Fixed Elements
◼︎ Changing the Scale of Images within Fixed Elements
Mobile
◼︎ Parallax Display in the Main Visual
◼︎ Partially Fixed Elements
◼︎ Changing the Scale of Images within Fixed Elements
Conclusion
1.For momentum scrolling, we use "lenis."
2.For parallax, element fixation, and image scaling, we use "gsap" and "scroll trigger."
3.There is no need for dedicated CSS for each of these libraries.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<meta name="description" content="Description" />
<link rel="stylesheet" href="https://portfolio-technics.com/sample/0-css/style.css" />
<!-- style -->
<style>
.mv {
height: 50vh;
margin-bottom: 4rem;
overflow: hidden;
position: relative;
width: 100%;
}
.mv img {
-webkit-transform: translateX(-50%) scale(1.2);
-webkit-transform-origin: 50% 50%;
display: block;
height: auto;
left: 50%;
position: absolute;
top: 0;
transform: translateX(-50%) scale(1.2);
transform-origin: 50% 50%;
width: 100%;
}
.fix {
margin: 0 auto;
max-width: 96rem;
}
.fix__list {
display: flex;
flex-direction: column;
margin-bottom: 4rem;
row-gap: 4rem;
}
.fix__item {
aspect-ratio: 1080/608;
overflow: hidden;
}
.fix__item img {
display: block;
height: auto;
width: 100%;
}
.fix__unfix {
align-items: center;
background-color: black;
color: #fff;
display: flex;
height: 300rem;
justify-content: center;
margin-bottom: 4rem;
position: relative;
}
.footer {
align-items: center;
background-color: black;
color: #fff;
display: flex;
height: 400rem;
justify-content: center;
}
</style>
<!-- style -->
</head>
<body>
<div id="js-page" class="page">
<main class="main page__main">
<div class="mv js-parallax">
<img loading="lazy" src="https://dl.dropbox.com/s/4hrgv4272zvctj6/sample20.jpg?dl=0" alt="" />
</div>
<div class="fix">
<ul class="fix__list">
<li class="fix__item js-fix --1">
<img loading="lazy" src="https://dl.dropbox.com/s/oq7so2vc6j7wgk6/sample1.jpg?dl=0" alt="" />
</li>
<li class="fix__item js-fix --2">
<img loading="lazy" src="https://dl.dropbox.com/s/03sa966wy301vt1/sample2.jpg?dl=0" alt="" />
</li>
<li class="fix__item js-fix --3">
<img loading="lazy" src="https://dl.dropbox.com/s/aa7uk8vh5hhermx/sample3.jpg?dl=0" alt="" />
</li>
<li class="fix__item js-fix --4">
<img loading="lazy" src="https://dl.dropbox.com/s/9h64k0vppliw3m9/sample4.jpg?dl=0" alt="" />
</li>
</ul>
<div class="fix__unfix js-unfix">
<p>
The upper items will be unfixed when this content reaches the top of the screen.
</p>
</div>
</div>
</main>
<footer class="footer page__footer">
<p>
footer
</p>
</footer>
</div>
<!-- CDN -->
<script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@1.0.25/bundled/lenis.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<!-- js -->
<script>
class MomentumLenis {
constructor() {
this._lenisInit();
}
_lenisInit() {
gsap.registerPlugin(ScrollTrigger);
const lenis = new Lenis({
lerp: 0.1,
});
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
// Images parallax
gsap.utils.toArray(".js-parallax").forEach((parallaxBoxes) => {
const parallaxImages = parallaxBoxes.querySelector("img");
const tl = gsap.timeline({
scrollTrigger: {
trigger: parallaxBoxes,
scrub: true,
pin: false,
// markers: true,
},
});
tl.fromTo(
parallaxImages,
{
yPercent: -20,
ease: "none",
},
{
yPercent: 20,
ease: "none",
}
);
});
// Fix the li elements and gradually scale the images on scroll
gsap.utils.toArray(".js-fix").forEach((fixItem) => {
const image = fixItem.querySelector("img");
const tl = gsap.timeline({
scrollTrigger: {
trigger: fixItem,
start: "top 10px",
endTrigger: ".js-unfix",
end: "top 10px", // Change the end condition to keep the item pinned until it reaches the top
scrub: 1,
pin: true,
pinSpacing: false,
// markers: true,
},
});
tl.to(fixItem, { y: 0, ease: "none" }).to(
image,
{
scale: 1.2,
ease: "none",
},
0
);
});
}
}
new MomentumLenis();
</script>
<!-- js -->
</body>
</html>
Practice
❶Implement momentum scrolling with Lenis.
basic usage|based in official site.
https://github.com/studio-freight/lenis
// smooth scroll setting
const lenis = new Lenis({
lerp: 0.2, // Linear interpolation (lerp) intensity (between 0 and 1)
duration: 1, // The duration of scroll animation (in seconds). Useless if lerp defined
});
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
❷In addition to Step 1, implement image parallax effect and zoom animation using GSAP + Scroll Trigger.
Combine GSAP & Scroll Trigger with Lenis | Based on the official website.
https://github.com/studio-freight/lenis#gsap-scrolltrigger-integration
https://greensock.com/scrolltrigger/
gsap.registerPlugin(ScrollTrigger);
const lenis = new Lenis({
lerp: 0.1,
});
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
// Images parallax
gsap.utils.toArray(".js-parallax").forEach((parallaxBoxes) => {
const parallaxImages = parallaxBoxes.querySelector("img");
const tl = gsap.timeline({
scrollTrigger: {
trigger: parallaxBoxes,
scrub: true,
pin: false,
// markers: true,
},
});
tl.fromTo(
parallaxImages,
{
yPercent: -20,
ease: "none",
},
{
yPercent: 20,
ease: "none",
}
);
});
❸In addition to Step 2, fix elements midway through scrolling and change their scale.
const lenis = new Lenis({
lerp: 0.1,
});
gsap.registerPlugin(ScrollTrigger);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
lenis.on("scroll", ScrollTrigger.update);
// Images parallax
gsap.utils.toArray(".js-parallax").forEach((parallaxBoxes) => {
const parallaxImages = parallaxBoxes.querySelector("img");
const tl = gsap.timeline({
scrollTrigger: {
trigger: parallaxBoxes,
scrub: true,
pin: false,
// markers: true,
},
});
tl.fromTo(
parallaxImages,
{
yPercent: -20,
ease: "none",
},
{
yPercent: 20,
ease: "none",
}
);
});
// Fix the li elements and gradually scale the images on scroll
gsap.utils.toArray(".js-fix").forEach((fixItem) => {
const image = fixItem.querySelector("img");
const tl = gsap.timeline({
scrollTrigger: {
trigger: fixItem,
start: "top 10px",
endTrigger: ".js-unfix",
end: "top 10px", // Change the end condition to keep the item pinned until it reaches the top
scrub: 1,
pin: true,
pinSpacing: false,
// markers: true,
},
});
tl.to(fixItem, { y: 0, ease: "none" }).to(
image,
{
scale: 1.2,
ease: "none",
},
0
);
});
Top comments (0)