【React】双十一就写个购物卡片动画组件吧

537 阅读4分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

介绍

双十一将至,不知道大家做过商城类的项目没有,大多比较枯燥死板的加减数量,同质化也都比较强,咱们本期就做一个清新风的购物卡片的React组件,让他增加一些用户体验,当然本文面相新手向和创意流,技术大佬请绕路。

先康康我们做完的效果吧:

VID_20211105_224645.gif

我们可以看到啊,每次点击加减数量都会出现一个产生一个总数累加的动效,当归0时,再减小,就会数量0就会放大提示用户我已经是0了别往下减了,这样虽然没有任何一句提示,仅仅通过动画就没有表达给用户。接下来,我们就开始实现它吧。

正文

1.基本逻辑

我们用react hook先写出一个卡片结构吧:

import './style.scss';
import { useState, useEffect, useRef } from "react"

function BuyCard(props) {
  const { src, price } = props;
  const [num, setNum] = useState(0);
  const [total, setTotal] = useState(0);
  const totalRef = useRef(null);
  const amountRef = useRef(null)

  useEffect(() => {
    setTotal(num * price)
  }, [num, price])

  function handleAdd() {
    setNum(num + 1)
  }
    
  function handleMinus() {
    setNum(num - 1)
  }

  return (
    <div className="buy-card">
      <img src={src} alt="" />
      <div className="buy-info">
        <div className="buy-title"><label>PRICE:</label><span>$</span><span>{price}</span></div>
        <div className="buy-title"><label>TOTAL:</label><span>$</span><span ref={totalRef}>{total}</span></div>
        <div className="btns">
          <button className="minus btn" onClick={handleMinus}>-</button>
          <p className="amount"><span ref={amountRef}>{num}</span></p>
          <button className="add btn" onClick={handleAdd}>+</button>
        </div>
      </div>

    </div>
  );
}

export default BuyCard;

我们期望从外界传入图片和价格,如果真是开发应该还有数量等信息,本次我们先将数量不暴露出来,方便观看效果,其逻辑只做简单表述。点击加减都绑定了对应的事件去改变数量,而改变后会因useEffect执行改变其总价格。

当然,现在还不能看,我们还要写点样式出来,非常简约的卡片样式,这里就不做赘述了:

.buy-card{
    width: 200px;
    height: 300px;
    background-color: #ffffff;
    box-sizing: border-box;
    box-shadow: 1px 1px 10px rgba(0,0,0,.1);
    border-radius: 5px;
    border: 2px dashed #c33;
    padding-top: 10px;
    transition: .2s all;
    &:hover{
        box-shadow: 1px 1px 12px rgba(0,0,0,.3);
    }
    &>img{
        width: 150px;
        height: 150px;
        display: block;
        margin: 0 auto;
    }
    .btns{
        display: flex;
        align-items: center;
        justify-content: center;
        margin-top: 10px;
        .btn{
            border: 0;
            cursor: pointer;
            border-radius: 50%;
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            &.add{
                background-color: rgb(118, 201, 98);
            }
            &.minus{
                background-color: rgb(233, 93, 88);
            }
        }
        .amount{
            width: 50px;
            text-align: center;
            height: 24px;
            line-height: 24px;
            border-bottom: 1px solid rgb(73, 73, 72);
            margin: 0 10px;
            span{
                font-size: 18px;
                font-weight: bold;
                width: 100%;
                height: 24px;
                display: block;
                line-height: 24px;
            }
        }
    }
    .buy-info{
        width:100%;
        box-sizing: border-box;
        padding: 15px;
        font-family: 'Courier New', Courier, monospace;
        .buy-title{
            margin-bottom: 13px;
            font-size: 16px;
            font-weight: bold;
            height: 24px;
            line-height: 24px;
            border-bottom: 1px solid rgb(73, 73, 72);
            label{
                margin-right: 5px;           
            }
        }
    }
}

现在我们就可以在App.js引用他,就可以看到样式了。

import BuyCard from "./components/buyCard"
const f0 = require("./assets/food0.png")
function App() {
  return (
    <div className="App">
      <div className="cards">
        <BuyCard price="100" src={f0.default} />
      </div>
    </div>
  );
}

微信截图_20211106105821.png

2.累计与触零动画

我们实现这两个动画的方案很多,今天就来说一种个人感觉最佳的方案——animejs,他可以自由实现各种各样的网页动画,而且十分流程。接下来就用它来实现效果吧。

  import anime from "animejs"
  function BuyCard(props) {
      // ...
      useEffect(() => {
        anime({
          targets: totalRef.current,
          textContent: [total, num * price],
          round: 1,
          easing: 'easeOutCirc',
          duration: 300,
          complete() {
            setTotal(num * price)
          }
        });
      }, [num, price])
      // ...
  }

这是那个累计动画,获取到要改变的dom元素后,让他在textContent去改变,可以设置缓动,周期等,但是一定要注意的是,在react中不像正常操作dom那样光改了就行了,还要在其动画结束后的complete函数内再给total赋值一遍,这样才会在react中改变total值,不然不光在react拿不到值,也会出现每次都是从0开始执行动画的bug。

  function handleMinus() {
    if(num===0){
      return anime({
          targets: amountRef.current,
          easing: 'linear',
          duration: 400,
          scale: [{ value: 1.6 }, { value: 1 }]
      });
    }
    setNum(num - 1)
  }

后面的触零动画就简单多了,我们每次如果数量到了0如果再往下减就阻断他,并且利用animejs改变scale属性,使其出现一个由大至小的动画,用动画来表明数量不可为负数了。

VID_20211106_130002.gif

说到这里其实已经完成了全部,是不是很容易,在线演示

结语

本次其实是react hook与animejs的一次结合使用,不知道是否激发了你新的创意,别看一个小小的卡片其实还可以增加很多东西,比如加减归零等触发声音,或再给商品加入一些趣味动画,与诸多扩展。等你来发挥~