Element"/>
仿Element
目标:
- 实现一个轮播图组件
基本步骤:
- 开发组件。packages/slider/slider.vue
- 添加到packages/index.js,全局注册
- 在测试页面中使用组件
准备工作
-
添加组件
创建一个组件
<template><div class=''>我是一个轮播图组件</div> </template><script> export default {name: 'MySlider' } </script>
-
导出组件
packages/index.js
// semantic-ui样式 import 'semantic-ui-css/semantic.css'// 收集所有packages下面的组件,并按vue插件的格式做导出 import Button from './button/button.vue' import Slider from './slider/slider.vue'export default {install (Vue) {// 创建全局组件Vueponent('MyButton', Button)Vueponent('MySlider', Slider)} }
-
在测试项目中使用组件
(1)添加测试页面
内容
<template><div class="home"><h3>对轮播图组件进行测试</h3><!-- 2000表示 每隔2s自动播放下一张 --><my-slider style="width:250px;height:350px;":auto="2000":curIdx="curIdx":list="list"></my-slider></div>
</template><script>export default {name: 'SliderTest',data () {return {curIdx: 1, // 默认显示第二张图list: [ // 需要轮播的图片数据{url: ';quality=80&size=b9999_10000&sec=1603552928895&di=033a3b0900cb6094b581325f1b6a43ad&imgtype=0&src=http%3A%2F%2Fpic1.win4000%2Fpic%2Fe%2F0f%2F1b7e513753.jpg',alt: '离太阳最近的地方的一场春耕'},{url: ';quality=80&size=b9999_10000&sec=1603552961721&di=e0ff498eb7dbb671ea204454cf0ca58f&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun%2F2f6ae348b19c83fdc721ca5a54d4adb8d7455fa31dc76-GMqiCq_fw658',alt: '长江三峡水库'},{url: ';quality=80&size=b9999_10000&sec=1603553792310&di=db02409d236f067161d3a6d51439d3a1&imgtype=0&src=http%3A%2F%2Fpic1.win4000%2Fpic%2F1%2F74%2Fe433515514.jpg_195.jpg',alt: '月球方舟在格陵兰岛测试'},{url: ';quality=80&size=b9999_10000&sec=1603553028011&di=100b009e9f826b13b7d52b86a1947568&imgtype=0&src=http%3A%2F%2Fpic1.win4000%2Fpic%2F1%2F50%2F3b06419346.jpg',alt: '虎门大桥水域恢复通航'}]}
}
</script>
(2)添加路由
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'Vue.use(VueRouter)const routes = [{path: '/',name: 'Home',component: Home},{path: '/button',name: 'Button',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visitedponent: () => import(/* webpackChunkName: "about" */ '../views/Button.vue')},{path: '/slider',name: 'Slider',component: () => import('../views/SliderTest.vue')}
]
(3)添加路由导航
App.vue
<div id="app"><div id="nav"><router-link to="/">按钮</router-link> |<router-link to="/test-alert">警示框</router-link> |<router-link to="/my-dialog">弹出框</router-link> |<router-link to="/my-switch">开关</router-link> |<router-link to="/slider">轮播图</router-link> |<router-link to="/headline">标题组件</router-link></div><router-view/></div>
效果
slider组件的基本结构和样式
下面是一个结构和样式,修改slider.vue的内容如下:
<template><div class="slider"><div class="slider-content"><!-- 每一个slider-item表示一张图 --><div class="slider-item"><img src=".jpeg?x-bce-process=image/crop,x_0,y_0,w_899,h_490" alt="离太阳最近的地方的一场春耕" /></div></div><!-- 左右两个按钮 --><span class="btn btn_left"></span><span class="btn btn_right"></span><!-- 方案 --><div class="txt">离太阳最近的地方的一场春耕</div><!-- 指示条 --><ol class="indirector"><li></li><li class="current"></li><li></li></ol></div>
</template>
<style>
.slider .slider-content,
.slider img {width: 100%;height: 100%;
}
.slider {margin: 0 auto;border: 1px solid #ccc;position: relative;cursor: pointer;
}
.slider .slider-content {overflow: hidden;position: relative;
}
.slider .slider-content .slider-item {top: 0;left: 0;width:100%;height:100%;
}
.slider img {width: 100%;
}
.slider .btn,
.slider .txt,
.slider .indirector {position: absolute;
}
.slider .btn {cursor: pointer;width: 50px;height: 50px;border-radius: 50%;background-color: rgba(255, 255, 255, 0);top: 50%;transform: translateY(-50%);transition: background-color 0.2s;
}
.slider .btn:hover {background-color: rgba(255, 255, 255, 1);
}.slider .btn:before,
.slider .btn:after {content: "";height: 3px;width: 25px;background-color: #fff;position: absolute;left: 15px;top: 23px;transform: rotateZ(60deg);transform-origin: 0px center;transition: all 0.2s;
}
.slider .btn:after {transform: rotateZ(-60deg);
}
.slider .btn:hover:before {transform: rotateZ(45deg);background-color: red;
}
.slider .btn:hover:after {transform: rotateZ(-45deg);background-color: red;
}.slider .btn.btn_right:before,
.slider .btn.btn_right:after {transform-origin: right center;
}.slider .btn.btn_left {left: 20px;
}
.slider .btn.btn_right {right: 20px;
}.slider .txt {text-indent: 1em;line-height: 40px;background-color: rgba(0, 0, 0, 0.5);text-align: left;bottom: 0;left: 0;width: 100%;color: #fff;
}
.slider .indirector {bottom: 10px;right: 1em;margin: 0;
}
.slider .indirector li {display: inline-block;margin: 0 5px;height: 10px;width: 10px;border-radius: 50%;background-color: #fff;
}
.slider .indirector li {transition: transform 0.2s;
}
.slider .indirector .current {background-color: #369;transform: scale(1.2);
}
</style>
分析
功能
- 允许用户传入图片列表
- 左右按钮可以点击切换
- 指定从哪一张开始放映
- 自动播放功能(指定播放时间间隔,鼠标进入时,要停止自动播放)
- 指示条上 鼠标滑动 也能切换
测试用例
<template><div class="about"><h1>对轮播组件测试</h1><!--这里的style会在组件的根元素上生效- list:是图片列表数据,它是一个数组,保存要轮播图片信息- auto:2000. 开启自动播放。2000毫秒切换一张.- curIdx:初始播放第几张
--><my-sliderstyle="width:550px;height:350px;"auto='2000'curIdx='1':list="list"></my-slider></div>
</template>
-
list:是图片列表数据,它是一个数组,保存要轮播图片信息
-
auto:2000. 开启自动播放。2000毫秒切换一张.
-
curIdx:初始播放第几张
基本实现
目标:
- 从用prop传入三项:
- 图片列表
- 当前索引
- auto
思路:
- 用v-for循环生成图片列表: 只显示当前项,其它隐藏
- 显示当前图片的文案
- 用v-for循环生成指示条,只有当前项是高亮的
$attrs和props
在父组件中向子组件传递自定义属性时,如果:
- 在子组件内部有对应的props项, 则,数据会传入props。
- 在子组件中没有定义对应的props,则在组件内部可以通过 $attrs来查看
$attrs最终会显示在dom格式上
<div data-v-63407e8a=""
class="slider"
list="[object Object],[object Object],[object Object],[object Object]" abc="123"
style="width: 550px; height: 350px;">
组件代码
- 通过循环生成轮播图的每一项,根据下标决定显示与否
- 根据list及传入的下标,取出当前的方案
- 通过循环生成指示条的每一项,根据下标决定显示高亮
<template><div class="slider"><div class="slider-content"><!-- 每一个slider-item表示一张图 --><div class="slider-item"v-for="(item, idx) in list":key="idx"v-show="curIdx==idx"><img :src="item.url" :alt="item.alt" /></div></div><!-- 左右两个按钮 --><span class="btn btn_left"></span><span class="btn btn_right"></span><!-- 文案 --><div class="txt">{{list[curIdx].alt}}</div><!-- 指示条 --><ol class="indirector"><li v-for="(item, idx) in list":key="idx":class="{'current': curIdx==idx}"></li></ol></div>
</template>
<script>
export default {name: 'MySlider',// 在父组件中向子组件传递自定义属性时,如果在子组件内部有对应的props项,// 则,数据会传入props。如果在子组件中没有定义对应的props,则在组件内部可以通过 $attrs来查看props: {list: {type: Array,required: true},curIdx: {type: [String, Number],default: 0 // 默认显示第一张},auto: {type: [String, Number], // "2000", 2000default: 0}}
}
</script>
给按钮添加点击事件
目标:让轮播图上的左右按钮产生点击切换效果。
思路:
- 修改当前项的下标
- 添加下标索值即可。要注意越界的情况。
步骤:
-
添加一个数据项:currentIndex。由于在切换上一张下一张时本质上是要修改索引值,而这个索引值是通过prop传入的,我们不能直接修改,所以这里补充一个currentIndex来保存传入的索引值。
data () {return {// 由于在子组件内,不允许直接赋值给props// 在这里,补充一个数据项,从curIdx中获取初始值currentIndex: this.curIdx}}
-
把模板中的原来对curIdx的引用,改成对currentIndex的引用。
-
在模板中对按钮添加点击事件,在事件中修改currentIndex的值。
模板
<template><div class="slider">....<!-- 左右两个按钮 -->
+ <span class="btn btn_left" @click="hPrev"></span>
+ <span class="btn btn_right" @click="hNext"></span>....</div>
</template>
代码
methods: {hNext () {// 下一张图// 1. 把下标向后移// 2. 判断是否越界this.currentIndex++if (this.currentIndex === this.list.length) {this.currentIndex = 0}},hPrev () {// 上一张图// 1. 把下标向前移// 2. 判断是否越界this.currentIndex--if (this.currentIndex === -1) {this.currentIndex = this.list.length - 1}// this.currentIndex = this.currentIndex === -1 ? this.list.length - 1 : this.currentIndex}}
添加自动播放功能
目标:
- 允许用户手动启动轮播功能
思路:
- 在组件上添加
auto
这个prop,它用来接收用户的输入,如果有值,则开启定时器 - 在创建组件时,如果有auto的值,就用setInterval来启动定时器。
组件代码:
补充一个钩子函数created
created () {// 如果用户设置了auto值,则表示开启自动播放功能// 每隔auto值,去调用一次:播放下一张if (this.auto) {setInterval(() => {this.hNext()}, this.auto)}}
补充一下对auto属性值的限制
props: {// ...auto: {type: [String, Number], // "2000", 2000default: 0,
+ validator: function (val) {if (typeof val === 'number') {return val >= 0}if (typeof val === 'string') {if (isNaN(val)) {console.log(val, '不是合法的数值')return false} else {return true}}}}},
改进轮播功能:hover暂停
目标:
-
当用户鼠标进入轮播图时,停止动画(clearInterval)
当用户鼠标移出轮播图时,再次启动动画
思路:
- 在组件中,给外层容器添加mouseenter,mouseleave事件。并在各自事件中删除或启动定时器。
<div class="slider"@mouseleave="hMouseLeave"@mouseenter="hMouseEnter">//...
</div>
代码
补充一个数据项,保存定时器
data () {return {// 由于在子组件内部,不允许修改从父组件中传入的props// 所以,这里补充定义一个数据项,从curIdx中获取初始值currentIdx: this.curIdx,
+ timer: null // 保存定时器 (它不是必须的,只是让大家看到值)}}
方法
created () {this.play()},methods: {play () {// 如果用户设置了auto值,则表示开启自动播放功能// 每隔auto值,去调用一次:播放下一张if (this.auto) {this.timer = setInterval(() => {this.hNext()}, this.auto)}},stop () {if (this.timer) {clearInterval(this.timer)}},// 下一张图hNext () {// 1. 把下标向后移// 2. 判断是否越界this.currentIndex++if (this.currentIndex === this.list.length) {this.currentIndex = 0}},hPrev () {// 上一张图// 1. 把下标向前移// 2. 判断是否越界this.currentIndex--if (this.currentIndex === -1) {this.currentIndex = this.list.length - 1}// this.currentIndex = this.currentIndex === -1 ? this.list.length - 1 : this.currentIndex},// 鼠标进入,如果自动播放的定时器,则要删除定时器,停止自动播放hMouseEnter () {this.stop()},// 鼠标离开,如果有自动播放,则要继续开启定时器,自动播放hMouseLeave () {this.play()}},beforeDestroy () {this.stop()}
指示条上鼠标滑动也能切换
思路:
- 给每个指示条上的 li添加mouseenter事件,调整当前图片索引值
组件的模块
<!-- 指示条 --><ol class="indirector"><!-- 只有是当前要显示的图,才会添加current类 --><li v-for="(item,idx) in list":key="idx":class="{current:idx===currentIdx}"
+ @mouseenter="currentIdx=idx"></li></ol>
emit事件
目标:
- 让用户能够监听
- 点击事件
- 切换事件
在组件内部的某个场合下,抛出来事件。
-
点击图片时
<template><div class="slider"@click="$emit('click',currentIndex)"@mouseleave="hMouseLeave"@mouseenter="hMouseEnter">
-
切换时
补充一个watch
watch: {currentIndex () {// console.log('当前的下标变了', this.currentIndex)this.$emit('slider', this.currentIndex)}}
测试代码
<div class="about"><h1>对轮播组件测试</h1><!--这里的style会在组件的根元素上生效- list:是图片列表数据,它是一个数组,保存要轮播图片信息- auto:2000. 开启自动播放。2000毫秒切换一张.- curIdx:初始播放第几张
--><my-sliderstyle="width:550px;height:350px;":list="list":curIdx='2'auto='2000'@click="hClick"@slider="hSlider"></my-slider></div>methods: {hClick (index) {alert(index)console.log('当前是', index, '被点击')},hSlider (idx) {this.curIdx = idxconsole.log(idx)}}
切换动画
列表动画transition-group组件:
格式:
<transition-group>列表项1,列表项2......
</transition-group>
属性值:
- name: 与
<transition>
组件中的name意义一样的,用于指定产生动画的那个特殊的类名。 - tag:要渲染成的dom类型.
补充css
.fade-enter, .fade-leave-to {opacity: 0.8;
}
.fade-enter-active, .fade-leave-active {transition: all 0.5s;
}
补充说明图片的地址问题
如果传入的图片是相对地址,则需要使用require()处理。
list: [{// 图片在本地,这里采用相对地址。// 结果不对。http://localhost:8080/1.jpeg。这样找不到。// url: './1.jpeg',url: require('./1.jpeg'),// http://localhost:8080/img/1.6034a006.jpegalt: '图片在本地,这里采用相对地址'},{url: '.jpeg?x-bce-process=image/crop,x_0,y_0,w_750,h_408',alt: 'babababa'}
]
e-leave-active {
transition: all 0.5s;
}
## 补充说明图片的地址问题如果传入的图片是相对地址,则需要使用require()处理。
list: [
{
// 图片在本地,这里采用相对地址。
// 结果不对。http://localhost:8080/1.jpeg。这样找不到。
// url: ‘./1.jpeg’,
url: require(’./1.jpeg’),// http://localhost:8080/img/1.6034a006.jpeg
alt: ‘图片在本地,这里采用相对地址’
},
{
url: ‘.jpeg?x-bce-process=image/crop,x_0,y_0,w_750,h_408’,
alt: ‘babababa’
}
]
更多推荐
仿Element
发布评论