blog icon indicating copy to clipboard operation
blog copied to clipboard

纯css实现水球图效果React版本

Open xianzou opened this issue 1 year ago • 0 comments

效果展示

示例:

原文参考:CSS实现个性化水球图 https://blog.csdn.net/qq_40289557/article/details/127593240

Liquid.js

import React, { useMemo } from 'react';
import cx from 'classnames';
import styles from './Liquid.scss';

const defaultConfig = {
    frontColor: '#0bc8e8', // 前面波纹颜色
    backColor: '#0b6d98', // 后面波纹颜色
    outerBorderColor: '#0bc8e8', // 外边框颜色
    outerBorderWidth: '4px', // 外变宽宽度
    outerPadding: '4px', // 外边框内边距
    innerBackground: 'transparent', // 水球内部背景颜色
    doubleWaves: true, // 是否显示双波浪线
    borderRadius: '50%', // 外围边框圆角程度
    crests: 40, // 波峰-波谷,值0-70,值越大,水面突出越明显
};
// 圆形双波效果
// const config = {
//     outerBorderWidth: '6px',
//     outerPadding: '6px',
//     innerBackground: '#ddf0f8',
//     crests: 50,
// };
// 正方形 双波效果
// const config = {
//     outerBorderWidth: '6px',
//     outerPadding: '6px',
//     innerBackground: '#ddf0f8',
//     borderRadius: '20%',
//     crests: 30,
// };
// 圆形单波浪效果
const config = {
    doubleWaves: false,
};
const rateNum = '75%';

const Liquid = () => {
    const styleObj = useMemo(() => {
        const obj = { ...defaultConfig, ...config };
        let rate = rateNum.replace('%', '');
        const waveDisplay = obj.doubleWaves ? 'block' : 'none';
        if (rate <= 0) {
            rate = 0;
        } else if (rate >= 100) {
            rate = 100;
        }

        return {
            '--front-color': obj.frontColor,
            '--back-color': obj.backColor,
            '--outer-border-color': obj.outerBorderColor,
            '--outer-border-width': obj.outerBorderWidth,
            '--outer-padding': obj.outerPadding,
            '--inner-background': obj.innerBackground,
            '--water-height': `${rate}%`,
            '--wave-display': waveDisplay,
            '--border-radius': obj.borderRadius,
        };
    }, []);

    const path = useMemo(() => {
        const obj = { ...defaultConfig, ...config };
        let { crests } = obj;
        if (crests >= 70) {
            crests = 70;
        } else if (crests <= 0) {
            crests = 0;
        }

        return `M 0 70 Q 75 ${70 - crests},150 70 T 300 70 T 450 70 T 600 70 L 600 140 L 0 140 L 0 70Z`;
    }, []);

    return (
        <div className={styles.pageBox}>
            <div className={styles.module}>
                {/* eslint-disable-next-line react/forbid-dom-props */}
                <div className={styles.boxWrap} style={styleObj}>
                    <div className={styles.box}>
                        <div className={styles.fillArea}>
                            <svg
                                xmlns="http://www.w3.org/2000/svg"
                                version="1.0"
                                className={cx(styles.waves, styles.backWave)}
                                viewBox="0 0 600 140"
                            >
                                <path d={path} />
                            </svg>
                            <svg
                                xmlns="http://www.w3.org/2000/svg"
                                version="1.0"
                                className={cx(styles.waves, styles.frontWave)}
                                viewBox="0 0 600 140"
                            >
                                <path d={path} />
                            </svg>
                        </div>
                        <div className={styles.slotContent}>
                            <span className={styles.slotFont1}>75%</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};
export default Liquid;

.pageBox {
    width: 100%;
    overflow: auto;
    text-align: center;
    display: flex;
    justify-content: center;
  }
 
  .module {
    /* 务必保证容器宽高一致、否则会导致水面高度计算有误 */
    width: 160px;
    height: 160px;
    box-sizing: border-box;
    padding: 10px;
  }
  .slotFont1 {
    color: #fff;
    font-size: 20px;
    font-weight: bold;
  }

.boxWrap {
    width: 100%;
    height: 100%;
    border: var(--outer-border-width) solid var(--outer-border-color);
    padding: var(--outer-padding);
    box-sizing: border-box;
    border-radius: var(--border-radius);
  }
  .box {
    position: relative;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    border-radius: var(--border-radius);
    /** 解决增加圆角后超出部分不隐藏bug */
    z-index: 1;
    overflow: hidden;
    background-color: var(--inner-background);
  }
  /* 波纹填充区域 */
  .fillArea {
    position: absolute;
    left: 0;
    bottom: -123.33%;
    width: 100%;
    height: 100%;
    transform: translateY(calc(0% - var(--water-height)));
    background-color: var(--front-color);
  }
  .waves {
    position: absolute;
    left: 0;
    bottom: 100%;
    width: 200%;
    stroke: none;
    /* 解决水球图中间有一条线问题 */
    box-shadow: 0 10px 4px 4px var(--front-color);
  }
  .frontWave {
    fill: var(--front-color);
    transform: translate(-50%, 0);
    animation: front-wave-move 3s linear infinite;
  }
  .backWave {
    display: var(--wave-display);
    fill: var(--back-color);
    transform: translate(0, 0);
    animation: back-wave-move 1.5s linear infinite;
  }
  @keyframes front-wave-move {
    100% {
      transform: translate(0, 0);
    }
  }
  @keyframes back-wave-move {
    100% {
      transform: translate(-50%, 0);
    }
  }
  /* 插槽内容样式 */
  .slotContent {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
 

xianzou avatar Jan 03 '24 02:01 xianzou