Storie

Questo pattern mostra come creare un componente Storie per il Web che sia adattabile, supporti la navigazione da tastiera e funzioni su più browser.

Articolo completo · Video su YouTube · Fonte su GitHub

<div class="stories"> 
 
<section class="user">
   
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
   
<article class="story" style="--bg: url(https://picsum.photos/480/841);"></article>
 
</section>
 
<section class="user">
   
<article class="story" style="--bg: url(https://picsum.photos/481/840);"></article>
 
</section>
 
<section class="user">
   
<article class="story" style="--bg: url(https://picsum.photos/481/841);"></article>
 
</section>
 
<section class="user">
   
<article class="story" style="--bg: url(https://picsum.photos/482/840);"></article>
   
<article class="story" style="--bg: url(https://picsum.photos/482/843);"></article>
   
<article class="story" style="--bg: url(https://picsum.photos/482/844);"></article>
 
</section>
</div>

       
.stories {
 
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 {
 
scroll-snap-align: start;
 
scroll-snap-stop: always;
 
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 .3s cubic-bezier(0.4, 0.0, 1, 1)
}

.story.seen {
 
opacity: 0;
 
pointer-events: none;
}
       

       
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')
})