Vue.js实战——ToggleSwitch组件介绍_8

一、背景

    1、项目中有个业务诉求:把多个可选项的开关控制放在一个页面中管理,方便用户自由组合选择。Android和IOS中有ToggleSwitch组件,而我们的项目是基于Html5的;

    2、查询开源组件的过程中,发现有vonicvuejs-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

猜你喜欢

转载自blog.csdn.net/dobuy/article/details/87967701
今日推荐