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封装轮播图组件的主要内容,如果未能解决你的问题,请参考以下文章

vue封装轮播图组件

为啥vue移动端轮播图的组件安装后没法使用?

vue3.0项目中实现手动封装轮播图

vue中的轮播图

uniapp小程序开发—— 组件封装之自定义轮播图

原生JS面向对象思想封装轮播图组件