페이지(문서) 제목 및 파비콘에 애니메이션 효과를 주는 반응 후크

핵심요약 - 데모, npm 패키지 및 코드



다음은 후크가 작동하는 모습을 보여주는 gif입니다.



The interactive demo is here.

The npm package is here.

The GitHub Repo is here.

즐기다!

react-use-Please-stay 뒤에 배경



분명 이전에 본 적이 있는 것이지만 최근에 Dutch version of the Mikkeller Web Shop 을(를) 방문하는 동안 움직이는 제목과 파비콘을 변경하는 것을 우연히 발견했습니다. 파비콘이 슬픈 표정의 Henry( Henry and Sally are the famous Mikkeller mascots )로 바뀌고 탭 제목이 다음 사이에서 바뀝니다.

헨리는 슬프다.

그리고

당신의 맥주를 기억

이상한 문법이 의도적으로 설계된 것인지 확실하지 않지만 모든 것이 나를 깨뜨렸습니다. 😂 소스를 다운로드하고 document.title를 검색하여 약간의 스누핑을 한 후, 내가 찾을 수 있었던 것은 가시성 변경 이벤트 리스너가 포함된 pleasestay.js라는 파일이었지만 모두 모듈화되었습니다. 그리고 11000줄 이상! 그것은 확실히 사용 가능한 형태가 아니었고 Google 검색 후에 기능의 this GitHub gist with a JQuery implementation만 찾을 수 있었습니다.

패키지 생성



인정해야 합니다. Mikkeler's Shop의 작은 애니메이션이 저를 사이트로 다시 끌어당겼습니다. 최소한 많은 웹사이트에서 볼 수 없는 멋진 터치입니다. 나는 그것이 훌륭한 React 후크를 만들 것이라고 생각했습니다. 특히 여러 옵션과 제목으로 구성할 수 있다면 더욱 그렇습니다. 그래서 나는 그것을 하기 위해 react-use-please-stay 패키지를 만들었습니다!

저는 자주 그렇듯이 제 블로그를 훅의 테스트베드로 사용하고 있습니다. 지금 브라우저의 다른 탭으로 이동하면 내 블로그의 파비콘과 제목이 움직이는 것을 볼 수 있습니다.

이 게시물을 작성하는 시점의 소스 코드



다시 말하지만, 패키지는 completely open source 이며 가장 최신 코드를 찾을 수 있지만 후크가 어떻게 작동하는지 바로 알고 싶다면 다음을 참조하십시오.

import { useEffect, useRef, useState } from 'react';
import { getFavicon } from '../../helpers/getFavicon';
import { AnimationType } from '../../enums/AnimationType';
import { UsePleaseStayOptions } from '../../types/UsePleaseStayOptions';
import { useInterval } from '../useInterval';

export const usePleaseStay = ({
  titles,
  animationType = AnimationType.LOOP,
  interval = 1000,
  faviconURIs = [],
  alwaysRunAnimations = false,
}: UsePleaseStayOptions): void => {
  if (animationType === AnimationType.CASCADE && titles.length > 1) {
    console.warn(
      `You are using animation type '${animationType}' but passed more than one title in the titles array. Only the first title will be used.`,
    );
  }

  // State vars
  const [shouldAnimate, setShouldAnimate] = useState<boolean>(false);

  // On cascade mode, we substring at the first character (0, 1).
  // Otherwise start at the first element in the titles array.
  const [titleIndex, setTitleIndex] = useState<number>(0);
  const [faviconIndex, setFaviconIndex] = useState<number>(0);
  const [isAppendMode, setIsAppendMode] = useState<boolean>(true);
  const [faviconURIsState, setFaviconURIsState] = useState<Array<string>>([]);

  // Ref vars
  const originalDocumentTitle = useRef<string>();
  const originalFaviconHref = useRef<string>();
  const faviconRef = useRef<HTMLLinkElement>();

  // Handler for visibility change - only needed when alwaysRunAnimations is false
  const handleVisibilityChange = () => {
    document.visibilityState === 'visible'
      ? restoreDefaults()
      : setShouldAnimate(true);
  };

  // The logic to modify the document title in cascade mode.
  const runCascadeLogic = () => {
    document.title = titles[0].substring(0, titleIndex);
    setTitleIndex(isAppendMode ? titleIndex + 1 : titleIndex - 1);
    if (titleIndex === titles[0].length - 1 && isAppendMode) {
      setIsAppendMode(false);
    }
    if (titleIndex - 1 === 0 && !isAppendMode) {
      setIsAppendMode(true);
    }
  };

  // The logic to modify the document title in loop mode.
  const runLoopLogic = () => {
    document.title = titles[titleIndex];
    setTitleIndex(titleIndex === titles.length - 1 ? 0 : titleIndex + 1);
  };

  // The logic to modify the document title.
  const modifyDocumentTitle = () => {
    switch (animationType) {
      // Cascade letters in the title
      case AnimationType.CASCADE:
        runCascadeLogic();
        return;
      // Loop over titles
      case AnimationType.LOOP:
      default:
        runLoopLogic();
        return;
    }
  };

  // The logic to modify the favicon.
  const modifyFavicon = () => {
    if (faviconRef && faviconRef.current) {
      faviconRef.current.href = faviconURIsState[faviconIndex];
      setFaviconIndex(
        faviconIndex === faviconURIsState.length - 1 ? 0 : faviconIndex + 1,
      );
    }
  };

  // The logic to restore default title and favicon.
  const restoreDefaults = () => {
    setShouldAnimate(false);
    setTimeout(() => {
      if (
        faviconRef &&
        faviconRef.current &&
        originalDocumentTitle.current &&
        originalFaviconHref.current
      ) {
        document.title = originalDocumentTitle.current;
        faviconRef.current.href = originalFaviconHref.current;
      }
    }, interval);
  };

  // On mount of this hook, save current defaults of title and favicon. also add the event listener. on un mount, remove it
  useEffect(() => {
    // make sure to store originals via useRef
    const favicon = getFavicon();
    if (favicon === undefined) {
      console.warn('We could not find a favicon in your application.');
      return;
    }
    // save originals - these are not to be manipulated
    originalDocumentTitle.current = document.title;
    originalFaviconHref.current = favicon.href;
    faviconRef.current = favicon;

    // TODO: small preload logic for external favicon links? (if not a local URI)
    // Build faviconLinksState
    // Append current favicon href, since this is needed for an expected favicon toggle or animation pattern
    setFaviconURIsState([...faviconURIs, favicon.href]);

    // also add visibilitychange event listener
    document.addEventListener('visibilitychange', handleVisibilityChange);
    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, []);

  // State change effects
  useEffect(() => {
    // Change in alwaysRunAnimations change the shouldAnimate value
    setShouldAnimate(alwaysRunAnimations);

    // Update title index
    setTitleIndex(animationType === AnimationType.CASCADE ? 1 : 0);
  }, [animationType, alwaysRunAnimations]);

  // Change title and favicon at specified interval
  useInterval(
    () => {
      modifyDocumentTitle();
      // this is 1 because we append the existing favicon on mount - see above
      faviconURIsState.length > 1 && modifyFavicon();
    },
    shouldAnimate ? interval : null,
  );
};


감사!



이것은 모든 꼬임을 해결하는 데 몇 시간 이상 걸리는 재미있는 작은 고리였습니다. 지금까지 내 사이트에서 안정적이었고 풀 리퀘스트, 비평 및 추가 기능에 열려 있습니다!

건배! 🍺

-크리스

좋은 웹페이지 즐겨찾기