积分球领取补位动画实现

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

动画演示

积分球与列表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);
  }
}
相关推荐
liangshanbo12153 小时前
写好 React useEffect 的终极指南
前端·javascript·react.js
哆啦A梦15885 小时前
搜索页面布局
前端·vue.js·node.js
_院长大人_6 小时前
el-table-column show-overflow-tooltip 只能显示纯文本,无法渲染 <p> 标签
前端·javascript·vue.js
SevgiliD6 小时前
el-table中控制单列内容多行超出省略及tooltip
javascript·vue.js·elementui
要加油哦~6 小时前
JS | 知识点总结 - 原型链
开发语言·javascript·原型模式
哆啦A梦15887 小时前
axios 的二次封装
前端·vue.js·node.js
阿珊和她的猫7 小时前
深入理解与手写发布订阅模式
开发语言·前端·javascript·vue.js·ecmascript·状态模式
yinuo7 小时前
一行 CSS 就能搞定!用 writing-mode 轻松实现文字竖排
前端
snow@li8 小时前
html5:拖放 / demo / 拖放事件(Drag Events)/ DataTransfer 对象方法
前端·html·拖放
爱看书的小沐8 小时前
【小沐杂货铺】基于Three.js渲染三维风力发电机(WebGL、vue、react、WindTurbine)
javascript·vue.js·webgl·three.js·opengl·风力发电机·windturbine