一、背景
1、项目中有个业务诉求:把多个可选项的开关控制放在一个页面中管理,方便用户自由组合选择。Android和IOS中有ToggleSwitch组件,而我们的项目是基于Html5的;
2、查询开源组件的过程中,发现有vonic和vuejs-toggle-switch2个组件基本满足要求,但是前者太大(组件库),后者只支持单个,且二者无法自由定义组件大小、颜色,所以想着自己开发一个支持多个开关按钮,同时又有一定的自由度的单一功能的ToggleSwitch组件。
二、目标
1、把上篇开源的ToggleSwitch组件的组件源码、碰到的问题记录下来,不少资料收集不易;
2、方便有兴趣的同学看到组件开发的全貌,欢迎交流,促使大家一起进步。
三、步骤
搞npmjs平台和github平台的差异,命令行下执行:npm i vue-multi-toggle-switch,可以看到本组件发布在npmjs上的源码。开源的流程细节参见上篇文档;本组件发布在github上的源码参见vue-toggle-switch。下面主要介绍vue-toggle-switch开发的中间过程及代码逻辑。
1、组件核心代码只在ToggleSwitch.vue文件内:
<template>
<div id="toggle-switch">
<div v-for="result in resultData" class="toggle-switch-item" :style="'width:'+myStyle.width+'px'">
<input
:id="['switch'+result.id]"
class="toggle-switch-checkbox"
type="checkbox"
@change="changeStatus(result.id,$event.target.checked)"
v-model="result.status"
>
<label
class="toggle-switch-label"
v-bind:for="['switch'+result.id]"
:style="'border: 2px solid '+myStyle.border"
>
<span
:id="['inner'+result.id]"
class="toggle-switch-inner"
:data-on="result.on"
:data-off="result.off"
:style="'height:'+myStyle.height+'px;line-height:'+myStyle.height+'px'"
></span>
<span
class="toggle-switch-switch"
:style="'width:'
+(myStyle.height-6)+'px;height:'
+(myStyle.height-6)+'px;top:50%;margin-top:-'
+(myStyle.height/2-1)+'px;border: 2px solid '
+myStyle.border"
></span>
</label>
</div>
</div>
</template>
<script>
export default {
name: "ToggleSwitch",
props: ["resultData", "curStyle"],
data() {
return {
myStyle: {
width: 70,
height: 30,
switch_on: "lawngreen",
switch_off: "darkgrey",
border: "#e6e6e6"
}
};
},
mounted: function() {
console.log("start to init toggle switch now.");
let self = this;
self.initInnerStyle();
},
methods: {
changeStatus(id, checked) {
console.log("start to change status:" + id + "," + checked);
this.$emit("changeSwitch", {
id: id,
checked: checked
});
},
initInnerStyle() {
let self = this;
//use customization toggle switch style
if (this.curStyle) {
if (this.curStyle.width !== undefined) {
this.myStyle.width = this.curStyle.width;
}
if (this.curStyle.height !== undefined) {
this.myStyle.height = this.curStyle.height;
}
if (this.curStyle.switch_on !== undefined) {
this.myStyle.switch_on = this.curStyle.switch_on;
}
if (this.curStyle.switch_off !== undefined) {
this.myStyle.switch_off = this.curStyle.switch_off;
}
if (this.curStyle.border !== undefined) {
this.myStyle.border = this.curStyle.border;
}
}
this.resultData.forEach(element => {
console.log("init inner color.");
let innerId = "inner" + element.id;
let inner = document.getElementById(innerId);
let className = inner.className;
//add css attributes to ":before" and ":after"
let rules = [
{
":before": {
"background-color": self.myStyle.switch_off,
height: self.myStyle.height + "px"
}
},
{
":after": {
"background-color": self.myStyle.switch_on,
height: self.myStyle.height + "px"
}
}
];
rules.forEach(rule => {
for (let key in rule) {
let attributes = rule[key];
let innerStyle = window.getComputedStyle(inner, key);
//innerRule:".toggle-switch-inner:after{background-color: red}"
let innerRule = "." + className;
innerRule += key + "{";
for (let attrName in attributes) {
let attrValue = attributes[attrName];
innerRule += attrName + ":" + attrValue + ";";
}
innerRule += "}";
//IE:document.styleSheets[0].addRule('.toggle-switch-inner::before','color: green');
document.styleSheets[0].insertRule(innerRule, 0);
}
});
});
}
}
};
</script>
<style scoped>
#toggle-switch {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
width: 100%;
font-size: 10px;
font-weight: bold;
}
.toggle-switch-item {
position: relative;
/* width: 90px; */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
margin: 2px;
}
.toggle-switch-checkbox {
display: none;
}
.toggle-switch-label {
display: block;
overflow: hidden;
/* border: 2px solid #e6e6e6; */
border-radius: 20px;
}
.toggle-switch-inner {
display: block;
width: 200%;
margin-left: -100%;
transition: margin 0.3s ease-in 0s;
}
.toggle-switch-inner:before,
.toggle-switch-inner:after {
display: block;
float: right;
width: 50%;
/* height: 30px; */
padding: 0;
/* line-height: 30px; */
color: white;
box-sizing: border-box;
}
.toggle-switch-inner:before {
content: attr(data-off);
padding-right: 10px;
/* background-color: #4c0; */
color: white;
display: flex;
justify-content: flex-end;
}
.toggle-switch-inner:after {
content: attr(data-on);
padding-left: 10px;
/* background-color: darkgrey; */
color: white;
display: flex;
justify-content: flex-start;
}
.toggle-switch-switch {
/* display: block; */
/* width: 30px;height: 30px; */
margin: 0px;
background: white;
position: absolute;
top: 0px;
bottom: 0px;
/* border: 2px solid #e6e6e6; */
border-radius: 20px;
transition: all 0.3s ease-in 0s;
display: flex;
justify-content: center;
align-items: center;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-inner {
margin-left: 0px;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch {
right: 0px;
}
</style>
1)提供2个外部参数:resultData/curStyle,下面是详细介绍:
resultData:数组类型,数组内每个元素表示一个开关,组件的操作和元素的id绑定,开关状态变化时,会回调父组件的changeSwitch方法,此时可以根据id知道是哪个按钮变化了,业务逻辑也由父组件决定;
curStyle:JSON对象,主要是自定义本组件的样式,目前提供了ToggleSwitch的宽度、高度、开时的颜色、关时的颜色、边框颜色自定义,也欢迎大家fork继续扩展。
2)当仅自定义其中一部分样式时,其它样式会使用默认值;
3)组件巧妙的使用了一个隐藏的checkbox组件来实现了ToggleSwitch开关,且是纯CSS样式打造,抛开每个元素上的:style属性,你会发现组件非常精巧。本文参考了这位大牛的代码设计:https://blog.csdn.net/u013347241/article/details/51915821。
4)开关会默认一个选中项,此处也巧妙的使用了resultData中每个元素的status字段,结合vue.js的v-model标签作为checkbox选中状态,使其进入时就可以绑定;
5)使用了Vue.js的@change语法糖,绑定了组件checkbox选中状态的变化,一旦状态变化,就会通过this.$emit调用父组件上绑定的changeSwitch方法回传回去;
6)为了支持组件颜色,大小的自定义,特地从组件中抽取了宽度、高度、颜色这些样式属性,放在单独的:style样式中,在Vue.js渲染组件时,会叠加div的class和:style属性;
7)修改CSS伪元素的样式比较麻烦,参见上述代码。注意:由于本人机器环境限制,暂时没法验证IE下是否可行,所以IE的修改方案只是写在注释中。
2、测试Demo示例代码:
<template>
<div id="toggle-switch-demo">
<toggle-switch :resultData="resultData" :curStyle="curStyle" @changeSwitch="change" v-if="isInit"></toggle-switch>
</div>
</template>
<script>
import ToggleSwitch from "vue-multi-toggle-switch";
import Vue from "vue";
Vue.use(ToggleSwitch);
export default {
name: "Demo",
data() {
return {
resultData: [],
curStyle: {},
isInit: false
};
},
mounted() {
console.log("start to init toggle switch components now.");
this.resultData = [
{
id: "123",
status: true,
on: "",
off: ""
},
{
id: "234",
status: false,
on: "ON",
off: "OFF"
},
{
id: "456",
status: true,
on: "开",
off: "关"
}
];
this.curStyle = { switch_on: "red" };
this.isInit = true;
},
methods: {
change(result) {
console.log("data changed:" + result.id + "," + result.checked);
}
}
};
</script>
1)demo加载组件的方式:
import ToggleSwitch from "vue-multi-toggle-switch";
import Vue from "vue";
Vue.use(ToggleSwitch);
2)ToggleSwitch组件使用:
<toggle-switch :resultData="resultData" :curStyle="curStyle" @changeSwitch="change" v-if="isInit"></toggle-switch>
3)效果展示:
四、总结
1、现在开源的组件确实不少,但是符合自己业务要求的不一定多,一定要从自己的业务出发,开发出既通用又简洁的组件;
2、网上有很多大牛贡献了各种技巧,但是现在最稀缺的就是综合别人优点的能力,学以致用最重要。
五、参考
[1]https://blog.csdn.net/u013347241/article/details/51915821
[2]https://blog.csdn.net/sandaray/article/details/78029763
[3]https://www.cnblogs.com/kain-wu/p/6610560.html
上一篇:Vue.js实战——开源ToggleSwitch组件_7 下一篇:Vue.js实战——微信拍照时页面会被刷新的BUG定位_9