I’m building a scroll effect where .logo
and .content
elements inside .container
blocks use position: sticky
and also receive scroll-based top
position adjustments using jQuery.
The goal is to keep these elements visually synced while scrolling through multiple full-height sections.
A smooth sticky scroll where .logo
and .content
stay perfectly in sync without visual shaking or micro-jumps.
Any advice or better way to handle this?
// Auto-jquery-cache function
$C = (($) => {
let c = {};
return (s, f) => {
if (!c[s] || f) {
c[s] = $(s);
}
return c[s];
};
})($);
$(document).ready(function() {
const $diamond = $C('.diamond').find('.logo');
const $ruby = $C('.ruby').find('.logo');
const $emerald = $C('.emerald').find('.logo');
// Add the same cache for the content divs
const $diamondContent = $C('.diamond').find('.content');
const $rubyContent = $C('.ruby').find('.content');
const $emeraldContent = $C('.emerald').find('.content');
const diamondTop = $diamond.offset().top;
const diaRelTop = $diamond.position().top;
const rubyTop = $ruby.offset().top;
const emeraldTop = $emerald.offset().top;
// Initial position adjustment
$ruby.css('top', -rubyTop + diamondTop + diaRelTop);
$emerald.css('top', -emeraldTop + diamondTop + diaRelTop);
// Synchronize position for content elements
$rubyContent.css('top', -rubyTop + diamondTop + diaRelTop);
$emeraldContent.css('top', -emeraldTop + diamondTop + diaRelTop);
const handle = (e) => {
e.preventDefault();
const t = $(window).scrollTop();
// Update the position for both .logo and .content
$diamond.css('top', t + diamondTop);
$ruby.css('top', -rubyTop + $diamond.offset().top + diaRelTop);
$emerald.css('top', -emeraldTop + $diamond.offset().top + diaRelTop);
$diamondContent.css('top', t + diamondTop);
$rubyContent.css('top', -rubyTop + $diamond.offset().top + diaRelTop);
$emeraldContent.css('top', -emeraldTop + $diamond.offset().top + diaRelTop);
};
$(window).on('scroll', handle);
});
*,
*:before,
*:after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
overflow-x: hidden;
}
body {
-webkit-text-size-adjust: none;
background: #fff;
}
.fixed {
position: fixed;
width: 100%;
text-align: center;
z-index: 10;
background: white;
}
.container {
height: 100dvh;
overflow: hidden;
position: relative;
}
.container .image-container img {
width: 100%;
height: auto;
object-fit: cover;
}
.container .logo {
position: relative;
/* top: 0%;
left: calc(50% - 440px); */
width: 500px;
height: 400px;
object-fit: cover;
}
.content {
display: flex;
flex-direction: column;
gap: 12px;
position: relative;
/* top: 0%;
left: 40%; */
}
@media screen and (max-width: 480px) {
.container .logo {
width: 250px;
margin-left: -125px;
}
}
.container.diamond {
background: #F0F8FF;
}
.container.ruby {
background: #DC143C;
}
.container.emerald {
background: #00FF7F;
}
.container .logo,
.container .content {
will-change: transform;
transition: transform 0.3s linear;
}
.sticky {
position: sticky;
top: 50px;
left: 50px;
z-index: 1;
}
<div id="wrapper" class="scroll">
<div class="container diamond">
<div class="sticky">
<a href="https://www.google.com/">
<div class="logo"><img
src="https://images.unsplash.com/photo-1747134392051-5e112c58ce1e?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw0fHx8ZW58MHx8fHx8"
alt="Image 1" class="image"></div>
</a>
<a href="https://www.google.com/">
<div class="content">
<h3>MAKERSPLACE 1</h3>
<span>2022-2025</span>
<span>[View project]</span>
</div>
</a>
</div>
</div>
<div class="container ruby">
<div class="sticky">
<a href="https://www.youtube.com/">
<div class="logo"><img
src="https://images.unsplash.com/photo-1750101272034-7becde7454dd?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw4fHx8ZW58MHx8fHx8"
alt="Image 1" class="image"></div>
</a>
<a href="https://www.google.com/">
<div class="content">
<h3>MAKERSPLACE 2</h3>
<span>2022-2025</span>
<span>[View project]</span>
</div>
</a>
</div>
</div>
<div class="container emerald">
<div class="sticky">
<a href="https://www.linkedin.com/help/linkedin">
<div class="logo"><img
src="https://images.unsplash.com/photo-1749984340771-c3a967db0a28?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwyMHx8fGVufDB8fHx8fA%3D%3D"
alt="Image 1" class="image"></div>
</a>
<a href="https://www.google.com/">
<div class="content">
<h3>MAKERSPLACE 3</h3>
<span>2022-2025</span>
<span>[View project]</span>
</div>
</a>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
code - https://codepen.io/Rejuanul-Islam/pen/WbvLJwQ
Instead of using JavaScript to calculate where items should be moved using computation intensive scroll event, what would I do is another approach that involves CSS clip-path
.full
of 100dvh
height,.container
to the same height (100%
)position: fixed
panes (.fixed
) inside each containerclip-path
on the container* { margin: 0; box-sizing: border-box; }
.full {
height: 100dvh;
}
.container {
clip-path: polygon(0 0, 100% 0%, 100% 100%, 0% 100%);
height: 100%;
background-color: var(--bg);
color: #fff;
}
.fixed {
position: fixed;
inset: 0;
}
/* Other styles */
.fixed {
display: grid;
padding: min(2rem, 5vmin); /* Some space around to see the nice bg */
& > * {
grid-area: 1 / 1; /* overlap children on top of each other */
}
}
.logo {
position: relative;
.image {
position: absolute;
width: 100%; height: 100%;
object-fit: cover;
}
}
.content {
position: relative;
display: flex;
flex-direction: column;
gap: 0.8rem;
align-self: end;
padding: 2rem;
}
<div class="full">
<div class="container" style="--bg:#BDE0FF;">
<div class="fixed">
<div class="logo">
<img
src="https://images.unsplash.com/photo-1747134392051-5e112c58ce1e?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw0fHx8ZW58MHx8fHx8"
alt="Image 1"
class="image">
</div>
<div class="content">
<h3>1 LOREM</h3>
<span>2022-2025</span>
<span>[View project]</span>
</div>
</div>
</div>
<div class="container" style="--bg:#DC143C;">
<div class="fixed">
<div class="logo">
<img
src="https://images.unsplash.com/photo-1750101272034-7becde7454dd?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw4fHx8ZW58MHx8fHx8"
alt="Image 1"
class="image">
</div>
<div class="content">
<h3>2 IPSUM</h3>
<span>2022-2025</span>
<span>[View project]</span>
</div>
</div>
</div>
<div class="container" style="--bg:#00FF7F;">
<div class="fixed">
<div class="logo">
<img
src="https://images.unsplash.com/photo-1749984340771-c3a967db0a28?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwyMHx8fGVufDB8fHx8fA%3D%3D"
alt="Image 1"
class="image">
</div>
<div class="content">
<h3>3 DOLOR</h3>
<span>2023-2023</span>
<span>[View project]</span>
</div>
</div>
</div>
</div>
To keep the HTML markup as simple and readable, I've removed the links, etc. You can easily put them back what is necessary.
Here's an example with even less styles and HTML markup:
* { margin: 0; box-sizing: border-box; }
.full {
height: 100dvh;
}
.container {
clip-path: polygon(0 0, 100% 0%, 100% 100%, 0% 100%);
height: 100%;
background-color: var(--bg);
color: #fff;
}
.fixed {
position: fixed;
inset: 0;
}
/* Other styles */
.fixed {
display: grid;
padding: min(2rem, 5vmin); /* Some space around to see the nice bg */
&>* {
grid-area: 1 / 1; /* overlap children on top of each other */
}
}
.image {
width: 100%;
min-height: 100%;
height: 100%;
object-fit: cover;
}
.content {
align-self: end;
display: flex;
flex-direction: column;
gap: 0.8rem;
padding: 2rem;
}
<div class="full">
<div class="container" style="--bg:#BDE0FF;">
<div class="fixed">
<img
src="https://images.unsplash.com/photo-1747134392051-5e112c58ce1e?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw0fHx8ZW58MHx8fHx8"
alt="Image 1"
class="image">
<div class="content">
<h3>1 LOREM</h3>
<span>2022-2025</span>
<span>[View project]</span>
</div>
</div>
</div>
<div class="container" style="--bg:#DC143C;">
<div class="fixed">
<img
src="https://images.unsplash.com/photo-1750101272034-7becde7454dd?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw4fHx8ZW58MHx8fHx8"
alt="Image 1"
class="image">
<div class="content">
<h3>2 IPSUM</h3>
<span>2022-2025</span>
<span>[View project]</span>
</div>
</div>
</div>
<div class="container" style="--bg:#00FF7F;">
<div class="fixed">
<img
src="https://images.unsplash.com/photo-1749984340771-c3a967db0a28?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwyMHx8fGVufDB8fHx8fA%3D%3D"
alt="Image 1"
class="image">
<div class="content">
<h3>3 DOLOR</h3>
<span>2023-2024</span>
<span>[View project]</span>
</div>
</div>
</div>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With