背景
刷B站刷到一个纯css实现的水滴效果的视频
感觉真不错,决定封装一个具有水滴效果的盒子(DIV)
涉及知识点
- CSS样式,核心是这个和box-shadow阴影,实现水滴boder和阴影效果。
- JS控制CSS样式
- 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 | 该盒子的宽 | number |
350 |
width | 该盒子的高 | number |
350 |
className | 该盒子的样式名,方便使用时增加额外样式 | string |
|
borderRadius | 水滴状的borderRadius | string |
52% 48% 33% 67% / 38% 45% 55% 62% |
backgroundColor | 该盒子的背景颜色 | string |
#eff0f2 |
embellishment | 水滴上两个小点缀的属性,包括位置及背景颜色 | embellishmentType |
#eff0f2 |
shadowColor | 该盒子的阴影颜色 | string |
rgba(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;
属性换成上边那一套就是紫色的水滴样子,没有属性默认是白色的样子