一、目的
本文通过 CSS 伪类、伪元素,结合动画 animation 和 Vue 动态样式属性(通过 CSS 变量)的写法,来实现电池充电、高能进度条的效果,如下图所示。
二、基础知识
1、CSS 伪类、伪元素
简单概括成以下 4 点:
1、CSS 伪类是对当前元素下,处于某些特定状态时而添加特殊效果的样式(基于文档之外的抽象,所以叫伪类),不同状态可以应用不同的样式。不产生新元素,写法用单冒号(:)开头,比如 :hover、:active、:visited、:first-child 等。
2、CSS 伪元素是对当前元素下,某些特殊部位应用样式,通过创建虚拟元素实现,一般与 content 属性一起使用(基于元素的抽象,所以叫伪元素)。表示文档中不存在实际的元素,写法用双冒号(::)开头,比如 ::before、::after 等。
3、注意的是,单冒号(:)在一些旧版 CSS 中也被用来表示伪元素,这是为了兼容旧的浏览器,但是在最新的 CSS 规范中,建议应该使用双冒号(::)来表示伪元素,与伪类区分开来。
4、灵活应用 CSS 伪类、伪元素可以创建出更丰富的前端页面效果。
更多详细可以参考以下文档链接:
- 伪元素 - CSS:层叠样式表 | MDN
- CSS 伪元素 | 菜鸟教程
2、CSS 变量 var()
定义:用于插入自定义属性,如果一个属性在多处被使用,该方法就很有用。
语法:var(custom-property-name, value),参数说明如下表格。
值 | 描述 |
---|---|
custom-property-name | 必需。自定义属性的名称,必需以 -- 开头。 |
value | 可选。备用值,在属性不存在的时候使用。 |
详细使用示例:
/* 在 :root 上使用自定义属性,其中 :root 表示文档根元素,该定义将在整个文档内生效 */
:root {
--main-bg-color: red;
--second-bg-color: var(--main-bg-color); /* 可以继承自其他已定义的变量,称为派生变量 */
--main-margin: 20px;
}
#div1 {
background-color: var(--main-bg-color); /* 等同于 background-color: red; */
--main-txt-color: blue; /* 也可以在局部定义变量 */
color: var(--main-txt-color); /* 等同于 color: blue; */
}
#div2 {
background-color: var(--second-bg-color); /* 等同于 background-color: red; */
/* 如果一个变量未被定义,可以使用回退值来替代 */
padding: var(--main-padding, 10px); /* 等同于 padding: 10px; */
/* 使用自定义属性作为回退值 */
margin: var(--second-margin, var(--main-margin, 30px)); /* 等同于 margin: 20px; 因为 --main-margin 有被设置,所以它的回退值不生效*/
}
CSS 变量可以被 JavaScript 动态修改,根据上面示例来操作,如下代码:
// 语法:DOM.style.setProperty(custom-property-name, value)
document.getElementById('div1').style.setProperty('--main-txt-color', 'green')
三、具体操作实现
为了节省代码,达到简单实用的目的,本次实例代码采用 Vue2 框架,ElementUI 组件库,搭配 CSS 预处理器 Scss 完成的。
Scss 写样式能用更简单且高级的写法降低开发成本,比如嵌套规则,父选择器 & 等,如果需要 HTML + CSS + JS 的形式,读者可以自行转化。
实例 1 —— Battery.vue 组件:
- 首先通过 CSS 伪元素描画出电池雏形和电池容量,同时加入闪电图标;
- 接着实现充电时波浪效果所需的容器,通过 CSS 伪类中的 hover 来实现鼠标悬停时效果;
- 然后通过 CSS 伪元素描画出充电时的波浪,让 CSS 伪类 hover 与 CSS 伪元素相结合,并加上动画实现样式效果;
- 最后通过 Vue 动态属性 :style 绑定值传入 CSS 中的变量 var() 使用,实现动态变化。更多具体内容查看下面完整代码。
<template>
<div style="padding: 50px;">
<div class="battery" :style="{'--height': `${100 - elecVal || 0}%`}">
<img :src="iconUrl" alt="闪电icon">
<div class="cover" :style="{'--top': `${0 - elecVal || 0}%`}"></div>
</div>
<div style="margin-top: 20px;">
当前电量:
<el-input-number
v-model="elecVal"
:min="0"
:max="100"
:step="5"
:precision="2"
controls-position="right"
style="width: 120px;"
></el-input-number>
%
</div>
</div>
</template>
<script>
export default {
data() {
return {
elecVal: 75, // 电池电量
iconUrl: 'https://showdoc.keytop.cn/Public/Uploads/2024-04-28/662dbe484313f.png' // 图标线上地址
}
}
}
</script>
<style lang="scss" scoped>
.battery { // 电池容器
width: 100px;
height: 150px;
background: rgb(246, 246, 246);
border: 1px solid rgb(211, 211, 211);
border-radius: 5px; // 大小、背景、边框
position: relative; // 后面绝对定位的前提
img { // 让闪电图标在电池里水平垂直居中,并处于高层级
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 100;
}
&::before { // 伪元素 - 模拟电池正极头
content: "";
width: 50px;
height: 15px;
position: absolute;
left: 50%;
top: -15px;
transform: translate(-50%, 0); // 大小,水平居中,垂直定位
background: rgb(246, 246, 246);
border: 1px solid rgb(211, 211, 211);
border-bottom: none;
border-radius: 5px 5px 0 0; // 背景、边框
}
&::after { // 伪元素 - 模拟电量容量
content: "";
background-color: rgb(68, 220, 148); // 容量颜色
position: absolute;
left: 0;
top: var(--height); // 容量大小,通过值传入动态变化
right: 0;
bottom: 0;
}
.cover{ // 充电时波浪效果容器
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0; // 占满整个原电池容器
overflow: hidden; // 超出部分隐藏
z-index: 10;
}
&:hover{ // 伪类 - 当鼠标 hover 在电池上时,模拟正在充电的波浪效果
.cover::before, .cover::after{ // 伪元素 - 两层波浪
content: "";
background: rgba(246, 246, 246, 0.8);
border-radius: 40% 30%;
position: absolute;
width: 150px;
height: 150px;
left: -30%;
top: var(--top); // 波浪所处位置,通过值传入动态变化
transform: translate(-50%, 0); // 大小,水平居中,垂直定位
animation: coverBefore 10s linear infinite; // 通过动画实现波浪
}
.cover::after{
background: rgba(246, 246, 246, 0.6); // 通过背景色深浅来体现出两层效果
animation: coverAfter 10s linear infinite;
}
}
}
// 定义的动画
@keyframes coverBefore {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes coverAfter {
0% {
transform: rotate(30deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
实例 2 —— EnergyProgress.vue 组件:
- 首先通过 CSS 描画出进度条容器,设置背景色及背景区域为 content-box;
- 接着通过 CSS 伪元素来填充进度条,让其与进度条容器契合,设置相同的 height、padding、background-clip,通过 linear-gradient() 实现填充的渐变颜色;
- 然后再通过 CSS 伪元素描画出结尾节点,并对节点的中心点进行定位;
- 最后通过 Vue 动态属性 :style 绑定值传入 CSS 中的变量 var() 使用,实现动态变化。更多具体内容查看下面完整代码。
<template>
<!-- 这里使用该背景色只是为了效果看得更明显而已 -->
<div style="padding: 50px; background-color: #000; color: #fff;">
<div class="energy-progress" :style="{'--width': `${progress || 0}%`}"></div>
<div style="margin-top: 20px;">
当前进度条:
<el-input-number
v-model="progress"
:min="0"
:max="100"
:step="5"
:precision="2"
controls-position="right"
style="width: 120px;"
></el-input-number>
%
</div>
</div>
</template>
<script>
export default {
data(){
return{
progress: 66.6, // 进度条值
}
}
}
</script>
<style lang="scss" scoped>
.energy-progress{ // 进度条容器
width: 300px;
height: 15px; // 大小
background-color: rgb(22, 91, 128);
padding: 3px 0;
background-clip: content-box; // 缩小容器本身,为了结尾的节点让出空间
position: relative; // 后面绝对定位的前提
&::before{ // 伪元素 - 已填充进度条的部分
content: "";
position: absolute;
left: 0;
top: 0;
width: var(--width); // 填充容量,通过值传入动态变化
height: 15px;
padding: 3px 0;
background-clip: content-box; // 为了与进度条容器契合,同理的
background-image: linear-gradient(to right, rgb(22, 91, 128) 0%, rgb(97, 177, 242) 70%, #fff 100%); // 进度条填充颜色渐变效果
}
&::after{ // 伪元素 - 结尾节点
content: "";
position: absolute;
top: 0;
left: var(--width); // 根据填充容量定位,通过值传入动态变化
width: 6px;
height: 15px; // 大小
background-color: #fff;
border-radius: 5px;
margin-left: -3px; // 这个设为宽度一半的负值,让原本是根据左边框对准位置的,这下让其中心对准位置了
transform: rotate(30deg);
}
&:hover{ // 伪类 - 当鼠标 hover 在电池上时,模拟进度条高能效果
cursor: pointer;
&::before{ // 伪元素 - 宽度填充动画(1 秒完成,且先慢后快)
animation: moveBefore 1s;
}
&::after{ // 伪元素 - 结尾节点填充动画(1 秒完成,且先慢后快)
animation: moveAfter 1s;
}
}
}
// 定义的动画
@keyframes moveBefore {
0% {
width: 0;
}
100% {
width: var(--width);
}
}
@keyframes moveAfter {
0% {
left: 0;
}
100% {
left: var(--width);
}
}
</style>
这是我本人在工作学习中做的一些总结,同时也分享出来给需要的小伙伴哈 ~ 供参考学习,有什么建议也欢迎评论留言,转载请注明出处哈,感谢支持!