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

如何用Framer Motion stagger效果实现Flex布局下的分享按钮动画?

实现方案

核心思路是直接将原生ul替换为motion.ul作为动画父容器,无需额外嵌套包装元素,完全保留ul > li的原生DOM结构,同时借助Framer Motion的父容器变体特性实现子元素错峰动画。

具体修改点如下:

  • 替换外层ul为motion.ul,定义父级变体,在可见状态中配置staggerChildren参数实现错峰效果
  • 将分享图标后所有需要展开的li统一改为motion.li,共享子级动画变体
  • 给分享图标增加悬停事件绑定,同时保留点击切换逻辑,满足点击、悬停两种交互需求
  • 优化动画变体属性,隐藏状态下配置宽度、边距归零,避免隐藏元素占用页面空间

修改后的完整代码:

import React from 'react';
import { motion } from 'framer-motion';
import { ShareIcon } from '@heroicons/react/solid';

import {
  Facebook,
  HackerNews,
  Reddit,
  Twitter,
  LinkedIn,
  Pinterest,
  Telegram,
  Whatsapp,
  Pocket
} from '../icons/index';

const isProduction = process.env.NODE_ENV === 'production';

export const SocialShare = ({ title, slug }) => {
  const [share, openShare] = React.useState(false);
  const [host, setHost] = React.useState('');

  React.useEffect(() => {
    setHost(window.location.host);
  }, []);

  const url = `${isProduction ? 'https://' : 'http://'}${host}/${slug}`;
  const text = title + url;
  const via = 'deadcoder0904';
  const sharer = {
    facebook: `https://www.facebook.com/sharer/sharer.php?u=${url}`,
    twitter: `https://twitter.com/intent/tweet?url=${url}&text=${text}&via=${via}`,
    reddit: `https://www.reddit.com/submit?title=${title}&url=${url}`,
    hackernews: `https://news.ycombinator.com/submitlink?u=${url}&t=${title}`,
    linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
    pinterest: `https://pinterest.com/pin/create/button/?url=${url}&description=${title}`,
    telegram: `https://telegram.me/share/url?url=${url}&text=${text}`,
    whatsapp: `https://wa.me/?text=${title}%0D%0A${url}%0D%0A%0D%0A${text}`,
    pocket: `https://getpocket.com/edit.php?url=${url}`
  };

  // 父容器变体,配置错峰动画
  const parentVariants = {
    hidden: {
      transition: {
        staggerChildren: 0.08,
        staggerDirection: -1 // 收起时从最后一个元素开始动画
      }
    },
    visible: {
      transition: {
        staggerChildren: 0.1 // 每个子元素延迟0.1s触发动画
      }
    }
  };

  // 子元素公共动画变体
  const itemVariants = {
    hidden: {
      opacity: 0,
      x: -16,
      width: 0,
      marginRight: 0,
      pointerEvents: 'none'
    },
    visible: {
      opacity: 1,
      x: 0,
      width: 'auto',
      marginRight: '1rem', // 对应原space-x-4的间距
      pointerEvents: 'auto',
      transition: {
        type: 'tween',
        ease: 'easeInOut'
      }
    }
  };

  return (
    <motion.ul 
      className="flex items-center mt-8 relative"
      variants={parentVariants}
      initial="hidden"
      animate={share ? 'visible' : 'hidden'}
    >
      <li className="mr-4">
        <a href={sharer.facebook} title="Share on Facebook">
          <Facebook />
        </a>
      </li>
      <li className="mr-4">
        <a href={sharer.twitter} title="Share on Twitter">
          <Twitter />
        </a>
      </li>
      <li className="mr-4">
        <a href={sharer.reddit} title="Share on Reddit">
          <Reddit />
        </a>
      </li>
      <li className="mr-4">
        <a href={sharer.hackernews} title="Share on Hacker News">
          <HackerNews />
        </a>
      </li>
      <motion.li 
        className="cursor-pointer mr-4"
        onMouseEnter={() => openShare(true)}
        onMouseLeave={() => openShare(false)}
      >
        <ShareIcon
          className="w-6 h-6 text-gray-300"
          onClick={() => openShare(!share)}
        />
      </motion.li>

      <motion.li variants={itemVariants}>
        <a href={sharer.linkedin} title="Share on LinkedIn">
          <LinkedIn />
        </a>
      </motion.li>
      <motion.li variants={itemVariants}>
        <a href={sharer.pinterest} title="Share on Pinterest">
          <Pinterest />
        </a>
      </motion.li>
      <motion.li variants={itemVariants}>
        <a href={sharer.telegram} title="Share on Telegram">
          <Telegram />
        </a>
      </motion.li>
      <motion.li variants={itemVariants}>
        <a href={sharer.whatsapp} title="Share on Whatsapp">
          <Whatsapp />
        </a>
      </motion.li>
      <motion.li variants={itemVariants} className="!mr-0">
        <a href={sharer.pocket} title="Share on Pocket">
          <Pocket />
        </a>
      </motion.li>
    </motion.ul>
  );
};

内容的提问来源于stack exchange,提问作者deadcoder0904

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 03:42:03