记录用react+css的clipPath及mask计算切割,得到圆环进度条

某次公司上了一个需求,要求做一个顺时针旋转的圆环进度条,根据百分比显示进度条比例,经过多方研究和查阅,最终决定用clipPath及坐标系计算圆环进度上x,y点的位置,然后切割圆环,留下需要的部分,就显示进度条了,话不多说,先上结果:

1、首先画两个圆,一个背景圈一个进度圈

下面是两个圈的样式,运用mask的radial-gradient画出圆环渐变背景色

.circleOut {
  width: 3rem;
  height: 3rem;
  background: #FFF9EF;
  border-radius: 50%;
  -webkit-mask: radial-gradient(transparent, transparent 1rem, #000 1rem);
  mask: radial-gradient(transparent 1rem, #000 1rem);
  .circle {
    width: 3rem;
    height: 3rem;
    background: conic-gradient(
      from 180deg,
      #FFE6C1,
      #FFC369,
    );
    border-radius: 50%;
    -webkit-mask: radial-gradient(transparent, transparent 1rem, #000 1rem);
    mask: radial-gradient(transparent 1rem, #000 1rem);
  }
}

2、计算以左上角点为原点建立x,y轴坐标系,计算在不同比例下得到的圆环内上内外点的坐标。

// 计算当前进度条的点在圆上x,y轴坐标(圆心坐标axisx,axisy, 半径r)
  const calAxis = (axisx, axisy, r) => {
    let deg = 0;
    if (rate < 0.25) {
      deg = (0.25 - rate) * 360 + 90;
      deg = -deg;
    } else if (rate >= 0.25 && rate <= 0.75) {
      deg = (0.75 - rate) * 360;
    } else {
      deg = -(rate - 0.75) * 360;
    }
    const x = axisx + r * Math.cos((deg * Math.PI) / 180);
    const y = -axisy + r * Math.sin((deg * Math.PI) / 180);
    console.log('11111', x, y);
    return {
      x: x,
      y: y
    };
  };

 3、计算在不同比例下clipPath切割内部进度圆环的方法(此处建议阅读clipPath方法)

// 计算切割点
  const clipPathStyle = () => {
    let clipPath = '';
    if (rate < 0.25) {
      clipPath = `polygon(0% 100%, ${axis.x * 100}% ${-axis.y * 100}%, 50% 50%, 50% 100%)`;
    } else if (rate >= 0.25 && rate <= 0.75) {
      clipPath = `polygon(0% 0%, ${axis.x * 100}% 0%, ${axis.x * 100}% ${
        -axis.y * 100
      }%, 50% 50%, 50% 100%, 0% 100%)`;
    } else {
      clipPath = `polygon(0% 0%, 100% 0%, 100% 100%, ${axis.x * 100}% ${
        -axis.y * 100
      }%, 50% 50%, 50% 100%, 0% 100%)`;
    }
    return {
      clipPath: clipPath
    };
  };

此时我们得到了切割后的圆环

4、最后就是把笑脸定位在进度的头上,然后中间加上文案就结束了

// 计算笑脸位置
  const calAxisSmile = () => {
    const inCirle = calAxis(0.5, 0.5, (0.5 * 2) / 3);
    let top = (-axis.y * 3 + -inCirle.y * 3) / 2 - 0.36;
    let left = (axis.x * 3 + inCirle.x * 3) / 2 - 0.36;
    return {
      top: `${top}rem`,
      left: `${left}rem`
    };
  };

此时我们得到:

最后附上原代码(第一个是js的代码页,第二个是css的代码页)

import { memo, useState, useEffect, Fragment } from 'react';
import styles from './index.scss';
import smile1 from '../../../assets/smile1.png';
import smile2 from '../../../assets/smile2.png';
import smile3 from '../../../assets/smile3.png';
import smile4 from '../../../assets/smile4.png';

const IncomeCircle = ({ rate = 0.8 }) => {
  rate = rate > 1 ? 1 : rate.toFixed(2);
  // console.log('111111', rate);
  // xy坐标
  const [axis, setAxis] = useState({
    x: 0,
    y: 0
  });
  // 当前进度的类型
  // 【0%~35%)-落差巨大、【35%~60%)-勉强维持、【60%~80%)-基本持平、【80%~100%】-舒适生活
  const [type, setType] = useState({
    text: '落差巨大',
    smile: smile4
  });

  useEffect(() => {
    const initData = () => {
      setAxis(calAxis(0.5, 0.5, 0.5));
      let nowType = {
        text: '落差巨大',
        smile: smile4
      };
      if (rate <= 0.35) {
        nowType = {
          text: '落差巨大',
          smile: smile4
        };
      } else if (rate > 0.35 && rate <= 0.6) {
        nowType = {
          text: '勉强维持',
          smile: smile3
        };
      } else if (rate > 0.6 && rate <= 0.8) {
        nowType = {
          text: '基本持平',
          smile: smile2
        };
      } else {
        nowType = {
          text: '舒适生活',
          smile: smile1
        };
      }
      setType(nowType);
    };
    initData();
  }, []);

  // 计算当前进度条的点在圆上x,y轴坐标(圆心坐标axisx,axisy, 半径r)
  const calAxis = (axisx, axisy, r) => {
    let deg = 0;
    if (rate < 0.25) {
      deg = (0.25 - rate) * 360 + 90;
      deg = -deg;
    } else if (rate >= 0.25 && rate <= 0.75) {
      deg = (0.75 - rate) * 360;
    } else {
      deg = -(rate - 0.75) * 360;
    }
    const x = axisx + r * Math.cos((deg * Math.PI) / 180);
    const y = -axisy + r * Math.sin((deg * Math.PI) / 180);
    console.log('11111', x, y);
    return {
      x: x,
      y: y
    };
  };

  // 计算切割点
  const clipPathStyle = () => {
    let clipPath = '';
    if (rate < 0.25) {
      clipPath = `polygon(0% 100%, ${axis.x * 100}% ${-axis.y * 100}%, 50% 50%, 50% 100%)`;
    } else if (rate >= 0.25 && rate <= 0.75) {
      clipPath = `polygon(0% 0%, ${axis.x * 100}% 0%, ${axis.x * 100}% ${
        -axis.y * 100
      }%, 50% 50%, 50% 100%, 0% 100%)`;
    } else {
      clipPath = `polygon(0% 0%, 100% 0%, 100% 100%, ${axis.x * 100}% ${
        -axis.y * 100
      }%, 50% 50%, 50% 100%, 0% 100%)`;
    }
    return {
      clipPath: clipPath
    };
  };

  // 计算笑脸位置
  const calAxisSmile = () => {
    const inCirle = calAxis(0.5, 0.5, (0.5 * 2) / 3);
    let top = (-axis.y * 3 + -inCirle.y * 3) / 2 - 0.36;
    let left = (axis.x * 3 + inCirle.x * 3) / 2 - 0.36;
    return {
      top: `${top}rem`,
      left: `${left}rem`
    };
  };

  return (
    <div className={styles.circleBox}>
      <div className={styles.content}>
        <div className={styles.circleOut}>
          <div className={styles.circle} style={clipPathStyle()} />
        </div>
        <div className={styles.centerText}>
          <div className={styles.rateText}>{`${rate * 100}%`}</div>
          <div className={styles.wordBox}>
            <div className={styles.wordText}>{type.text}</div>
            <div className={styles.wordDivide} />
          </div>
        </div>
        <div className={styles.bottomCircle} />
        {/* 笑脸 */}
        {axis.x !== 0 && axis.y !== 0 && (
          <img className={styles.smile} src={type.smile} alt='' style={calAxisSmile()} />
        )}
      </div>
    </div>
  );
};

export default memo(IncomeCircle);

下面是css的代码

.circleBox {
  width: 100%;
  height: 4rem;
  @include flex-center();
}
.content {
  width: 3rem;
  height: 3rem;
  position: relative;
  .smile {
    position: absolute;
    width: 0.72rem;
    height: 0.72rem;
  }
  .centerText {
    position: absolute;
    width: 3rem;
    height: 3rem;
    top: 0;
    left: 0;
    background-color: transparent;
    @include flex-center(column);
    .rateText {
      color: var(--unnamed, #FF6C32);
      font-family: PingFang SC;
      font-size: 0.48rem;
      font-style: normal;
      font-weight: 500;
      line-height: 0.65rem;
    }
    .wordBox {
      position: relative;
      .wordText {
        color: var(--unnamed, #FF6C32);
        text-align: right;
        font-family: PingFang SC;
        font-size: 0.32rem;
        font-style: normal;
        font-weight: 500;
        line-height: 0.45rem;
      }
      .wordDivide {
        position: absolute;
        width: 1.42rem;
        height: 0.14rem;
        border-radius: 0.04rem;
        background: rgba(255, 108, 50, 0.10);
        bottom: 0;
        left: -0.08rem;
      }
    }
  }
  .bottomCircle {
    position: absolute;
    width: 0.5rem;
    height: 0.5rem;
    top: 2.5rem;
    left: 1.25rem;
    background: #FFE6C1;
    border-radius: 50%;
  }
}
.circleOut {
  width: 3rem;
  height: 3rem;
  background: #FFF9EF;
  border-radius: 50%;
  -webkit-mask: radial-gradient(transparent, transparent 1rem, #000 1rem);
  mask: radial-gradient(transparent 1rem, #000 1rem);
  .circle {
    width: 3rem;
    height: 3rem;
    background: conic-gradient(
      from 180deg,
      #FFE6C1,
      #FFC369,
    );
    border-radius: 50%;
    -webkit-mask: radial-gradient(transparent, transparent 1rem, #000 1rem);
    mask: radial-gradient(transparent 1rem, #000 1rem);
  }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值