argyleink / gui-challenges

Components from the YouTube show GUI Challenges: accessible, responsive, adaptive and cross browser components.

Home Page:https://youtube.com/playlist?list=PLNYkxOF6rcIAaV1wwI9540OC_3XoIzMjQ

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Stories bug

vasilich6107 opened this issue · comments

Hi. I tried to implement the stories code from you challenge.
The initial stories implementation has a bug when we mix up the scroll and tap movement through the stories.

Example - tap till the end of the stories, than scroll back with mouse scroll then try to tap forward - nothing works.

i've implemented the solution to improve the gesture mix in react.
If you are accepting PRs I will adapt it to vanilla js code

/* eslint-disable react/style-prop-object */
import { useCallback, useEffect, useRef, useState } from "react";
import "./stories.css";

const storiesBreakpoints = {};

function Stories() {
  const storiesRef = useRef(null);

  const storiesMedian = useCallback(
    () => storiesRef.current.offsetLeft + storiesRef.current.clientWidth / 2,
    [storiesRef]
  );

  const [currentStory, setCurrentStory] = useState(null);

  useEffect(() => {
    for (let i = 0; i < storiesRef.current.children.length; i++) {
      storiesBreakpoints[
        storiesRef.current.children[i].offsetLeft -
          storiesRef.current.offsetLeft
      ] = i;
    }
  }, []);

  useEffect(() => {
    setCurrentStory(storiesRef.current.firstElementChild.lastElementChild);
  }, []);

  const navigateStories = useCallback(
    (direction) => {
      if (currentStory === null) {
        return;
      }

      let lastItemInUserStory;
      let nextUserStory;

      if (direction === "next") {
        lastItemInUserStory = currentStory.parentNode.firstElementChild;
        nextUserStory =
          currentStory.previousElementSibling ||
          currentStory.parentElement.nextElementSibling?.lastElementChild;
      } else if (direction === "prev") {
        lastItemInUserStory = currentStory.parentNode.lastElementChild;

        nextUserStory =
          currentStory.nextElementSibling ||
          currentStory.parentElement.previousElementSibling?.firstElementChild;
      }

      if (lastItemInUserStory === currentStory && nextUserStory) {
        nextUserStory.scrollIntoView({
          behavior: "smooth",
        });
      } else if (nextUserStory) {
        (direction === "prev" ? nextUserStory : currentStory).classList.toggle(
          "seen"
        );
      }

      if (nextUserStory) {
        setCurrentStory(nextUserStory);
      }
    },
    [currentStory]
  );

  const storiesClick = useCallback(
    (e) => {
      if (e.target.nodeName !== "ARTICLE") {
        return;
      }

      navigateStories(e.clientX > storiesMedian() ? "next" : "prev");
    },
    [navigateStories, storiesMedian]
  );

  return (
    <>
      <div
        ref={storiesRef}
        className="stories"
        onClick={storiesClick}
        onScroll={(e) => {
          const currentUserIndex =
            storiesBreakpoints[e.target.scrollLeft] ?? null;

          let currentStory = null;

          if (currentUserIndex != null) {
            const userStories = Array.from(
              storiesRef.current.children[currentUserIndex]?.children
            );

            currentStory =
              userStories.findLast((e) => !e.classList.contains("seen")) ??
              null;
          }

          setCurrentStory(currentStory);
        }}
      >
        <section className="user">
          <article
            className="story story1"
            style={{ "--bg": "url(https://picsum.photos/480/840)" }}
          ></article>
          <article
            className="story story2"
            style={{ "--bg": "url(https://picsum.photos/480/841)" }}
          ></article>
        </section>
        <section className="user">
          <article
            className="story story3"
            style={{ "--bg": "url(https://picsum.photos/481/840)" }}
          ></article>
        </section>
        <section className="user">
          <article
            className="story story4"
            style={{ "--bg": "url(https://picsum.photos/481/841)" }}
          ></article>
        </section>
        <section className="user">
          <article
            className="story story5"
            style={{ "--bg": "url(https://picsum.photos/482/840)" }}
          ></article>
          <article
            className="story story6"
            style={{ "--bg": "url(https://picsum.photos/482/843)" }}
          ></article>
          <article
            className="story story7"
            style={{ "--bg": "url(https://picsum.photos/482/844)" }}
          ></article>
        </section>
      </div>
    </>
  );
}

export default Stories;

nice, yes, the demo does have that issue 👍🏻 I never implemented a scroll observer that tried to determine the current snapped set and then reconciled the index. I see your onScroll handler there that does that work, rad.

i'd love to see a demo of the react component, your implementation looks worth sharing!

and yes, if you want to submit a PR with a vanilla js update that fixes that inconsistency in snapped item and the tap snap index, that'd be grrrreat!