You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为类Instagram的GIF搜索结果实现单个GIF的加载Spinner

如何为类Instagram的GIF搜索结果实现单个GIF的加载Spinner

我来帮你实现每个GIF单独的加载Spinner效果,核心思路是给每一个GIF项单独跟踪它的加载状态,而不是用全局的加载状态。这样就能做到每个GIF加载完成后才替换掉加载动画,完全符合你想要的效果。

核心修改点

  1. 跟踪单个GIF的加载状态:用一个Set来记录已经加载完成的GIF ID,这样能高效判断每个GIF是否加载完毕
  2. 重置加载状态:每次搜索到新的GIF列表时,清空已加载的ID集合,确保新的GIF都处于未加载状态
  3. 单个GIF卡片的加载逻辑:在每个卡片里,加载完成前显示Spinner/Skeleton,加载完成后显示GIF
  4. 利用图片的onLoad事件:每个GIF图片加载完成后,触发回调更新加载状态

修改后的完整代码

import { Box, Group, Image, Skeleton, Stack, TextInput, Spinner } from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import axios from 'axios'; // 记得导入axios

const GifSelectorModal = () => {
  const [searchQuery, setSearchQuery] = useState('');
  const [debouncedQuery] = useDebouncedValue(searchQuery, 400);

  const [gifs, setGifs] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  // 用Set存储已加载完成的GIF ID,高效判断状态
  const [loadedGifIds, setLoadedGifIds] = useState(new Set<string>());

  const searchGifs = async (query: string) => {
    const response = await axios.get('https://tenor.googleapis.com/v2/search', {
      params: {
        q: query,
        key: 'my-key', // 替换为你的实际API Key
        limit: 30,
        client_key: 'tribecrafter',
      },
    });

    return response.data;
  };

  useEffect(() => {
    // 当搜索关键词长度≥2时发起请求
    if (debouncedQuery.length >= 2) {
      setIsLoading(true);
      const fetchGifs = async () => {
        try {
          const data = await searchGifs(debouncedQuery);
          setGifs(data.results);
        } catch (err) {
          console.error('搜索GIF失败:', err);
          setGifs([]);
        } finally {
          setIsLoading(false);
        }
      };
      fetchGifs();
    } else {
      // 关键词过短时清空结果并结束加载状态
      setGifs([]);
      setIsLoading(false);
    }
  }, [debouncedQuery]);

  // 每次GIF列表更新时,重置已加载的ID集合
  useEffect(() => {
    setLoadedGifIds(new Set());
  }, [gifs]);

  // 标记某个GIF为已加载
  const markGifAsLoaded = (gifId: string) => {
    setLoadedGifIds(prev => new Set(prev).add(gifId));
  };

  return (
    <Stack gap="md" p="md">
      <TextInput
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="Search GIFs"
        autoFocus
      />

      {isLoading ? (
        // 全局加载时显示30个骨架屏,模拟网格布局
        <Group wrap="wrap" gap="md">
          {Array.from({ length: 30 }).map((_, idx) => (
            <Skeleton key={idx} width={120} height={120} borderRadius="md" />
          ))}
        </Group>
      ) : (
        <Group wrap="wrap" gap="md">
          {gifs.map((gif) => (
            <GifCard 
              key={gif.id} 
              gif={gif} 
              isLoaded={loadedGifIds.has(gif.id)}
              onLoad={() => markGifAsLoaded(gif.id)}
            />
          ))}
        </Group>
      )}
    </Stack>
  );
};

const GifCard = ({ gif, isLoaded, onLoad }) => {
  return (
    <Box 
      width={120} 
      height={120} 
      position="relative" 
      borderRadius="md" 
      overflow="hidden"
      cursor="pointer"
      onClick={() => console.log('selected gif:', gif)}
    >
      {/* 加载中的状态:显示Spinner和浅灰色背景 */}
      {!isLoaded && (
        <Box 
          position="absolute" 
          inset={0} 
          bg="#f5f5f5"
          display="flex" 
          alignItems="center" 
          justifyContent="center"
          zIndex={1}
        >
          <Spinner size="sm" color="gray" />
        </Box>
      )}

      {/* GIF图片:加载完成前透明度为0,实现平滑过渡 */}
      <Image
        src={gif.media_formats.gif.url}
        alt="GIF"
        fit="cover"
        width="100%"
        height="100%"
        onLoad={onLoad}
        style={{ opacity: isLoaded ? 1 : 0, transition: 'opacity 0.2s ease' }}
      />
    </Box>
  );
};

export default GifSelectorModal;

效果说明

  • 当你输入搜索词时,先显示30个骨架屏作为全局加载状态
  • 当GIF列表加载完成后,每个卡片会显示Spinner,直到对应的GIF加载完成
  • GIF加载完成后会平滑过渡显示,不会出现突然闪现的情况
  • 完全和你提供的Instagram截图效果一致,每个GIF独立控制加载状态

备注:内容来源于stack exchange,提问作者Sujal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:19:52