积分球领取补位动画实现

案例动画: 支付宝-会员-积分球列表领取

动画演示

积分球与列表UI结构,以及动画轨迹/样式变化路径:

CSS实现

核心都是基于css的transition属性,来实现各种属性的动画效果:

1、球领取中,触发单球飞起的动画,手动计算单球与终点的位移transform,然后设置transform中属性的动画效果:

js 复制代码
 // 计算动画偏移量
ballEl.style.transition = 'transform 0.7s cubic-bezier(.52,.2,.13,.97), opacity 0.7s';
ballEl.style.transform = `translate3d(${dx}px, ${dy}px, 0) scale(0.2)`;
ballEl.style.opacity = '0';

2、列表中,球飞起同时,其占位元素的宽度、边距等属性变化,触发flex布局的列表中其他球的位移(transform):

关键动画属性:

  • 在flex布局中,每一个球的位置自适应: flex: 0 0 auto;
  • 下一个球的补位动画实现,主要由消失球的width等变化,和下一个球的位移(transform)变化组合实现
js 复制代码
.ball-item {
  width: 118px;
  /* 关键1、在flex布局中,每一个球的位置和尺寸自适应 */
  flex: 0 0 auto;
  /* 关键2、球补位动画的实现变化,主要由消失球的width等UI变化,和下一个球的transform变化组合实现 */
  transition: width 0.7s, margin-left 0.7s, opacity 0.7s, transform 0.7s;
  z-index: 1;

   /* 消失球的UI变化 */
  &.removing,
  &.batch-removing {
    // 平滑变小,飞出动画
    width: 0 !important;
    margin-left: 0 !important;
    opacity: 0;

    .ball-content {
      opacity: 0;
    }
  }
}

完整代码

js 复制代码
import { delay } from 'lodash';
import { useAsyncEffect } from 'ahooks';
import React, { useRef, useMemo } from 'react';
import cn from 'classnames';
import './index.less';
import { IPointCertInfoItemProps } from '../../../store/initialState';

type BallItemProps = {
  ball: IPointCertInfoItemProps;
  onClick?: () => void;
  targetId: string;
  children: React.ReactNode;
  id: string;
};

export const BallItem = ({ ball, targetId, id, children, onClick }: BallItemProps) => {
  const ballRef = useRef<HTMLDivElement>(null);

  const animationDuration = useMemo(() => {
    return `${3 + Math.random()}s`;
  }, []);

  const animationDelay = useMemo(() => {
    return `${Math.random()}s`;
  }, []);

  useAsyncEffect(async () => {
    const ballEl = ballRef.current;
    const targetEl = document.getElementById(targetId);
    // "领取"或"批量领取"状态响应动画
    if ((ball.state === 'removing' || ball.state === 'batch-removing') && ballEl && targetEl) {
      const ballRect = ballEl.getBoundingClientRect();
      const targetRect = targetEl.getBoundingClientRect();
      const dx = targetRect.left - ballRect.left;
      const dy = targetRect.top - ballRect.top;
      // 计算动画偏移量
      ballEl.style.transition = 'transform 0.7s cubic-bezier(.52,.2,.13,.97), opacity 0.7s';
      ballEl.style.transform = `translate3d(${dx}px, ${dy}px, 0) scale(0.2)`;
      ballEl.style.opacity = '0';
      // 清理动画,方便下一次复用
      await delay(700);
      if (ballEl) {
        ballEl.style.transition = '';
        ballEl.style.transform = '';
        ballEl.style.opacity = '';
      }
    }
  }, [ball.state]);

  return (
    <div
      id={id}
      className={cn('ball-item', {
        removing: ball.state === 'removing',
        'batch-removing': ball.state === 'batch-removing',
      })}
      onClick={onClick}
    >
      <div
        className={cn('ball-content', {
          float: !ball.state,
        })}
        style={{
          animationDuration,
          animationDelay,
        }}
        ref={ballRef}
      >
        {children}
      </div>
    </div>
  );
};
less 复制代码
.ball-item {
  width: 118px;
  flex: 0 0 auto;
  transition: width 0.7s, margin-left 0.7s, opacity 0.7s, transform 0.7s;
  z-index: 1;

  .ball-content {
    width: 118px;
    height: 118px;
    transition: opacity 0.7s, transform 0.7s;

    &.float {
      animation: upDown 0.8s ease-in-out infinite;
    }
  }

  &.removing,
  &.batch-removing {
    // 平滑变小,飞出动画
    width: 0 !important;
    margin-left: 0 !important;
    opacity: 0;

    .ball-content {
      opacity: 0;
    }
  }
}

@keyframes upDown {
  0% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-6px);
  }

  100% {
    transform: translateY(0);
  }
}
相关推荐
kyle~31 分钟前
Swagger ---基于 描述文件 生成 交互式API文档 的工具
前端·后端·规格说明书·说明文档
meilindehuzi_a8 小时前
从零理解并实现 RAG:用 LangChain.js 构建语义检索问答系统
开发语言·javascript·langchain
世界哪有真情9 小时前
拿人类意识卡 AI?等于用 bug 验收正式产品
前端·人工智能·后端
鱼樱前端9 小时前
别再自己拼凑了!这款 Vue 3.5+ 严肃产品级组件库,把 AI 对话、工作流和复杂数据全包了!
javascript·vue.js·github
Listen·Rain10 小时前
Vue3:setup详解
前端·javascript·vue.js
Patrick_Wilson11 小时前
修好 bug 只是开始:一次由监控需求反向重构日志结构的复盘
前端·监控·数据可视化
kyriewen11 小时前
面试官让我优化一个卡死的表格,我打开 Claude 五秒重写,他放下咖啡问我要不要来当组长
前端·javascript·面试
小粉粉hhh12 小时前
React(二)——dom、组件间通信、useEffect
前端·javascript·react.js
Csvn12 小时前
🤖 AI 生成前端代码的 5 个典型翻车现场及修复指南
前端
IT_陈寒13 小时前
SpringBoot自动装配坑了我一天,原来问题出在这
前端·人工智能·后端