<!--
From: https://web.dev/building-a-stories-component/
Author: https://twitter.com/argyleink
-->
<div class="stories">
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=1);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=2);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=3);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=12);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=22);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=32);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=42);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=6412);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=412);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=422);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=432);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841?random=442);"></article>
</section>
</div>
@import url("https://fonts.googleapis.com/css2?family=Exo:wght@600&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100vw;
min-height: 100vh;
font-family: "Exo", Arial, sans-serif;
display: grid;
align-items: center;
justify-items: center;
place-items: center;
background: hsl(200, 15%, 93%);
}
.stories {
width: 100vw;
height: 100vh;
box-shadow: 0 5px 2.5px hsla(200, 95%, 3%, 0.037),
0 12px 6.5px hsla(200, 95%, 3%, 0.053),
0 22.5px 13px hsla(200, 95%, 3%, 0.065),
0 40.2px 24px hsla(200, 95%, 3%, 0.077),
0 75.2px 44px hsla(200, 95%, 3%, 0.093),
0 180px 80px hsla(200, 95%, 3%, 0.13);
display: grid;
grid: 1fr / auto-flow 100%;
grid-gap: 1ch;
gap: 1ch;
overflow-x: auto;
scroll-snap-type: x mandatory;
overscroll-behavior: contain;
touch-action: pan-x;
}
.user {
/* outer */
scroll-snap-align: start;
scroll-snap-stop: always;
/* inner */
display: grid;
grid: [story] 1fr / [story] 1fr;
}
.story {
grid-area: story;
background-size: cover;
background-image: var(--bg),
linear-gradient(to top, rgb(249, 249, 249), rgb(226, 226, 226));
user-select: none;
touch-action: manipulation;
transition: opacity 0.3s cubic-bezier(0.4, 0, 1, 1);
}
.story.seen {
opacity: 0;
pointer-events: none;
}
@media (hover: hover) {
.stories {
border-radius: 3ch;
}
}
@media (hover: hover) and (min-width: 480px) {
.stories {
max-width: 480px;
max-height: 848px;
}
}
@media (hover: hover) and (max-height: 880px) and (min-width: 720px) {
.stories {
max-width: 320px;
max-height: 568px;
}
}
const stories = document.querySelector(".stories");
const median = stories.offsetLeft + stories.clientWidth / 2;
const state = {
current_story: stories.firstElementChild.lastElementChild
};
const navigateStories = (direction) => {
const story = state.current_story;
const lastItemInUserStory = story.parentNode.firstElementChild;
const firstItemInUserStory = story.parentNode.lastElementChild;
const hasNextUserStory = story.parentElement.nextElementSibling;
const hasPrevUserStory = story.parentElement.previousElementSibling;
if (direction === "next") {
if (lastItemInUserStory === story && !hasNextUserStory) return;
else if (lastItemInUserStory === story && hasNextUserStory) {
state.current_story =
story.parentElement.nextElementSibling.lastElementChild;
story.parentElement.nextElementSibling.scrollIntoView({
behavior: "smooth"
});
} else {
story.classList.add("seen");
state.current_story = story.previousElementSibling;
}
} else if (direction === "prev") {
if (firstItemInUserStory === story && !hasPrevUserStory) return;
else if (firstItemInUserStory === story && hasPrevUserStory) {
state.current_story =
story.parentElement.previousElementSibling.firstElementChild;
story.parentElement.previousElementSibling.scrollIntoView({
behavior: "smooth"
});
} else {
story.nextElementSibling.classList.remove("seen");
state.current_story = story.nextElementSibling;
}
}
};
stories.addEventListener("click", (e) => {
if (e.target.nodeName !== "ARTICLE") return;
navigateStories(e.clientX > median ? "next" : "prev");
}); // left & right are free with snap points 👍
document.addEventListener("keydown", ({ key }) => {
if (key !== "ArrowDown" || key !== "ArrowUp")
navigateStories(key === "ArrowDown" ? "next" : "prev");
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.