React封装一个纯CSS实现的水滴样式的盒子

news2025/1/9 1:23:11

背景

刷B站刷到一个纯css实现的水滴效果的视频
感觉真不错,决定封装一个具有水滴效果的盒子(DIV)


涉及知识点

  1. CSS样式,核心是这个和box-shadow阴影,实现水滴boder和阴影效果。
  2. JS控制CSS样式
  3. 16进制的颜色(#的写法)与rgba的写法的转换

代码

类型定义:

type embellishmentType = {
  position?: {
    top?: string;
    left?: string;
  };
  backgroundColor?: string;
};

interface WaterDropletProps {
  width?: number;
  height?: number;
  className?: string;
  borderRadius?: string;  
  backgroundColor?: string; 
  embellishment?: embellishmentType;  
  shadowColor?: string;  
  children?: React.ReactNode;
}
WaterDropletProps
参数说明类型默认值
width该盒子的宽number350
width该盒子的高number350
className该盒子的样式名,方便使用时增加额外样式string
borderRadius水滴状的borderRadiusstring52% 48% 33% 67% / 38% 45% 55% 62%
backgroundColor该盒子的背景颜色string#eff0f2
embellishment水滴上两个小点缀的属性,包括位置及背景颜色embellishmentType#eff0f2
shadowColor该盒子的阴影颜色stringrgba(0, 0, 0, 0.05)
children子元素React.ReactNode
embellishmentType
参数说明类型默认值
position位置{left:string;top:string}{left:‘22.85%’,top:‘14.28%’}
backgroundColor背景色string#ffffff

DOM结构

return (
    <div
      style={{ borderRadius, backgroundColor }}
      className={`al-mixed-box-water-droplet ${className}`}
      ref={waterRef}
    >
      {children}
    </div>
  );

三个方法,分别是颜色的hex转rgba,rgba转hex,以及通过正则判断是否是rgba的写法

/**
 * @description: rgba => hex
 * @return {*}
 */
export const rgbaToHex = (val: string, alpha?: number) => {
  //RGB(A)颜色转换为HEX十六进制的颜色值
  let r,
    g,
    b,
    a,
    regRgba = /rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3})(,([.\d]+))?\)/, //判断rgb颜色值格式的正则表达式,如rgba(255,20,10,.54)
    rsa = val.replace(/\s+/g, "").match(regRgba);
  if (!!rsa) {
    r = parseInt(rsa[1]).toString(16);
    r = r.length === 1 ? "0" + r : r;
    g = (+rsa[2]).toString(16);
    g = g.length === 1 ? "0" + g : g;
    b = (+rsa[3]).toString(16);
    b = b.length === 1 ? "0" + b : b;
    a = +(rsa[5] ? rsa[5] : alpha ?? 1) * 255;
    return {
      hex: "#" + r + g + b,
      r: parseInt(r, 16),
      g: parseInt(g, 16),
      b: parseInt(b, 16),
      alpha: rsa[5] ? rsa[5] : alpha ?? 1,
      hexa:
        "#" +
        r +
        g +
        b +
        (a.toString(16).split(".")[0].length === 1
          ? "0" + a.toString(16).split(".")[0]
          : a.toString(16).split(".")[0]),
    };
  } else {
    return { hex: "无效", alpha: 100 };
  }
};

/**
 * @description: hex => rgba
 * @param {string} val
 * @return {*}
 */
export const hexToRgba = (val: string) => {
  // 16进制颜色值的正则
  let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/;
  // 把颜色值变成小写
  let color = val.toLowerCase();
  let result = "";
  if (reg.test(color)) {
    // 如果只有3位的值,需变成8位,如:#fff => #ffffffff
    if (color.length === 4) {
      let colorNew = "#";
      for (let i = 1; i < 4; i += 1) {
        colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1));
      }
      color = colorNew + "ff";
    }
    // 如果只有6位,需要变成8位,如:#ffffff => #ffffffff
    if (color.length === 7) {
      color = color + "ff";
    }
    // 处理8位的颜色值,转为RGBA
    let colorChange = [];
    for (let i = 1; i < 9; i += 2) {
      if (i >= 7) {
        colorChange.push(parseInt("0x" + color.slice(i, i + 2)) / 255);
      } else colorChange.push(parseInt("0x" + color.slice(i, i + 2)));
    }
    result = "rgba(" + colorChange.join(",") + ")";
    return {
      rgba: result,
      r: colorChange[0],
      g: colorChange[1],
      b: colorChange[2],
      a: colorChange[3],
    };
  } else {
    result = "error";
    return { rgba: result };
  }
};

/**
 * @description: 检查是否符合rgba的格式
 * @param {string} val
 * @return {*}
 */
export const regRgbaFormat = (val: string) => {
  let regRgba = /rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3})(,([.\d]+))?\)/;
  return regRgba.test(val);
};

CSS样式

通过var设置的样式变量,以及calc的样式计算

.al-mixed-box-water-droplet {
  width: var(--droplet-width);
  height: var(--droplet-height);
  position: relative;
  overflow: hidden;
  box-shadow: inset var(--box-shadow-20) var(--box-shadow-20)
      var(--box-shadow-20) var(--shadowColor),
    var(--box-shadow-25) var(--box-shadow-35) var(--box-shadow-20)
      var(--shadowColor),
    var(--box-shadow-25) var(--box-shadow-30) var(--box-shadow-30)
      var(--shadowColor),
    inset calc(-1 * var(--box-shadow-20)) calc(-1 * var(--box-shadow-20))
      var(--box-shadow-25) rgba(255, 255, 255, 0.8);
  --embellishmentL: 22.85%;
  --embellishmentT: 14.28%;
  --embellishmentBKC: #ffffff;
  --shadowColor: rgba(0, 0, 0, 0.05);
  --box-shadow-20: calc(var(--droplet-width) * 20 / 350);
  --box-shadow-25: calc(var(--droplet-width) * 25 / 350);
  --box-shadow-30: calc(var(--droplet-width) * 30 / 350);
  --box-shadow-35: calc(var(--droplet-width) * 35 / 350);
}
.al-mixed-box-water-droplet::before,
::after {
  content: "";
  position: absolute;
  left: var(--embellishmentL);
  top: var(--embellishmentT);
  width: var(--embellishmentWH);
  height: var(--embellishmentWH);
  background-color: var(--embellishmentBKC);
  border-radius: 50%;
  opacity: 0.9;
}
.al-mixed-box-water-droplet::after {
  width: calc(var(--embellishmentWH) / 2);
  height: calc(var(--embellishmentWH) / 2);
  transform: translate(200%, 200%);
}

JS控制CSS样式

通过.style.setProperty()改变css中的变量来达到控制css样式目的

  useEffect(() => {
    // set water droplet width and height
    waterRef.current?.style.setProperty("--droplet-width", width + "px");
    waterRef.current?.style.setProperty("--droplet-height", height + "px");
    // set embellishment width and height
    waterRef.current?.style.setProperty(
      "--embellishmentWH",
      `${Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) / 14}px`
    );
    // set embellishment position and background color
    if (embellishment) {
      waterRef.current?.style.setProperty(
        "--embellishmentL",
        embellishment.position?.left ?? null
      );
      waterRef.current?.style.setProperty(
        "--embellishmentT",
        embellishment.position?.top ?? null
      );
      waterRef.current?.style.setProperty(
        "--embellishmentBKC",
        embellishment.backgroundColor ?? null
      );
    }
    // set shadow color
    if (shadowColor) {
      let newShadowColor = "";
      if (
        shadowColor.includes("#") &&
        hexToRgba(shadowColor).rgba !== "error"
      ) {
        newShadowColor = hexToRgba(shadowColor).rgba;
      } else if (regRgbaFormat(shadowColor)) {
        newShadowColor = hexToRgba(rgbaToHex(shadowColor, 0.05).hexa!).rgba;
      } else {
        console.warn("Please check the color format————", shadowColor);
      }
      if (newShadowColor !== "") {
        waterRef.current?.style.setProperty("--shadowColor", newShadowColor);
      }
    }
  }, [width, height, embellishment, shadowColor]);

完整代码

/*
 * @Author: atwLee
 * @Date: 2022-12-24 10:15:57
 * @LastEditors: atwLee
 * @LastEditTime: 2022-12-24 21:01:00
 * @Description: 水滴形状的box
 * @FilePath: /mixed/src/packages/box/water-droplet/index.tsx
 */
import { useEffect, useRef } from "react";
import { hexToRgba, regRgbaFormat, rgbaToHex } from "../../utils";
import "./index.css";

type embellishmentType = {
  position?: {
    top?: string;
    left?: string;
  };
  backgroundColor?: string;
};

interface WaterDropletProps {
  width?: number; // 该盒子的宽
  height?: number; // 该盒子的高
  className?: string; // 该盒子的样式名,方便使用时增加额外样式
  borderRadius?: string; // 水滴状的borderRadius
  backgroundColor?: string; // 该盒子的背景颜色
  embellishment?: embellishmentType; // 水滴上两个小点缀的属性,包括位置及背景颜色
  shadowColor?: string; // 阴影的颜色
  children?: React.ReactNode; // 子元素
}

function WaterDroplet(props: WaterDropletProps) {
  const {
    width = 350,
    height = 350,
    children,
    className = "",
    borderRadius = "52% 48% 33% 67% / 38% 45% 55% 62%",
    backgroundColor = "#eff0f2",
    embellishment,
    shadowColor,
  } = props;

  const waterRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // set water droplet width and height
    waterRef.current?.style.setProperty("--droplet-width", width + "px");
    waterRef.current?.style.setProperty("--droplet-height", height + "px");
    // set embellishment width and height
    waterRef.current?.style.setProperty(
      "--embellishmentWH",
      `${Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) / 14}px`
    );
    // set embellishment position and background color
    if (embellishment) {
      waterRef.current?.style.setProperty(
        "--embellishmentL",
        embellishment.position?.left ?? null
      );
      waterRef.current?.style.setProperty(
        "--embellishmentT",
        embellishment.position?.top ?? null
      );
      waterRef.current?.style.setProperty(
        "--embellishmentBKC",
        embellishment.backgroundColor ?? null
      );
    }
    // set shadow color
    if (shadowColor) {
      let newShadowColor = "";
      if (
        shadowColor.includes("#") &&
        hexToRgba(shadowColor).rgba !== "error"
      ) {
        newShadowColor = hexToRgba(shadowColor).rgba;
      } else if (regRgbaFormat(shadowColor)) {
        newShadowColor = hexToRgba(rgbaToHex(shadowColor, 0.05).hexa!).rgba;
      } else {
        console.warn("Please check the color format————", shadowColor);
      }
      if (newShadowColor !== "") {
        waterRef.current?.style.setProperty("--shadowColor", newShadowColor);
      }
    }
  }, [width, height, embellishment, shadowColor]);

  return (
    <div
      style={{ borderRadius, backgroundColor }}
      className={`al-mixed-box-water-droplet ${className}`}
      ref={waterRef}
    >
      {children}
    </div>
  );
}

export default WaterDroplet;

使用

import { WaterDroplet } from "./packages/box";
function App() {
  return (
    <div className="App">
      <WaterDroplet
        // width={120}
        // height={120}
        // embellishment={{
        //   position: { top: "15px", left: "30px" },
        //   backgroundColor: "rgba(255,255,255,0.45)",
        // }}
        // borderRadius={"34% 66% 65% 35% / 57% 58% 42% 43%"}
        // backgroundColor="#c61dff"
        // shadowColor="rgba(190,1,254,0.1)"
        width={120}
        height={120}
        embellishment={{
          position: { top: "35px", left: "30px" },
          backgroundColor: "rgba(255,255,255,0.45)",
        }}
        borderRadius={"75% 25% 68% 32% / 29% 70% 30% 71%"}
        backgroundColor="#01b4ff"
        shadowColor="rgb(1,180,255)"
      >
        <div>you code</div>
      </WaterDroplet>
    </div>
  );
}

export default App;

属性换成上边那一套就是紫色的水滴样子,没有属性默认是白色的样子


感谢观看!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/113112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot项目+注册功能实现

注册功能实现分析 目录 一、AppConfig.java 二、register.html 三、UserDto.java 四、UserController.java 五、UserMapper.xml 六、运行结果 一、AppConfig.java Configuration public class AppConfig implements WebMvcConfigurer {//统一视图跳转Overridepublic void…

(1)Qt的基本数据类型以及基本输出

基础类型 因为Qt是一个C框架, 因此C中所有的语法和数据类型在Qt中都是被支持的, 但是Qt中也定义了一些属于自己的数据类型, 下边给大家介绍一下这些基础的数据类型。 类型名称注释备注qint8signed char有符号8位数据qint16signed short16位数据类型qint32signed short32位有符号…

数据结构与算法java篇--二叉树

内容有点多&#xff0c;建议先收藏 目录 一.树的介绍 二.java代码实现树 1.Node类 2.Tree类 3.查找节点 4.插入节点 5.遍历树 5.1 中序遍历 5.2 前序遍历和后序遍历 6.查找最大值和最小值 7.删除节点 7.1 删除没有子节点的节点 7.2 删除有一个子节点的结点 7.3 删除…

java美食论坛系统发帖子系统美食论坛网站美食分享论坛源码

ssm开发的美食论坛系统&#xff0c;用户注册之后可以发布关于美食的帖子&#xff0c;其他人可以回帖&#xff0c;评论&#xff0c;点赞回复和评论&#xff0c;分为楼主&#xff0c;第一楼&#xff0c;第二楼等。可以再个人中心查看我对别人的回复&#xff0c;以及别人对我的回复…

学习黑客十余年,如何成为一名安全工程师?

1. 前言 说实话&#xff0c;一直到现在&#xff0c;我都认为绝大多数看我这篇文章的读者最后终究会放弃&#xff0c;原因很简单&#xff0c;自学终究是一种适合于极少数人的学习方法&#xff0c;而且非常非常慢&#xff0c;在这个过程中的变数过大&#xff0c;稍有不慎&#x…

主成分分析(PCA)

主成分分析是一种降维&#xff08;去除噪声和不重要信息&#xff09;方法&#xff0c;它能将多个指标转换为少数几个主成分&#xff0c;这些主成分是原始变量的线性组合&#xff0c;且彼此之间互不相关。 适用性&#xff1a;当研究的问题涉及到多变量且变量之间存在很强的相关…

【LeetCode】2099. 找到和最大的长度为 K 的子序列

【LeetCode】2099. 找到和最大的长度为 K 的子序列 给你一个整数数组 nums 和一个整数 k 。你需要找到 nums 中长度为 k 的 子序列 &#xff0c;且这个子序列的 和最大 。 请你返回 任意 一个长度为 k 的整数子序列。 子序列 定义为从一个数组里删除一些元素后&#xff0c;不…

BIT.1_常见指令以及权限理解

目录使用 XShell 远程登录 Linux下载安装 XShell查看 Linux 主机 ip使用 XShell 登陆主机XShell 下的复制粘贴文件 内容数据 属性数据Linux目录结构相对/绝对路径Linux下基本指令01. ls 指令02. pwd命令03. cd 指令04. touch指令05.mkdir指令&#xff08;重要&#xff09;&am…

基于Xlinx的时序分析与约束(4)----主时钟约束

主时钟约束语法 主时钟约束&#xff0c;就是我们对主时钟&#xff08;Primary Clock&#xff09;的时钟周期进行约束&#xff08;告诉综合工具布局布线的标准&#xff09;&#xff0c;这个约束是我们用的最多的约束了&#xff0c;也是最重要的约束。 主时钟必须与一个网表对象相…

seo综合查询,怎么看网站在移动端权重高低

移动权重就是指在手机、IPAD等的流量&#xff0c;数值越大流量越多。 未来百度流量一定会更倾向于移动端&#xff0c;移动端搜索将是百度搜索引擎的主要阵地。这一点和用户上网习惯有关系&#xff0c;因为移动网络无处不在。 那么怎么看网站在移动端权重高低&#xff1f;最…

Office选装 + Visio安装

一.Office选装 这里我以装Word、Excel和Powerpoint为例 第一步&#xff1a;下载office tool plus 下载地址&#xff1a;https://otp.landian.vip/zh-cn/ 打开网址后&#xff0c;点击红色的立即下载&#xff0c;进入文件存放页面&#xff1b; 第二步&#xff1a;选择其中一个下…

STM32F103C8T6平衡车

链接&#xff1a;https://pan.baidu.com/s/1WJ9otyE9LuO6Kh5gyQ0Tug?pwd8888 提取码&#xff1a;8888 #define BIN2_Pin GPIO_PIN_12 #define BIN2_GPIO_Port GPIOB #define BIN1_Pin GPIO_PIN_13 #define BIN1_GPIO_Port GPIOB #define AIN1_Pin GPIO_PIN_14 #define AIN1_…

CSS知识点1

CSS:层叠样式表 1.行内样式&#xff1a;<h1 style"background-color: aqua;">Hellocss</h1> 仅对当前标签有效 2.内部样式&#xff1a;写在title处 对当前页面有效 3.外部标签&#xff1a;<link rel"stylesheet" href"./js/main…

干货分享丨超详细的200G QSFP56光模块知识

随着数据中心的快速发展&#xff0c;在100G光模块不足以满足日益增长的网络升级要求而400G还未普及的情况下&#xff0c;200G QSFP56光模块成为了200G以太网部署的主流解决方案。接下来&#xff0c;就由易天光通信为你详细介绍一下200G QSFP56光模块。 200G QSFP56光模块是40G…

供应Biotin-PEG-acid,Biotin-PEG-COOH,生物素-聚乙二醇-羧基

含有生物素和羧酸的线性杂双功能PEG试剂化学试剂Biotin-PEG-acid /Biotin-PEG-COOH其英文名为&#xff0c;它所属分类为Biotin PEG Carboxylic acid PEG。 peg试剂的分子量均可定制&#xff0c;有&#xff1a;生物素-PEG1-羧基、Biotin-PEG2-COOH、生物素-peg3.4-羧基、Biotin…

python代码~创意圣诞树

2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 圣诞节(Christmas)本身是一个宗教节&#xff0c;用来庆祝耶稣的诞辰&#xff0c;因而又名耶诞节 Hope all your Christmas dreams come true!    愿你所有的圣诞梦想都成真!    Hope you enjoy the happiness o…

Express 基本使用(简)

前一篇内容讲到Express框架的安装以及对Express项目的目录文件有一定的认识了解之后&#xff0c;使用Express创建了最基本的一个Web服务器&#xff0c;接下来进行对Express框架的一些内容来做一个基本的使用&#xff1b; 创建 Web 服务器 node 或 nodemon 执行app.js文件&#…

贪心法算法

目录 一 算法简介 1&#xff09;算法案例引入 2&#xff09;算法思想 3&#xff09;算法概念 4&#xff09; 算法求解的问题的特征 5&#xff09;算法应用 二 算法常见问题 1&#xff09;活动安排问题&#xff08;区间调度问题&#xff09; 今年暑假不AC 2&#xff…

【QT】信号与槽

信号与槽 信号(Signal) 与 槽(Slot) 是Qt中对象之间的通信方式&#xff0c;可以用一个简单的栗子说明&#xff1a;当我们想要开灯时&#xff0c;按下开关发出指令&#xff0c;这就是信号&#xff1b;而灯泡接收到电流变化&#xff0c;发出光亮&#xff0c;这就是响应(槽)。 我…

通过cmd指令创建vue项目

通过cmd指令创建vue项目 基础材料&#xff1a; 已安装node.js 已安装 npm&#xff08;安装node.js后会自带安装npm&#xff09; 首先通过node –v和npm –v确保他们都安装了&#xff0c;能够看见版本号就表示ok 然后通过指令安装vue脚手架 npm install -g vue/cli5.0.8 安…