javascript 带分页的图像查看器非常慢

mnowg1ta  于 2023-05-05  发布在  Java
关注(0)|答案(1)|浏览(150)

我有一个React组件,可以显示图像网格。网格:

  • 必须提供分页。(显示150个图像的批次,允许转到下一个和上一个150个图像)。
  • 必须允许选择图像(单击进行单选,单击时按住ctrl键进行多个单选,单击时按住shift键进行多个相邻选择。
  • 选择必须沿着所有文件,而不仅仅是显示的文件。

我所面临的问题是图像选择非常不充分。如果单击图像,有时可能需要3-4秒才能选中。因此,要么handleClickOnGridItem函数是超级无效的,要么发生了一些奇怪的事情。

**如果问题的原因确实是功能效率低下,如果您能提供一个满足要求且不会导致我遇到的问题的解决方案,我将非常感激。

如果你去the CodeSandbox MRE,你会明白我在说什么。尝试上传500张或更多图片,您将看到
下面是我的组件:

import {
  useState,
  useMemo,
  ChangeEvent,
  CSSProperties,
  MouseEvent,
} from 'react';

function App() {
  const [files, setFiles] = useState<File[]>([]);
  const [startIndex, setStartIndex] = useState(0);
  const [selectedFilenames, setSelectedFilenames] = useState<string[]>([]);

  const imagesPerPage = 150;

  const endIndex = useMemo(() => {
    return Math.min(startIndex + imagesPerPage, files.length);
  }, [files, startIndex]);

  const displayedFiles = useMemo(() => {
    return files.slice(startIndex, endIndex);
  }, [files, startIndex]);

  const handlePrevClick = () => {
    if (startIndex - imagesPerPage >= 0) {
      setStartIndex(startIndex - imagesPerPage);
    }
  };

  const handleNextClick = () => {
    if (endIndex < files.length) {
      setStartIndex(startIndex + imagesPerPage);
    }
  };

  const handleFileInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    const newFiles = Array.from(e.target.files!);
    setFiles(newFiles);
  };

  const handleClickOnGridItem = (
    i: number,
    e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>
  ) => {
    if (e.ctrlKey || e.metaKey) {
      setSelectedFilenames((prevSelectedFilenames) => {
        if (prevSelectedFilenames.includes(displayedFiles[i].name)) {
          return prevSelectedFilenames.filter(
            (name) => name !== displayedFiles[i].name
          );
        } else {
          return [...prevSelectedFilenames, displayedFiles[i].name];
        }
      });
    } else if (e.shiftKey) {
      if (selectedFilenames.length === 0) {
        const range = [0, i].sort((a, b) => a - b);
        const newSelectedFilenames = displayedFiles
          .slice(range[0], range[1] + 1)
          .map((file) => file.name);
        setSelectedFilenames(newSelectedFilenames);
      } else if (selectedFilenames.length > 0) {
        const firstIndex = displayedFiles.findIndex(
          (file) => file.name === selectedFilenames[0]
        );
        const range = [firstIndex, i].sort((a, b) => a - b);
        const newSelectedFilenames = displayedFiles
          .slice(range[0], range[1] + 1)
          .map((file) => file.name);
        setSelectedFilenames(newSelectedFilenames);
      }
    } else {
      setSelectedFilenames([displayedFiles[i].name]);
    }
  };

  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <input
        type='file'
        className='mt-3'
        id='singleFileInput'
        title='Upload an image'
        accept='image/*'
        multiple
        onChange={handleFileInputChange}
      />
      <div
        style={{
          display: 'flex',
          flexDirection: 'row',
          justifyContent: 'evenly',
          alignItems: 'center',
          width: '25vw',
          marginLeft: 'auto',
        }}
      >
        <button onClick={handlePrevClick}> Prev </button>
        <button onClick={handleNextClick}> Next </button>
      </div>
      <div
        style={{
          display: 'flex',
          flexDirection: 'row',
          justifyContent: 'flex-start',
          alignItems: 'center',
          flexWrap: 'wrap',
        }}
      >
        {displayedFiles.map((file, i) => {
          const isSelected = selectedFilenames.includes(file.name);
          const maxHeight = 80;
          const margin = 2;
          const imgStyle: CSSProperties = isSelected
            ? {
                marginLeft: margin,
                marginTop: margin,
                maxHeight,
                opacity: 1,
              }
            : {
                marginLeft: margin,
                marginTop: margin,
                maxHeight,
                opacity: 0.5,
              };

          return (
            <img
              style={imgStyle}
              src={URL.createObjectURL(file)}
              onClick={(e) => handleClickOnGridItem(i, e)}
            />
          );
        })}
      </div>
    </div>
  );
}

export default App;
lymnna71

lymnna711#

问题不在于handleClickOnGridItem函数的低效率。问题是,我在组件的return范围内的每个渲染器上生成每个图像URL。现在,当用户上传文件时,我会生成图像URL列表,它现在工作得非常快,延迟为0。
下面是超级高效的代码😅:

import {
  useState,
  useMemo,
  ChangeEvent,
  CSSProperties,
  MouseEvent,
} from 'react';

function App() {
  const [urls, setUrls] = useState<string[]>([]);
  const [startIndex, setStartIndex] = useState(0);
  const [selectedUrls, setSelectedUrls] = useState<string[]>([]);

  const imagesPerPage = 150;

  const endIndex = useMemo(() => {
    return Math.min(startIndex + imagesPerPage, urls.length);
  }, [urls, startIndex]);

  const displayedUrls = useMemo(() => {
    return urls.slice(startIndex, endIndex);
  }, [urls, startIndex]);

  const handlePrevClick = () => {
    if (startIndex - imagesPerPage >= 0) {
      setStartIndex(startIndex - imagesPerPage);
    }
  };

  const handleNextClick = () => {
    if (endIndex < urls.length) {
      setStartIndex(startIndex + imagesPerPage);
    }
  };

  const handleFileInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    const newFiles = Array.from(e.target.files!);
    setUrls(newFiles.map((file) => URL.createObjectURL(file)));
  };

  const handleClickOnGridItem = (
    i: number,
    e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>
  ) => {
    if (e.ctrlKey || e.metaKey) {
      setSelectedUrls((prevUrls) => {
        if (prevUrls.includes(displayedUrls[i])) {
          return prevUrls.filter((url) => url !== displayedUrls[i]);
        } else {
          return [...prevUrls, displayedUrls[i]];
        }
      });
    } else if (e.shiftKey) {
      if (selectedUrls.length === 0) {
        const range = [0, i].sort((a, b) => a - b);
        const newUrls = displayedUrls
          .slice(range[0], range[1] + 1)
          .map((url) => url);
        setSelectedUrls(newUrls);
      } else if (selectedUrls.length > 0) {
        const firstIndex = displayedUrls.findIndex(
          (url) => url === selectedUrls[0]
        );
        const range = [firstIndex, i].sort((a, b) => a - b);
        const newUrls = displayedUrls
          .slice(range[0], range[1] + 1)
          .map((url) => url);
        setSelectedUrls(newUrls);
      }
    } else {
      setSelectedUrls([displayedUrls[i]]);
    }
  };

  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <input
        type='file'
        className='mt-3'
        id='singleFileInput'
        title='Upload an image'
        accept='image/*'
        multiple
        onChange={handleFileInputChange}
      />
      <div
        style={{
          display: 'flex',
          flexDirection: 'row',
          justifyContent: 'evenly',
          alignItems: 'center',
          width: '25vw',
          marginLeft: 'auto',
        }}
      >
        <button onClick={handlePrevClick}> Prev </button>
        <button onClick={handleNextClick}> Next </button>
      </div>
      <div
        style={{
          display: 'flex',
          flexDirection: 'row',
          justifyContent: 'flex-start',
          alignItems: 'center',
          flexWrap: 'wrap',
        }}
      >
        {displayedUrls.map((url, i) => {
          const isSelected = selectedUrls.includes(url);
          const maxHeight = 80;
          const margin = 2;
          const imgStyle: CSSProperties = isSelected
            ? {
                marginLeft: margin,
                marginTop: margin,
                maxHeight,
                opacity: 1,
              }
            : {
                marginLeft: margin,
                marginTop: margin,
                maxHeight,
                opacity: 0.25,
              };

          return (
            <img
              style={imgStyle}
              src={url}
              onClick={(e) => handleClickOnGridItem(i, e)}
            />
          );
        })}
      </div>
    </div>
  );
}

export default App;

相关问题