【VUE3.0】动手做一套像素风的前端UI组件库---Button

目录

  • 引言
  • 做之前先仔细看看UI设计稿
    • 解读一下都有哪些元素:
    • 素材补充
  • 代码编写
    • 1. 按钮四周边框
    • 2. 默认状态下按钮颜色立体效果
    • 3. 鼠标移入聚焦
    • 4. 模拟鼠标点击效果
  • 组件封装
    • 1. 按类型设置颜色
    • 2. 设置按钮禁用状态
    • 3. 处理一个bug
    • 4. 看下整体组件效果
    • 5. 组件完整代码
    • 6. 组件调用方式
  • 总结

引言

本教程基于前端UI样式库 NES.css 的UI设计,自行研究复现。欢迎大家交流优化实现方法~

此次组件库开发基于vue3框架,框架基础搭建过程以及基础素材准备参考:【VUE3.0】动手做一套像素风的前端UI组件库—先导篇

本篇复现的组件为button,日常项目中较为常见的组件,主要涉及到的内容有:

  1. 基础样式构建。
  2. 点击动效设计。
  3. 参考市面上常见组件库的设计,根据type切换颜色。
  4. 设置禁用模式。

做之前先仔细看看UI设计稿

UI稿

解读一下都有哪些元素:

  • 按钮四周的边框带缺角,带点子像素的风格。
  • 默认状态下字体区域较亮,右下角阴影处较暗,凸显立体按钮效果。
  • 鼠标移入时亮区压缩,提醒用户聚焦于此处。
  • 鼠标按下时亮区往右下方移动,模拟立体按钮被按下。
  • 整体设计效果太对味儿了!

素材补充

禁用按钮时需要将手势图片替换为禁止图片,使用系统默认的会比较突兀,我这里做了一张图。
禁用
你只需要在iconfont上随便找个禁用图片,然后通过我另一篇文档介绍的方法处理即可。这张图我也补充到文档资源绑定了,在文档开头获取。
链接地址:【VUE3.0】如何得到一张像素风格的图片?

代码编写

按照设计稿解读内容:

1. 按钮四周边框

想象一下如何在一个dom元素四周加上一个带缺角的边框?

  • 最简单的方式是直接按钮设置边框,在按钮内部设置四个子元素,正方形白色底,定位到父元素的四个角盖上。
  • 上一个方法的变体是不设置按钮边框,将内部四个子元素设置成边框的宽高和底色,定位到四个边位置。
  • 有一个比较变态的玩法是在按钮上方覆盖一个黑色底的dom,利用clip-path抠出边框套在按钮上。(难度太大,且不好设置)
  • 本教程采用使用伪类的方式设置边框样式。

首先写好html部分,设置样式类和插槽:

  <button class="pButton">
    <slot>button</slot>
  </button>

设置button基础样式,包含基础的宽高、相对定位、字体样式及鼠标样式。字体和鼠标样式在先导篇配置过,从本文引言处跳转回看。

.pButton {
  height: 45px;
  padding: 0 15px;
  display: flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
  font-family: pixel_en, pixel_ch;
  font-weight: bold;
  letter-spacing: 1.5px;
  user-select: none;
  border: 0px;
  position: relative;
  cursor: var(--cursor_pointer);
}

利用before和after两个伪类,分别设置上下边框和左右边框,交叉叠加在按钮底部,上下和左右都超出按钮两倍的边框厚度,保证可以露出来。

.pButton::after {
  content: "";
  border-left: 4px solid #333;
  border-right: 4px solid #333;
  position: absolute;
  left: -4px;
  top: 0;
  width: calc(100% + 8px);
  height: 100%;
  box-sizing: border-box;
  z-index: -1;
}

.pButton::before {
  content: "";
  border-top: 4px solid #333;
  border-bottom: 4px solid #333;
  position: absolute;
  left: 0;
  top: -4px;
  width: 100%;
  box-sizing: border-box;
  height: calc(100% + 8px);
  z-index: -2;
}

看下这步的效果
边框
至此边框部分就完成了。

2. 默认状态下按钮颜色立体效果

想象一下如何实现这种立体效果?

  • 最简单的方式是按钮设置个暗一些的底色,内部套个子元素设置亮一些,把按钮的文字放在子元素内部。
  • 上一个方法的变体是仍然套一个子元素,子元素宽高小于按钮宽高,使用阴影将剩余空间补满,阴影设置暗色。(子元素宽高不好把握数值)
  • 最终采取按钮本体的向内阴影解决这个问题。

这里应用到两个点,一个是box-shadow的向内阴影,一个是hsl颜色模型。

  • 关于hsl可以参考:【CSS Tricks】在css中尝试一种新的颜色模型HSL,这里简单解释下为什么要使用hsl。因为按钮的亮部和暗部都属于同一个颜色,不同的亮度,如果采用rgb颜色模型,三个参数都要变,后续有进一步的颜色变化则还需要新查询三个rgb参数,处理起来比较复杂。如果使用hsl颜色模型,则只需要调整最后一个参数亮度加减,即可得到同色系不同亮度的色值。
  • box-shadow需要注意内部阴影的左右偏移值,以及为了避免过度锐利,将模糊和传播距离设置为1px,优化效果。
.pButton {
 /* 其他样式规则 */
  color: #fff;
  /* 背景色 */
  background: hsl(204, 86%, 56%);
  box-shadow: inset -4px -4px 1px 1px hsl(204, 86%, 42%);
}

看下这步的效果
按钮

3. 鼠标移入聚焦

这步主要是利用css的hover选择器,鼠标移入时,将阴影偏移加大2px,同时将高亮区域亮度稍微调暗一点,表示聚焦。

.pButton:hover {
  background: hsl(204, 86%, 51%);
  box-shadow: inset -6px -6px 1px 1px hsl(204, 86%, 42%);
}

4. 模拟鼠标点击效果

这步主要是利用css的active选择器,在鼠标点击按钮时触发,将box-shadow偏移值取反,移动至右下角,模拟按下操作。

.pButton:active{
  background: hsl(204, 86%, 51%);
  box-shadow: inset 6px 6px 1px 1px hsl(204, 86%, 42%);
}

看下3、4步后的效果
按钮测试
至此按钮的基本雏形就完成了。

组件封装

1. 按类型设置颜色

  • 根据市面上常见的UI组件库,按钮的type一般分为primary、success、warning、info、error和default这几种,分别对应不同的颜色。我们需要在组件内部接收一个参数type,根据类型对按钮颜色做改变。
  • 先提取不同类型下颜色的hsl值,利用到vue3的css变量,去动态设置颜色。

js部分

import { ref} from "vue";
const props = defineProps({
  type: {
    type: String,
    default: "",
  },
});
// 颜色处理
let hue = ref("0");
let saturation = ref("0%");
let light = ref("100%");
switch (props.type) {
  case "primary":
    hue.value = "204";
    saturation.value = "86%";
    light.value = "53%";
    break;
  case "success":
    hue.value = "85";
    saturation.value = "58%";
    light.value = "53%";
    break;
  case "error":
    hue.value = "10";
    saturation.value = "75%";
    light.value = "62%";
    break;
  case "warning":
    hue.value = "51";
    saturation.value = "93%";
    light.value = "54%";
    break;
  case "info":
    hue.value = "0";
    saturation.value = "0%";
    light.value = "83%";
    break;
  default:
    hue.value = "0";
    saturation.value = "0%";
    light.value = "100%";
    break;
}

css部分:亮度变化通过calc进行计算后赋值。

.pButton {
/* 设置hsl参数变量 */
  --btn_hue: v-bind(hue);
  --btn_saturation: v-bind(saturation);
  --btn_light: v-bind(light);
  /* 替换变量 */
  background: hsl(
    var(--btn_hue),
    var(--btn_saturation),
    calc(var(--btn_light) + 3%)
  );
  box-shadow: inset -4px -4px 1px 1px
    hsl(var(--btn_hue), var(--btn_saturation), calc(var(--btn_light) - 11%));
}

2. 设置按钮禁用状态

通过接收disabled的Boolean变量控制按钮禁用状态。

  • 因为我们对button的样式做了重写,所以在button设置了disabled属性后默认的禁用样式失效,需要在button上设置动态class编写禁用后的样式。
  • 对button设置disabled属性后,可以阻止按钮点击事件,达到禁用效果。

html部分

  <button class="pButton" :class="{ disabled }" :disabled="disabled">
    <slot>button</slot>
  </button>

js部分

const props = defineProps({
  disabled: {
    type: Boolean,
    default: false,
  },
});

css部分

  • 设置手部禁用样式。
  • 复写hover后的样式,和button默认状态保持一致,鼠标移入后不发生变化,达到禁用效果。
.disabled {
  opacity: 0.5;
  cursor: var(--cursor_disabled);
}
.disabled:hover {
  background: hsl(
    var(--btn_hue),
    var(--btn_saturation),
    calc(var(--btn_light) + 3%)
  );
  box-shadow: inset -4px -4px 1px 1px
    hsl(var(--btn_hue), var(--btn_saturation), calc(var(--btn_light) - 11%));
}

3. 处理一个bug

当颜色偏白了之后,字体颜色就不能为白色,不然看不清楚。这时候hsl模型的好处就体现出来了,hue、saturation、light三个参数,我们简单的判断light参数是否大于80%(自己摸索的值,可以根据实际情况调整),如果大于80%则颜色偏白,设置深色字体颜色,反之设置白色字体颜色。

js部分

const fontColor = ref(
  parseInt(light.value) >= 80 ? "hsl(210, 11%, 15%)" : "hsl(0, 0%, 100%)"
);

css部分

.pButton {
// 其他样式
 color: v-bind(fontColor);
 }

4. 看下整体组件效果

整体效果

5. 组件完整代码

<template>
  <button class="pButton" :class="{ disabled }" :disabled="disabled">
    <slot>button</slot>
  </button>
</template>

<script setup>
import { ref } from "vue";
const props = defineProps({
  type: {
    type: String,
    default: "",
  },
  disabled: {
    type: Boolean,
    default: false,
  },
});
// 颜色处理
let hue = ref("0");
let saturation = ref("0%");
let light = ref("100%");
switch (props.type) {
  case "primary":
    hue.value = "204";
    saturation.value = "86%";
    light.value = "53%";
    break;
  case "success":
    hue.value = "85";
    saturation.value = "58%";
    light.value = "53%";
    break;
  case "error":
    hue.value = "10";
    saturation.value = "75%";
    light.value = "62%";
    break;
  case "warning":
    hue.value = "51";
    saturation.value = "93%";
    light.value = "54%";
    break;
  case "info":
    hue.value = "0";
    saturation.value = "0%";
    light.value = "83%";
    break;
  default:
    hue.value = "0";
    saturation.value = "0%";
    light.value = "100%";
    break;
}
const fontColor = ref(
  parseInt(light.value) >= 80 ? "hsl(210, 11%, 15%)" : "hsl(0, 0%, 100%)"
);
</script>
<style scoped>
.pButton {
  --btn_hue: v-bind(hue);
  --btn_saturation: v-bind(saturation);
  --btn_light: v-bind(light);
  height: 45px;
  padding: 0 15px;
  display: flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
  font-family: pixel_en, pixel_ch;
  font-weight: bold;
  letter-spacing: 1.5px;
  color: v-bind(fontColor);
  user-select: none;
  background: hsl(
    var(--btn_hue),
    var(--btn_saturation),
    calc(var(--btn_light) + 3%)
  );
  border: 0px;
  position: relative;
  box-shadow: inset -4px -4px 1px 1px
    hsl(var(--btn_hue), var(--btn_saturation), calc(var(--btn_light) - 11%));
  cursor: var(--cursor_pointer);
}

.pButton:hover {
  background: hsl(
    var(--btn_hue),
    var(--btn_saturation),
    calc(var(--btn_light) - 2%)
  );
  box-shadow: inset -6px -6px 1px 1px
    hsl(var(--btn_hue), var(--btn_saturation), calc(var(--btn_light) - 11%));
}
.pButton:active {
  background: hsl(
    var(--btn_hue),
    var(--btn_saturation),
    calc(var(--btn_light) - 2%)
  );
  box-shadow: inset 6px 6px 1px 1px
    hsl(var(--btn_hue), var(--btn_saturation), calc(var(--btn_light) - 11%));
}
.pButton::after {
  content: "";
  border-left: 4px solid #333;
  border-right: 4px solid #333;
  position: absolute;
  left: -4px;
  top: 0;
  width: calc(100% + 8px);
  height: 100%;
  box-sizing: border-box;
  z-index: -1;
}

.pButton::before {
  content: "";
  border-top: 4px solid #333;
  border-bottom: 4px solid #333;
  position: absolute;
  left: 0;
  top: -4px;
  width: 100%;
  box-sizing: border-box;
  height: calc(100% + 8px);
  z-index: -2;
}
.disabled {
  opacity: 0.5;
  cursor: var(--cursor_disabled);
}
.disabled:hover {
  background: hsl(
    var(--btn_hue),
    var(--btn_saturation),
    calc(var(--btn_light) + 3%)
  );
  box-shadow: inset -4px -4px 1px 1px
    hsl(var(--btn_hue), var(--btn_saturation), calc(var(--btn_light) - 11%));
}
</style>

6. 组件调用方式

    <p-button type="primary">primary</p-button>
    <p-button type="success">success</p-button>
    <p-button type="error">error</p-button>
    <p-button type="warning">warning</p-button>
    <p-button type="info">info</p-button>
    <p-button>button</p-button>
    <p-button type="primary" disabled>primary</p-button>

总结

至此一个完整的button像素风按钮就开发完成了。开发过程中我也收获了许多:

  • 锻炼了一下对UI稿的拆解分析能力。
  • 结合自身技能储备,对每个步骤都能想到一些解决办法并选择合适的方案,巩固了一下技能。
  • 探索了一些新的东西,比如hsl颜色模型。为将来项目开发提供了新思路。
  • 更加深入理解了一下组件封装的逻辑。

有了第一个组件的样式模板的经验,后续的组件开发也有了参考依据,我想后边做起来会比较快一些。但愿

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue.js 展示一个树形结构的数据视图,并禁用其中默认选中的节点

功能描述 展示树形结构&#xff1a; 使用 Element UI 的 <el-tree> 组件展示树形结构数据。数据由 content 数组提供&#xff0c;树形结构包含了嵌套的节点及其子节点。 默认选中节点&#xff1a; 使用 defaultCheckedKeys 属性指定默认选中的节点。这些节点在树形结构渲…

求职Leetcode题目(11)

1.最长连续序列 解题思路: 方法一&#xff1a; • 首先对数组进行排序&#xff0c;这样我们可以直接比较相邻的元素是否连续。• 使用一个变量 cur_cnt 来记录当前的连续序列长度。• 遍历排序后的数组&#xff1a; 如果当前元素与前一个元素相等&#xff0c;则跳过&#xf…

Debian安装mysql遇到的问题解决及yum源配置

文章目录 一、安装mysql遇到的问题解决二、Debain系统mysql8.0的安装以及远程连接三、彻底卸载软件四、Python 操作 mysql五、debian软件源source.list文件格式说明1. 第一部分2. 第二部分3. 第三部分4. 第四部分5. 关于源的混用问题6. 按需修改自己的sources.list7. 更新软件包…

python爬虫案例——腾讯网新闻标题(异步加载网站数据抓取,post请求)(6)

文章目录 前言1、任务目标2、抓取流程2.1 分析网页2.2 编写代码2.3 思路分析前言 本篇案例主要讲解异步加载网站如何分析网页接口,以及如何观察post请求URL的参数,网站数据并不难抓取,主要是将要抓取的数据接口分析清楚,才能根据需求编写想要的代码。 1、任务目标 目标网…

LabVIEW提高开发效率技巧----使用LabVIEW工具

LabVIEW为开发者提供了多种工具和功能&#xff0c;不仅提高工作效率&#xff0c;还能确保项目的质量和可维护性。以下详细介绍几种关键工具&#xff0c;并结合实际案例说明它们的应用。 1. VI Analyzer&#xff1a;自动检查代码质量 VI Analyzer 是LabVIEW提供的一款强大的工…

Java — LeetCode 面试经典150题(一)

双指针 125.验证回文串 题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回…

验收测试:从需求到交付的全程把控!

在软件开发过程中&#xff0c;验收测试是一个至关重要的环节。它不仅是对软件质量的把关&#xff0c;也是对整个项目周期的全程把控。从需求分析到最终的软件交付&#xff0c;验收测试都需要严格进行&#xff0c;以确保软件能够符合预期的质量和性能要求。 一、需求分析阶段 在…

0-1开发自己的obsidian plugin DAY 1

官网教程有点mismatch&#xff0c;而且从0-100跨度较大&#xff0c;&#x1f4dd;记录一下自己的踩坑过程 首先&#xff0c;官网给的example里只有main.ts&#xff0c;需要自己编译成main.js 在视频教程&#xff08;https://www.youtube.com/watch?v9lA-jaMNS0k&#xff09;里…

K8S服务发布

一 、服务发布方式对比 二者主要区别在于&#xff1a; 1. 部署复杂性&#xff1a;传统的服务发布方式通常涉及手动配置 和管理服务器、网络设置、负载均衡等&#xff0c;过程相对复 杂且容易出错。相比之下&#xff0c;Kubernetes服务发布方式 通过使用容器编排和自动化部署工…

大数据新视界 --大数据大厂之 Reactjs 在大数据应用开发中的优势与实践

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

虚幻引擎的射线检测/射线追踪

射线检测在 FPS/TPS 游戏中被广泛应用 什么是射线检测? 两个点行成一条线 , 射线检测是从一个起始点发出一条到终点的射线 , 如果射线命中一个游戏对象&#xff0c;就可以获取到对象命中时的 位置、距离、角度、是否命中、骨骼 等非常多的信息 , 这些信息在射击游戏中至关重…

付费电表系统的通用功能和应用过程参考模型(上)

Generic functions and application process reference model for the Payment Metering System 付费电表系统的通用功能和应用过程参考模型 1. 参考模型 Reference model 1.1 在参考模型中的符号的说明 Legend of symbols used in the reference model 功能框 (function bo…

AWS 管理控制台

目录 控制台主页 AWS 账户信息 AWS 区域 AWS 服务选择器 AWS 搜索 AWS CloudShell AWS 控制面板小部件 控制台主页 注册新的 AWS 账户并登录后&#xff0c;您将看到控制台控制面板。这是与各种 AWS 服务以及其他重要控制台组件进行交互的起点。控制面板由页面顶部的导航…

mqtt网关数据接入rabbitmq,缓存离线数据,实现消息保留

应用场景&#xff1a;网关将设备数据发布至mqtt服务器后&#xff0c;数采程序因为重启或者升级等原因&#xff0c;未能接到到离线的订阅消息&#xff0c;利用rabbitmq-mqtt可将离线数据缓存&#xff0c;待上线后接收 启用mqtt插件 rabbitmq-plugins enable rabbitmq_mqtt

成为谷歌开发者专家(GDE)的经历

大家好&#xff0c;我是张海龙(Jason)。经过一年多的准备&#xff0c;GDE申请 终于正式成功通过面试&#xff0c;成为了国内第一位Firebase GDE。下面对整个过程做个总结&#xff0c;希望对大家有所帮助。 1.什么是 GDE&#xff1f; Google Developers上面有详细的说明&#x…

Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】

Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】 目录 Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】 一、简单介绍 二、状态模式&#xff08;State Pattern&#xff09; 1、什么时候使用状态模式 2、使用状态模式的…

VCNet论文阅读笔记

VCNet论文阅读笔记 0、基本信息 信息细节英文题目VCNet and Functional Targeted Regularization For Learning Causal Effects of Continuous Treatments翻译VCNet和功能目标正则化用于学习连续处理的因果效应单位芝加哥大学年份2021论文链接[2103.07861] VCNet和功能定向正…

OpenCV_图像旋转超详细讲解

图像转置 transpose(src, dst); transpose()可以实现像素下标的x和y轴坐标进行对调&#xff1a;dst(i,j)src(j,i)&#xff0c;接口形式 transpose(InputArray src, // 输入图像OutputArray dst, // 输出 ) 图像翻转 flip(src, dst, 1); flip()函数可以实现对图像的水平翻转…

9.23 C++类中的特殊内容

仿照string类&#xff0c;实现myString //my_string.cpp #include "my_string.h" #include <iostream> #include <cstring>using namespace std;My_string::My_string():size(15){this->ptr new char[size];this->ptr[0] \0; //表示…

Qt_多元素控件

目录 1、认识多元素控件 2、QListWidget 2.1 使用QListWidget 3、QTableWidget 3.1 使用QListWidget 4、QTreeWidget 4.1 使用QTreeWidget 5、QGroupBox 5.1 使用QGroupBox 6、QTabWidget 6.1 使用QTabWidget 结语 前言&#xff1a; 在Qt中&#xff0c;控件之间…