积分球领取补位动画实现

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

动画演示

积分球与列表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);
  }
}
相关推荐
vipbic29 分钟前
别再把“做个H5”挂嘴边了:这个词,官方压根就没有定义过
前端
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_39:(Flexbox 弹性盒子核心机制)
前端·css·ui·html·tensorflow
小陈同学呦2 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
开发者每周简报2 小时前
网海三部曲·无名宗师传
javascript·人工智能
喵个咪3 小时前
GoWind Toolkit 前端代码生成|Vue3(ElementPlus/Vben)、React(AntDesign)全自动一键生成教程
前端·vue.js·react.js
摆烂大大王4 小时前
玩转 OpenClaw:用 TaskFlow + Heartbeat 打造自动化工作流
前端·人工智能·自动化
zhangxingchao4 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
梦想的颜色4 小时前
TypeScript 完全指南(上):从零开始掌握类型系统
前端·typescript
之歆4 小时前
Day01_ES6+ 专业指南:从基础到实战的现代JavaScript开发(下)
前端·javascript·es6
lichenyang4535 小时前
鸿蒙 MVVM 实战:从 Demo 到工程化,聊聊登录、状态管理与埋点系统设计
前端