next.js/examples/cms-agilitycms/lib/components/image.tsx
image.tsx225 lines4.5 KB
import React, { useCallback, useState } from "react";
import { useInView } from "react-intersection-observer";

type State = {
  lazyLoad: boolean;
  isSsr: boolean;
  isIntersectionObserverAvailable: boolean;
  inView?: boolean;
  loaded: boolean;
};

const imageAddStrategy = ({
  lazyLoad,
  isSsr,
  isIntersectionObserverAvailable,
  inView,
  loaded,
}: State) => {
  if (!lazyLoad) {
    return true;
  }

  if (isSsr) {
    return false;
  }

  if (isIntersectionObserverAvailable) {
    return inView || loaded;
  }

  return true;
};

const imageShowStrategy = ({
  lazyLoad,
  isSsr,
  isIntersectionObserverAvailable,
  loaded,
}: State) => {
  if (!lazyLoad) {
    return true;
  }

  if (isSsr) {
    return false;
  }

  if (isIntersectionObserverAvailable) {
    return loaded;
  }

  return true;
};

type ImageData = {
  aspectRatio: number;
  base64?: string;
  height?: number;
  width: number;
  sizes?: string;
  src?: string;
  srcSet?: string;
  webpSrcSet?: string;
  bgColor?: string;
  alt?: string;
  title?: string;
};

type ImageProps = {
  data: ImageData;
  className?: string;
  pictureClassName?: string;
  fadeInDuration?: number;
  intersectionThreshold?: number;
  intersectionMargin?: string;
  lazyLoad?: boolean;
  style?: React.CSSProperties;
  pictureStyle?: React.CSSProperties;
  explicitWidth?: boolean;
};

const Image = function ({
  className,
  fadeInDuration,
  intersectionThreshold,
  intersectionMargin,
  pictureClassName,
  lazyLoad = true,
  style,
  pictureStyle,
  explicitWidth,
  data,
}: ImageProps) {
  const [loaded, setLoaded] = useState(false);

  const handleLoad = useCallback(() => {
    setLoaded(true);
  }, []);

  const [ref, inView] = useInView({
    threshold: intersectionThreshold || 0,
    rootMargin: intersectionMargin || "0px 0px 0px 0px",
    triggerOnce: true,
  });

  const isSsr = typeof window === "undefined";

  const isIntersectionObserverAvailable = isSsr
    ? false
    : !!window.IntersectionObserver;

  const absolutePositioning: React.CSSProperties = {
    position: "absolute",
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
  };

  const addImage = imageAddStrategy({
    lazyLoad,
    isSsr,
    isIntersectionObserverAvailable,
    inView,
    loaded,
  });
  const showImage = imageShowStrategy({
    lazyLoad,
    isSsr,
    isIntersectionObserverAvailable,
    inView,
    loaded,
  });

  const webpSource = data.webpSrcSet && (
    <source srcSet={data.webpSrcSet} sizes={data.sizes} type="image/webp" />
  );

  const regularSource = data.srcSet && (
    <source srcSet={data.srcSet} sizes={data.sizes} />
  );

  const placeholder = (
    <div
      style={{
        backgroundImage: data.base64 ? `url(${data.base64})` : null,
        backgroundColor: data.bgColor,
        backgroundSize: "cover",
        opacity: showImage ? 0 : 1,
        transition:
          !fadeInDuration || fadeInDuration > 0
            ? `opacity ${fadeInDuration || 500}ms ${fadeInDuration || 500}ms`
            : null,
        ...absolutePositioning,
      }}
    />
  );

  const { width, aspectRatio } = data;
  const height = data.height || width / aspectRatio;

  const sizer = (
    <svg
      className={pictureClassName}
      style={{
        width: explicitWidth ? `${width}px` : "100%",
        height: "auto",
        display: "block",
        ...pictureStyle,
      }}
      height={height}
      width={width}
    />
  );

  return (
    <div
      ref={ref}
      className={className}
      style={{
        display: "inline-block",
        overflow: "hidden",
        ...style,
        position: "relative",
      }}
    >
      {sizer}
      {placeholder}
      {addImage && (
        <picture
          style={{
            ...absolutePositioning,
            opacity: showImage ? 1 : 0,
            transition:
              !fadeInDuration || fadeInDuration > 0
                ? `opacity ${fadeInDuration || 500}ms`
                : null,
          }}
        >
          {webpSource}
          {regularSource}
          {data.src && (
            <img
              src={data.src}
              alt={data.alt}
              title={data.title}
              onLoad={handleLoad}
              style={{ width: "100%" }}
            />
          )}
        </picture>
      )}
      <noscript>
        <picture className={pictureClassName} style={{ ...pictureStyle }}>
          {webpSource}
          {regularSource}
          {data.src && <img src={data.src} alt={data.alt} title={data.title} />}
        </picture>
      </noscript>
    </div>
  );
};

export default Image;
Quest for Codev2.0.0
/
SIGN IN