vue3.0封装一个图标选择组件
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
基于vite2.0+vue3.0
项目写了一个图标选择器,项目引入对应的css字体文件就行,支持模糊搜索
项目的文件目录
1、IconPicker.vue
<template>
<div class="pp_picker" ref="myRef">
<div class="inp_box">
<i class="inp_icon" :class="iconText"></i>
<input
class="inp"
type="text"
v-model="iconText"
@focus="data.isShow = true"
placeholder="请选择图标"
/>
<i class="inp_close" v-if="iconText" @click="changeIcon()">×</i>
</div>
<div class="poper" :class="data.cTop" v-if="data.isShow">
<ul class="pp_list">
<li v-for="(item, index) in data.newIconList" :key="index">
<div
class="pp_box"
:class="{ active: iconText == item }"
@click="changeIcon(item, index)"
>
<i class="pp_name" :class="item" :title="item"></i>
</div>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { reactive, ref, toRefs } from "@vue/reactivity";
import initIcon from "@/utils/iconStyle.js";
import {
onBeforeMount,
onMounted,
watchEffect,
} from "@vue/runtime-core";
const props = defineProps({
icon: {
type: String,
default: "",
},
highColor: {
//图标选中状态高亮颜色
type: String,
default: "#409eff",
},
iconArr: {
//获取相对应图标的二维数组,例如[[".el-icon-"], [".fa", "fa"], [".icon-", "iconfont"]]
// 这里默认使用的是element-plus的图标,[[".el-icon-"]]
type: Array,
default: [[".el-icon-"]],
},
});
const myRef = ref(null); //绑定最外层的元素,用于计算位置
const iconText = ref(props.icon.trim()); //重新定义icon的值,input框的双向绑定
const data = reactive({
allIcon: [], //这个数组用于模糊查询
newIconList: [], //这个数组用于页面数据遍历
searchVal: "", //用于搜索
isShow: false, //显示或隐藏图标选择栏
cTop: "", //图标选择栏的位置(上面或下面)
});
// 初始化数据,获取css图标
const initFontIconData = () => {
// 这里有异步操作,不使用data.newIconList=res; 使用push(...res)追加到数组中。
for (const item of props.iconArr) {
initIcon(...item).then((res) => {
data.newIconList.push(...res); //用于遍历数据
data.allIcon.push(...res); //这个于模糊查询
});
}
};
const emit = defineEmits(["update:icon"], ["chooseIcon"]);
// 选择图标和清空input框的值
const changeIcon = (el = "", i) => {
iconText.value = el;
emit("update:icon"); //双向绑定,更新父组件的值
data.newIconList = data.allIcon;
// data.isShow = false; //关闭图标选择弹窗
};
// 关闭图标选择弹窗
const closeIcon = (e) => {
if (!myRef.value.contains(e.target)) {
data.isShow = false;
}
};
watchEffect(() => {
data.searchVal = iconText.value;
// 判断input框的值在不在数组里面,如果在,清空searchVal,不做模糊查询
if (data.allIcon.indexOf(iconText.value) > -1) {
data.searchVal = "";
emit("chooseIcon", iconText.value); //选中图标触发父组件事件
}
if (data.searchVal) {
// 正则的方式匹配数组有searchVal的数据来组成新的数组
const reg = new RegExp(data.searchVal);
let arr = [];
for (let i = 0; i < data.allIcon.length; i++) {
if (reg.test(data.allIcon[i])) {
arr.push(data.allIcon[i]);
}
}
data.newIconList = arr;
}
});
onBeforeMount(() => {
initFontIconData(); //初始化数据
});
onMounted(() => {
//点击空白关闭图标选择弹窗,调用closeIcon
document.addEventListener("click", closeIcon, false);
//图标选择的位置(在input的上面或者下面)
document.addEventListener("scroll", (e) => {
// scroll,wheel
// scroll 滚动条滚动,wheel 鼠标滚动。监听滚动条滚动会好一点
const dom = myRef.value;
// console.log(window.pageYOffset);// 滚动条滚动的距离
let winTop = dom.getBoundingClientRect().top; //div顶部到浏览器窗口顶部的距离
let winBot = window.innerHeight - dom.getBoundingClientRect().bottom; //div底部到浏览器窗口底部的距离
if (winBot > 315 || winBot < 0) {
//当div底部
data.cTop = "";
} else {
if (winTop > 315) {
data.cTop = "top";
} else {
data.cTop = "";
}
}
});
});
</script>
<style lang="scss" scoped>
.pp_picker {
display: block;
width: 100%;
position: relative;
.inp_box {
position: relative;
.inp {
width: 100%;
-webkit-appearance: none;
background-color: transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-sizing: border-box;
color: #606266;
display: inline-block;
height: 34px;
line-height: 34px;
outline: 0;
padding: 0 30px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:focus {
border-color: v-bind("props.highColor");
outline: 0;
}
}
& > i {
font-style: normal;
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 20px;
line-height: 20px;
width: 20px;
height: 20px;
text-align: center;
}
.inp_icon {
left: 5px;
z-index: -1;
font-size: 14px;
}
.inp_close {
display: none;
z-index: 10;
cursor: pointer;
right: 5px;
}
&:hover .inp_close,
.inp:focus + .inp_close {
display: block;
}
}
.poper {
position: absolute;
left: 0;
top: 100%;
width: 100%;
height: 300px;
z-index: 100;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
-webkit-box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
color: #606266;
font-size: 14px;
line-height: 1.4;
min-width: 150px;
padding: 12px;
margin-top: 15px;
&::before {
content: "";
width: 12px;
height: 12px;
box-sizing: border-box;
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -6px;
background: #fff;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
border: 1px solid #ebeef5;
border-bottom-color: transparent;
border-right-color: transparent;
}
&.top {
top: -315px;
margin-top: 0;
&::before {
top: auto;
bottom: -6px;
-webkit-transform: rotate(225deg);
transform: rotate(225deg);
}
}
}
}
.pp_list {
display: flex;
flex-wrap: wrap;
margin: 0 -6px;
max-height: 100%;
overflow: auto;
&::-webkit-scrollbar {
width: 6px;
border-radius: 4px;
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(144, 147, 153, 0.3);
opacity: 0.5;
-webkit-box-shadow: 0 0 1px 1px #ccc;
box-shadow: 0 0 1px 1px #ccc;
border-radius: 4px;
&:hover {
background-color: rgba(144, 147, 153, 0.5);
}
}
li {
width: 4%;
padding: 6px;
.pp_box {
display: block;
width: 100%;
padding-bottom: 80%;
position: relative;
border: 1px solid #ddd;
border-radius: 3px;
&:hover,
&.active {
color: v-bind("props.highColor");
border-color: v-bind("props.highColor");
}
i {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
}
}
.icon-name {
display: none;
}
}
}
@media screen and (max-width: 1400px) {
.pp_list li {
width: 5%;
}
}
@media screen and (max-width: 1100px) {
.pp_list li {
width: calc(100% / 17);
}
}
@media screen and (max-width: 960px) {
.pp_list li {
width: calc(100% / 15);
}
}
@media screen and (max-width: 768px) {
.pp_list li {
width: calc(100% / 12);
}
}
@media screen and (max-width: 600px) {
.pp_list li {
width: 50px;
}
}
</style>
2、iconStyle.js
封装获取css的方法,也可以直接写到上面的vue组件里面去,这里单独拿出来是以后可能还会用到这个方法,不想使用异步的话,可以直接改掉
/**
* @function 获取去css图标的class类名
* @param {string} prefix 区分字体图标的前缀,例如:.icon-
* @param {string} [fullIcon=''] 是否需要图标的class全名,例如:iconfont
* @returns {arr}
* @example initIcon(".icon-","iconfont").then((res)=>{console.log(res);})
*/
const initIcon = (prefix, fullIcon = '') => {
return new Promise((resolve, reject) => {
const regx = /\:before/; //用于正则匹配有伪类:before的class名
const styles = document.styleSheets;
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {
for (let j = 0; j < styles[i].cssRules.length; j++) {
let cText = styles[i].cssRules[j].selectorText;
// 判断是对应前缀开头、是否有::before伪类
if (
cText &&
cText.indexOf(prefix) === 0 &&
regx.test(cText)
) {
// 清空前面的.和后面的::before,例如.el-icon-info::before=>el-icon-info
// /\./gi 替换.为空。/\:\:before/gi 将::before去除。
// /\[data-v-(.+?)\]/gi
let classname = cText.replace(/\./gi, "").replace(/\:\:before/gi, "")
// 可能有fa-close,fa-close-o的这种情况
if (cText.indexOf(",") > 0) {
let m = classname.split(","), m2 = [];
if (fullIcon) {
for (let a = 0; a < m.length; a++) {
m2.push(fullIcon + ' ' + m[a].replace('.', "").trim())
}
} else {
m2 = m;
}
sheetsIconList.push(...m2);
} else {
if (fullIcon) {
classname = fullIcon + ' ' + classname
}
sheetsIconList.push(classname);
}
}
}
}
if (sheetsIconList.length > 0) {
// 得到数组后,先去重一下在将数组返回出去,避免有重复的class
sheetsIconList = [...new Set(sheetsIconList)]
resolve(sheetsIconList);
} else{
reject("未获取到值,请刷新重试");
}
});
}
// 导出方法
export default initIcon;
3、文件调用
<template>
<div style="height: 120vh; background: #eee"></div>
<div style="padding: 15px">
<IconPicker
v-model:icon="icon_name"
:iconArr="arr"
@chooseIcon="getIcon"
></IconPicker>
</div>
<div style="height: 120vh; background: #eee"></div>
</template>
<script setup>
import { ref, reactive } from "vue";
import IconPicker from "@/components/others/IconPicker.vue";
const arr = ref([[".el-icon-"], [".fa", "fa"], [".icon", "iconfont"]]);
// el-icon-menu
const icon_name = ref("el-icon-coffee");
const getIcon = (e) => {
console.log(e);
};
</script>
<style>
</style>
组件参数说明
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
icon | 图标的完整class名,例如:fa fa-th-list 、iconfont icon-menu ; 使用时建议使用 v-model:page 进行双向绑定 | String | 无 |
highColor | 图标选中后的高亮颜色 | String | 默认为#409eff |
iconArr | 获取相对应图标的二维数组,点击跳到详细说明. | Array | 默认为[[“.el-icon-”]] element-plus的图标 |
@chooseIcon | 图标选中后触发的事件,参数为图标选中后的class |
iconArr参数的详细介绍
首先在项目中引入自己需要的字体css文件,main.js引入、组件内引入、index.html引入等,
注意是全局引入
vue项目中按需引入会有[data-v-******]这样的标识,只能在当前组件内使用,
例如在main.js中引入
数组内容
图标区分是采用css字体样式的图标前缀
和伪类::before
来区分的,但需要注意的是,
以[".icon", "iconfont"]
这个为例,我在页面写了一个以.icon开头
的css样式,含有::before
伪类,这样同样也会被当成图标样式提取出来
//这种可能会被当成图标样式
.icon_div1 {
width: 100%;
position: relative;
&::before {
content: "";
position: absolute;
top:0;
left:0;
}
}
有问题欢迎大家评论、留言或者私信!
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献1条内容
所有评论(0)