vue封装轮播图组件
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue封装轮播图组件相关的知识,希望对你有一定的参考价值。
前言:心血来潮想做个最简单的轮播图组件练练手。
思路框架很简单,首先实现一个demo,再寻找该demo所有的可动态化的量。
文件夹结构
第一个难点是轮播图的css。我们采用这样的html
<div class="carousel">
<div class="photo">
<img v-for="idx in img_num" :src="`./img/${idx}.png`" />
</div>
</div>
我们希望通过控制.photo的margin-left来控制显示的图片。具体地,.photo的margin-left是-100%时显示第2张图片,以此类推。
首先.carousel应该设置overflow: hidden。记.carousel的宽度和高度分别为w和h,则.photo的宽度和高度应分别为img_num*w和h。
img的宽、高分别为w和h。因为img是行内元素,所以设置好宽高,直接堆叠即可,不需要flex布局等额外样式了。
完整css如下。index.css
body{
margin: 0;
}
.container{
width: 100%;
height: 100vh;
display: grid;
place-items: center;
}
.carousel{
overflow: hidden;
}
.photo{
animation: switch 10s infinite;
/*animation-direction: alternate;*//*左移后再右移*/
transition-timing-function: linear;
}
名为switch的keyframes不在css写死,而是用js生成。因为我们的目标是封装成组件,所以我们把这段代码放在mounted。
接下来的难点是,能否只用1行就生成控制动画的字符串数组难吗?)。经过一番踩坑,我们最终找到了这样的技巧:
let arr = [...Array(this.img_num)].map((v,idx) => getKthImg(idx))
数组Array(this.img_num)的若干元素都是empty,使用map函数不会被遍历到,因此我们用了es6的展开运算符(如果不支持es6则只能老老实实地for循环+push了QAQ),如此就能被map函数遍历到。
getKthImg用来生成类似这样的字符串:
20.0%,30.0%{
margin-left: -100%;
}
最后合并生成keyframes文本:
this.anime_switch = `@keyframes switch{
${arr.join('')}
}`
生成的keyframes文本:
@keyframes switch{
0.0%,10.0%{
margin-left: 0%;
}16.7%,26.7%{
margin-left: -100%;
}33.3%,43.3%{
margin-left: -200%;
}50.0%,60.0%{
margin-left: -300%;
}66.7%,76.7%{
margin-left: -400%;
}83.3%,93.3%{
margin-left: -500%;
}
}
文本生成了,接下来考虑怎么插入这个名为switch的keyframes。一开始采用的是document.styleSheets[2].insertRule这个函数,但是chrome浏览器有个什么bug,会报DOMException。因此我们换用了如下方法:向已有的style标签插入文本。
$("style").text($("style").text() + this.anime_switch)
完整js如下。index.js
"use strict";
function main(){
let vm = new Vue({
el: '#app',
mounted(){
let getKthImg = (x) => {
let val = (x * 100 / this.img_num)
return `${val.toFixed(1)}%,${(val + 10).toFixed(1)}%{
margin-left: ${-100 * x}%;
}`
}
let arr = [...Array(this.img_num)].map((v,idx) => getKthImg(idx))
this.anime_switch = `@keyframes switch{
${arr.join('')}
}`
$("style").text($("style").text() + this.anime_switch)
},
data(){
return {
img_num: 6,
w: '400px',
h: '300px',
anime_switch: ''
}
},
methods: {}
});
}
$(document).ready(main);
完整html如下。index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>轮播图</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" type="text/css" href = "./index.css" />
<style type="text/css"></style>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.5.2/vue.min.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<main id="app" class="container">
<div :style="{
width: `${w}`,
height: `${h}`
}" class="carousel">
<div :style="{
width: `calc(${img_num} * ${w})`,
height: `${h}`
}" class="photo">
<img :style="{
width: `${w}`,
height: `${h}`
}" v-for="idx in img_num" :src="`./img/${idx}.png`" />
</div>
</div>
</main>
<script src="./index.js"></script>
</body>
</html>
效果
(先咕着,等下补)
最后找到所有需要动态化的量。我们希望能给该组件指定图片的宽、高、图片数组(为了简化,改成了img_num)。
显然keyframes名称之间存在冲突问题,因此keyframes的名字要动态化,故设置anime_name变量。
于是组件所需所有参数:
props: {
anime_name: String,
img_num: Number,
w: String,
h: String
}
然后我选择了动态化.photo的动画总时间,即规定总时间为2*img_num。
animation: `switch${this.anime_name} ${2 * this.img_num}s infinite`,
因为没有使用vue-cli,所以生成组件的html模板略麻烦。vue官方文档指出有个插件可以用,用法类似react的jsx。但我们还是勉强用着render函数。render函数有1个参数:createElement。createElement是一个函数,它有3个参数。第一个参数是根的标签名,第二个参数是根的属性(一个对象),比如样式、类名。第三个参数是子节点数组。第二个参数的格式类似:
{
attrs: {
class: 'carousel'
},
style: {
width: this.w,
height: this.h
}
}
style不要错写成styles,否则它不报错,又没展示样式,调死人……
mounted函数无需变化。
下面给出完整代码。
component_ver.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>轮播图——组件版本</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" type="text/css" href = "./component_ver.css" />
<style type="text/css"></style>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.5.2/vue.min.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<main id="app" class="container">
<carousel :anime_name="2333" :img_num="5" :w="'500px'" :h="'400px'"></carousel>
<carousel :anime_name="2334" :img_num="6" :w="'400px'" :h="'300px'"></carousel>
<carousel :anime_name="2335" :img_num="4" :w="'400px'" :h="'300px'"></carousel>
</main>
<script src="./component_ver.js"></script>
</body>
</html>
component_ver.css
body{
margin: 0;
}
.container{
width: 100%;
height: 100vh;
display: grid;
place-items: center;
}
.carousel{
overflow: hidden;
}
component_ver.js
"use strict";
function main(){
Vue.component('carousel',{
props: {
anime_name: String,
img_num: Number,
w: String,
h: String
},
data(){
return {
anime_switch: ''
}
},
render(createElement){
let imgs = [...Array(this.img_num)].map((v,idx) => {
return createElement('img',{
attrs: {
src: `./img/${idx+1}.png`
},
style: {
width: this.w,
height: this.h
}
})
})
let photo_div = createElement('div',{
attrs: {
class: 'photo'
},
style: {
width: `calc(${this.img_num} * ${this.w})`,
height: this.h,
animation: `switch${this.anime_name} ${2 * this.img_num}s infinite`,
transitionTimingFunction: 'linear'
}
},imgs)
let carousel = createElement('div',{
attrs: {
class: 'carousel'
},
style: {
width: this.w,
height: this.h
}
},[
photo_div
])
return carousel
},
mounted(){
let getKthImg = (x) => {
let val = (x * 100 / this.img_num)
return `${val.toFixed(1)}%,${(val + 10).toFixed(1)}%{
margin-left: ${-100 * x}%;
}`
}
let arr = [...Array(this.img_num)].map((v,idx) => getKthImg(idx))
this.anime_switch = `@keyframes switch${this.anime_name}{
${arr.join('')}
}\\n`
$("style").text($("style").text() + this.anime_switch)
},
methods: {}
})
let vm = new Vue({
el: '#app',
data(){
return {}
},
methods: {}
});
}
$(document).ready(main);
效果
(先咕着,等下补)
以上是关于vue封装轮播图组件的主要内容,如果未能解决你的问题,请参考以下文章