vue用了有一段时间了,开发的后台管理系统也趋于完善,现在时间比较算是有点空闲吧!这个空闲时间我在研究vue的另外的一些玩法,比如组件,插件等。今天,我就分享一个组件的练手项目--焦点图切换组件。这个项目是我用于vue组件练习的一个项目,当然了,代码也会提交到github(ec-slider),也会维护。我也想我开发的东西好用一点!现在,就是建议有需要的伙伴,可以来玩下这个项目,当练习的作用!另外,如果大家有什么建议,欢迎指点!
建议
1.下面的步骤,最好在自己本地上跑起来,根据文章的步骤,逐步完成,如果只看代码,很容易懵逼的。
2.如果不清楚哪个代码有什么作用,可能自己调试下,把代码去掉后,看下有什么影响,就很容易想出代码有什么作用了!
很普通,很好理解的一个目录,但还是简单的解释一下吧
:文件依赖模块(自动生成)
- node_modules
:打包文件产出目录(自动生成)
- dist
:开发文件目录
- src
:组件文件目录
- src/components
:babel编译es6的配置文件
- .babelrc
:不提交到git的文件(目录)的配置文件
- .gitnore
:设置rem算法的文件(现在没用到,忽略)
- fontSize
:模板文件
- index.html
:入口文件
- index.js
:配置文件
- package.json
:说明文档
- README.md
:webpack配置文件
- webpack.config.babel.js
这是项目的第一步(项目搭建这个,我不多说,之前的文章已经说了几次了!),现在
这里输出一个‘守候’1.首先,在
- src/components/ec-slider.vue
里面输出‘守候’,代码如下
- src/components/ec-slider.vue
- <template>
- <div>
- 守候
- </div>
- </template>
- <script type="text/javascript">
- export
- default {
- data() {
- return {
- }
- },
- computed: {
- },
- mounted() {
- },
- props: [],
- methods: {
- }
- }
- </script>
2.然后,在
里面设置注册组件(要带一个install方法),代码如下
- src/components/index.js
- import SlideImg from './ec-slider.vue'const ecslide = {
- install: function(Vue) {
- Vue.component('ec-slide', SlideImg)
- }
- }
- export
- default ecslide;
3.在入口文件,index.js里面引入并且使用组件
- require("./index.html");
- require("./src/sass/index.scss");
- import Vue from 'vue'
- //引入并且使用组件
- import ecslide from './src/js/components/index';
- Vue.use(ecslide);
- let app6 = new Vue({
- el: "#app6",
- data: {
- },
- mounted() {
- }
- });
4.在index.html(模板文件),输出组件
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <title>Title</title>
- </head>
- <body>
- <div id="app6">
- <ec-slide></ec-slide>
- </div>
- </body>
- </html>
5.命令行输入
跑起来,结果完美!这几步的原理貌似没什么可多说的,都是固定式的步骤。
- $ npm run dev
经过上一步之后,基础就已经打好了,那么接下来就是一个开发的过程,大部分都是修改
这个文件。开发之前,大家不要急着写代码,先分析下当中的运行流程!首先,一个焦点图切换,需要什么参数?根据下面的一个淘宝栗子,我简单分析下,就是下面这几个!
- src/components/ec-slider.vue
-图片列表[{src:'url',href:'www.baidu.com'},{src:'url',href:'www.163.com'}](src:图片的src,href:跳转连接,点击图片的时候)
- list
-是否自动播放 布尔 (默认false)
- autoplay
-轮播方式‘transparent’(透明度切换), 'slide'(滑动切换) (默认slide)
- type
-对应切换 (默认false,不显示)
- option
-轮播间隔时间,毫秒 (默认4000)
- time
-过渡效果 (默认'ease'慢速开始,然后变快,然后慢速结束的过渡效果,参考:transition-timing-function)
- sildetype
-箭头图片链接
- arrowurl
-箭头尺寸‘width,height’
- arrowsize
-切换方向'left'(左右) 'top'(上下) (默认:左右)
- direction
分析完了之后,就知道暂时需要这么多参数,那么接下来就是在
里面,接收这些参数。父子组件传参方式,我想大家知道--
- ec-slider.vue
。代码如下
- props
- <template>
- <div>
- 守候
- </div>
- </template>
- <script type="text/javascript">
- export
- default {
- data() {
- return {
- }
- },
- computed: {
- },
- mounted() {
- },
- props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl', 'arrowsize', 'option', 'direction'],
- methods: {
- }
- }
- </script>
有地方接收参数,肯定要有地方传参数,就是
模板文件里面传
- index.html
- <div class="slider-left">
- <ec-slide :list='list' :autoplay="true" :type="'slide'" :option="true" :time="4000" :sildetype="'ease'" :arrowurl="'http://i1.buimg.com/1949/4d860a3067fab23b.jpg'" :arrowsize="'20,40'" :direction="'left'"></ec-slide>
- </div>
既然知道了,会接收什么参数,那下面先把样式布局,给弄好先,这个不多说,代码如下!(有些解释我也是直接打到代码上)
- <template>
- <div class="switch-img-box" id="ec-slide-box">
- <div class="switch-img-type switch-img-left">
- <ul :style="{'width':ulWidth,'transition-timing-function':slideChange}">
- <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
- <a :href="li.href?li.href:'javascript:;'">
- <img :src="li.src" class="slider-img"/>
- </a>
- </li>
- </ul>
- </div>
- <!--如果需要显示对应的点-->
- <div class="switch-option" v-if="option">
- <div>
- <span v-for="(li,index) in list"></span>
- </div>
- </div>
- <!--如果需要显示箭头-->
- <div class="switch-arrow" v-if="arrowurl&&arrowsize">
- <div :class="{'arrow-left':direction==='left','arrow-top':direction==='top'}"
- :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"></div>
- <div :class="{'arrow-right':direction==='left','arrow-bottom':direction==='top'}"
- :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"></div>
- </div>
- </div>
- </template>
- <script type="text/javascript">
- export default {
- data () {
- return {
- slideChange: '',
- arrowWidth: '',
- arrowHeight: '',
- }
- },
- computed: {
- //ul宽度
- ulWidth: function () {
- return (this.list.length) + "00%";
- },
- //li宽度
- listWidth: function () {
- return 100 / (this.list.length)
- }
- },
- mounted(){
- //设置各个数据初始值
- this.slideChange = this.sildetype || 'ease';
- if (this.arrowsize && this.arrowurl) {
- this.arrowWidth = this.arrowsize.split(',')[0];
- this.arrowHeight = this.arrowsize.split(',')[1];
- }
- },
- props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl', 'arrowsize', 'option', 'direction'],
- methods: {
- }
- }
- </script>
- <style lang="scss">
- .ec-slide-img-box {
- width: 100%;
- height: 100%;
- position: relative;
- touch-action: none;
- }
- .ec-slide-img-type {
- position: relative;
- overflow: hidden;
- width: 100%;
- height: 100%;
- &.ec-slide-img-top {
- }
- &.ec-slide-img-left {
- li {
- display: inline-block;
- font-size: 0;
- }
- }
- &.ec-slide-img-transparent {
- li {
- opacity: 0;
- transition: opacity 1s;
- width: 0;
- &.cur {
- width: auto;
- }
- &.show {
- opacity: 1;
- }
- }
- }
- ul {
- font-size: 0;
- &.tran {
- transition: all .4s;
- }
- li {
- text-align: center;
- }
- img {
- vertical-align: middle;
- max-width: 100%;
- max-height: 100%;
- }
- }
- }
- .ec-slide-arrow {
- div {
- position: absolute;
- z-index: 2;
- margin: auto;
- top: 0;
- bottom: 0;
- right: 0;
- left: 0;
- opacity: .5;
- &:hover {
- opacity: 1;
- }
- &.arrow-left {
- left: 10px;
- right: auto;
- }
- &.arrow-right {
- right: 10px;
- left: auto;
- transform: rotate(180deg);
- }
- &.arrow-top {
- top: 10px;
- bottom: auto;
- }
- &.arrow-bottom {
- bottom: 10px;
- top: auto;
- transform: rotate(180deg);
- }
- }
- }
- .ec-slide-option {
- position: absolute;
- font-size: 0;
- bottom: 10px;
- text-align: center;
- width: 100%;
- z-index: 5;
- &.isFirst {
- span:first-child {
- display: none;
- }
- }
- &.isLast {
- span:last-child {
- display: none;
- }
- }
- span {
- border-radius: 100%;
- margin: 0 5px;
- background: #fff;
- display: inline-block;
- width: 10px;
- height: 10px;
- &.active {
- background: #09f;
- }
- }
- &.ec-slide-option-top {
- display: table;
- width: 10px;
- height: 100%;
- top: 0;
- right: 10px;
- margin: auto;
- bottom: 0;
- span {
- margin: 5px 0;
- }
- div {
- display: table-cell;
- vertical-align: middle;
- }
- }
- }
- </style>
运行结果,就是下面这样
布局搞定了,下面就可以写动画,让轮播动起来!这里也需要增加几个变量,一个是nowIndex,记录当前索引。一个是timer定时器!
首先,我用
这个方式控制
- transform:translate3d()
的滑动。
- ul
- <ul :style="{'width':ulWidth,'transform':'translate3d(-'+(listWidth*(nowIndex))+'%,0,0)','transition-timing-function':slideChange,'transition': 'all .4s'}">
- <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
- <a :href="li.href?li.href:'javascript:;'">
- <img :src="li.src" class="slider-img" />
- </a>
- </li>
- </ul>
然后,根据nowIndex,设置对应点的class。
- <div class="switch-option" v-if="option">
- <div>
- <!--如果当前索引index等于nowIndex。则添加active这个class,点就会变成蓝色-->
- <span v-for="(li,index) in list" :class="{'active':index===nowIndex}"></span>
- </div>
- </div>
js代码如下
- <script type="text/javascript">
- export
- default {
- data() {
- return {
- nowIndex:
- 0,
- timer: null,
- slideChange: '',
- arrowWidth: '',
- arrowHeight: '',
- }
- },
- computed: {
- //ul宽度
- ulWidth: function() {
- return (this.list.length) + "00%";
- },
- //li宽度
- listWidth: function() {
- return 100 / (this.list.length)
- }
- },
- mounted() {
- //是否自动播放
- if (this.autoplay) {
- this.autoSwitch();
- }
- //设置初始值
- this.slideChange = this.sildetype || 'ease';
- if (this.arrowsize && this.arrowurl) {
- this.arrowWidth = this.arrowsize.split(',')[0];
- this.arrowHeight = this.arrowsize.split(',')[1];
- }
- },
- props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl', 'arrowsize', 'option', 'direction'],
- methods: {
- //滑动操作
- switchDo(reduce) {
- clearInterval(this.timer);
- //根据reduce判断this.nowIndex的增加或者减少!
- //如果是减少模式reduce=‘reduce’
- if (reduce === 'reduce') {
- //如果nowIndex等于0,已经是第一个了,就回到最后一个
- if (this.nowIndex === 0) {
- this.nowIndex = this.list.length - 1;
- } else {
- this.nowIndex--;
- }
- }
- //如果是增加模式reduce=undefined
- else {
- //如果nowIndex等于this.list.length-1,已经是最后一个了,就回到第一个
- if (this.nowIndex === this.list.length - 1) {
- this.nowIndex = 0;
- } else {
- this.nowIndex++;
- }
- }
- //如果需要自动播放
- if (this.autoplay) {
- this.autoSwitch();
- }
- },
- //自动播放函数
- autoSwitch() {
- let time = this.time || 4000;
- this.timer = setInterval(() = >{
- this.switchDo();
- },
- time);
- }
- }
- }
- </script>
到了这里,剩下的就只有点击两个箭头,执行相应动画,这个就相对简单,无非就是调用switchDo函数,唯一区别在于,点击左边的箭头,是减少模式,右边箭头的增加模式。代码如下,很好理解。
- <!--判断是否需要显示箭头-->
- <div class="switch-arrow" v-if="arrowurl&&arrowsize">
- <div :class="{'arrow-left':direction==='left','arrow-top':direction==='top'}"
- :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}" @click.stop="switchDo('reduce')"></div>
- <div :class="{'arrow-right':direction==='left','arrow-bottom':direction==='top'}"
- :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}" @click.stop="switchDo"></div>
- </div>
到了这里,对交互有强迫症的开发者就受不了了,到了最后一张,再点击右边箭头,就会出现下面的情况!
到了第一张,再点击左边箭头也是类似的情况,这样就很不好。理想情况是下面这样
要想做上面的效果,改的地方会比较多,先说下原理吧,到了最后一张,这个时候,再点击右边箭头,像淘宝那样,回到第一张。到了第一张,再点击左边箭头类似效果回到最后一张。那么最后的布局是这样
这样布局能实现效果,到了最后一张,这个时候,再点击右边箭头,像淘宝那样,回到第一张。就像下面
这个时候,就需要多做一步,滚动到这里的时候,瞬间拉回去。而且这个拉回去,要把ul的过渡效果
去掉,不然就会看到拉回去的过渡效果!同时要改变nowIndex。
- transition
1.首先,ul布局方面
- <div class="switch-img-type switch-img-left" v-if="type==='slide'&&direction==='left'">
- <!--用tran这个class控制ul是否含有过渡效果,样式已经写好-->
- <ul :style="{'width':ulWidth,'transform':'translate3d(-'+(listWidth*(nowIndex+1))+'%,0,0)','transition-timing-function':slideChange}"
- :class="{'tran':noLast}">
- <!--最后一张图片-->
- <li :style="{'width':listWidth+'%'}">
- <a :href="list[list.length-1].href?list[list.length-1].href:'javascript:;'">
- <img :src="list[list.length-1].src" class="slider-img" />
- </a>
- </li>
- <!--遍历出来的图片-->
- <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
- <a :href="li.href?li.href:'javascript:;'">
- <img :src="li.src" class="slider-img" />
- </a>
- </li>
- <!--第一张图片-->
- <li :style="{'width':listWidth+'%'}">
- <a :href="list[0].href?list[0].href:'javascript:;'">
- <img :src="list[0].src" class="slider-img" />
- </a>
- </li>
- </ul>
- </div>
2.然后,对应的点修改
- <!--isLast:隐藏最后一个span,isFirst隐藏第一个span-->
- <div class="switch-option" v-if="option"
- :class="{'isLast':nowIndex===list.length, 'isFirst':nowIndex===-1,'switch-option-top':direction==='top'}">
- <div>
- <span class="active span1" v-if="nowIndex===list.length"></span>
- <span v-for="(li,index) in list" :class="{'active':index===nowIndex}"></span>
- <span class="active span2" v-if="nowIndex===-1"></span>
- </div>
- </div>
这个可能会有点绕,我解释下,比如滚动最后一张了,再点击右边箭头,向右滑动到第一张的时候,如下图
这个时候又要把第一个点变成蓝色,但是对应点的索引和nowIndex对不上,这个时候用一个技巧。把第一个(.span1)点显示出来,然后把最后一个点隐藏。这样还是用户看到还是看到4个点在屏幕!等动画执行完了,拉回去第一张的时候。把.span1隐藏,正常显示对应的点!这个大家细想一下就知道了。到了第一张,再点击左边箭头类似效果回到最后一张也是相同的处理方式!
到这里,功能就基本完成了,下面给出这部分代码!
- <template>
- <div class="ec-slide-img-box" id="ec-slide-box">
- <div class="ec-slide-img-type ec-slide-img-left" v-if="type==='slide'&&direction==='left'">
- <!--用tran这个class控制ul是否含有过渡效果,样式已经写好-->
- <ul :style="{'width':ulWidth,'transform':'translate3d(-'+(listWidth*(nowIndex+1))+'%,0,0)','transition-timing-function':slideChange}"
- :class="{'tran':noLast}">
- <!--最后一张图片-->
- <li :style="{'width':listWidth+'%'}">
- <a :href="list[list.length-1].href?list[list.length-1].href:'javascript:;'">
- <img :src="list[list.length-1].src" class="slider-img"/>
- </a>
- </li>
- <!--遍历出来的图片-->
- <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
- <a :href="li.href?li.href:'javascript:;'">
- <img :src="li.src" class="slider-img"/>
- </a>
- </li>
- <!--第一张图片-->
- <li :style="{'width':listWidth+'%'}">
- <a :href="list[0].href?list[0].href:'javascript:;'">
- <img :src="list[0].src" class="slider-img"/>
- </a>
- </li>
- </ul>
- </div>
- <!--isLast:隐藏最后一个span,isFirst隐藏第一个span-->
- <div class="ec-slide-option" v-if="option"
- :class="{'isLast':nowIndex===list.length, 'isFirst':nowIndex===-1,'ec-slide-option-top':direction==='top'}">
- <div>
- <span class="active" v-if="nowIndex===list.length"></span>
- <span v-for="(li,index) in list" :class="{'active':index===nowIndex}"></span>
- <span class="active" v-if="nowIndex===-1"></span>
- </div>
- </div>
- <div class="ec-slide-arrow" v-if="arrowurl&&arrowsize">
- <div :class="{'arrow-left':direction==='left','arrow-top':direction==='top'}"
- :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"
- @click.stop="switchDo('reduce')"></div>
- <div :class="{'arrow-right':direction==='left','arrow-bottom':direction==='top'}"
- :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"
- @click.stop="switchDo"></div>
- </div>
- </div>
- </template>
- <script type="text/javascript">
- export default {
- data () {
- return {
- nowIndex: 0,
- noLast: true,
- timer: null,
- slideChange: '',
- arrowWidth: '',
- arrowHeight: ''
- }
- },
- computed: {
- ulWidth: function () {
- return (this.list.length + 2) + "00%";
- },
- listWidth: function () {
- return 100 / (this.list.length + 2)
- }
- },
- mounted(){
- if (this.autoplay) {
- this.autoSwitch();
- }
- this.slideChange = this.sildetype || 'ease';
- if (this.arrowsize && this.arrowurl) {
- this.arrowWidth = this.arrowsize.split(',')[0];
- this.arrowHeight = this.arrowsize.split(',')[1];
- }
- },
- props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl', 'arrowsize', 'option', 'direction'],
- methods: {
- //滑动操作
- switchDo(reduce){
- clearInterval(this.timer);
- //根据reduce判断this.nowIndex的增加或者减少!
- if (reduce === 'reduce') {
- if (this.nowIndex === 0) {
- //如果是滑动切换
- this.nowIndex--;
- //执行完了这次动画之后,去除过渡效果
- setTimeout(() => {
- this.nowIndex = this.list.length - 1;
- this.noLast = false;
- }, 400)
- }
- else {
- this.nowIndex--;
- }
- }
- else {
- this.nowIndex++;
- }
- if (this.nowIndex === this.list.length) {
- //执行完了这次动画之后,去除过渡效果
- setTimeout(() => {
- this.nowIndex = 0;
- this.noLast = false;
- }, 400)
- }
- //如果需要自动播放
- if (this.autoplay) {
- this.autoSwitch();
- }
- //如果是滑动切换,设置this.noLast,增加过渡效果
- this.noLast = true;
- },
- //自动播放函数
- autoSwitch(){
- let time = this.time || 4000;
- this.timer = setInterval(() => {
- this.switchDo();
- }, time);
- }
- }
- }
- </script>
- <style lang="scss">
- .ec-slide-img-box {
- width: 100%;
- height: 100%;
- position: relative;
- touch-action: none;
- }
- .ec-slide-img-type {
- position: relative;
- overflow: hidden;
- width: 100%;
- height: 100%;
- &.ec-slide-img-top {
- }
- &.ec-slide-img-left {
- li {
- display: inline-block;
- font-size: 0;
- }
- }
- &.ec-slide-img-transparent {
- li {
- opacity: 0;
- transition: opacity 1s;
- width: 0;
- &.cur {
- width: auto;
- }
- &.show {
- opacity: 1;
- }
- }
- }
- ul {
- font-size: 0;
- &.tran {
- transition: all .4s;
- }
- li {
- text-align: center;
- }
- img {
- vertical-align: middle;
- max-width: 100%;
- max-height: 100%;
- }
- }
- }
- .ec-slide-arrow {
- div {
- position: absolute;
- z-index: 2;
- margin: auto;
- top: 0;
- bottom: 0;
- right: 0;
- left: 0;
- opacity: .5;
- &:hover {
- opacity: 1;
- }
- &.arrow-left {
- left: 10px;
- right: auto;
- }
- &.arrow-right {
- right: 10px;
- left: auto;
- transform: rotate(180deg);
- }
- &.arrow-top {
- top: 10px;
- bottom: auto;
- }
- &.arrow-bottom {
- bottom: 10px;
- top: auto;
- transform: rotate(180deg);
- }
- }
- }
- .ec-slide-option {
- position: absolute;
- font-size: 0;
- bottom: 10px;
- text-align: center;
- width: 100%;
- z-index: 5;
- &.isFirst {
- span:first-child {
- display: none;
- }
- }
- &.isLast {
- span:last-child {
- display: none;
- }
- }
- span {
- border-radius: 100%;
- margin: 0 5px;
- background: #fff;
- display: inline-block;
- width: 10px;
- height: 10px;
- &.active {
- background: #09f;
- }
- }
- &.ec-slide-option-top {
- display: table;
- width: 10px;
- height: 100%;
- top: 0;
- right: 10px;
- margin: auto;
- bottom: 0;
- span {
- margin: 5px 0;
- }
- div {
- display: table-cell;
- vertical-align: middle;
- }
- }
- }
- </style>
码农怎么会满足于现状,只有一种切换方式,怎么行,所以我又完善了些:
1.一个透明度的切换方式。
2.当传进的list长度为1的时候只显示图片,不进行任何动画。
3.手机的左右滑动事件的处理(不规范处理)!虽然也是很少功能,但是我在日常开发可以满足!
4.增加上下轮播方式。
完整代码有点多,会导致篇幅过长,在这里不贴了,大家上github看吧!ec-slider
好了,今天的开发就到此为止了。起初这个项目我是打算当练手用的,但是后来在项目上使用了,虽然这个写得比较简单,但是效果还不错。现在情况还不是很好,以后有需要也会维护。目前来说,也是建议大家可以玩下这个项目,虽然文章有点长,但是直接看下,边动手写代码,边看文章,会发现。一下子就看完了!这个应该是不错的练手项目,可以熟悉使用vue开发组件!最后,如果大家觉得有哪里写错了,写得不好,欢迎指点!
来源: https://juejin.im/post/5a0c19c65188253edc7f75ec