vue实现类似横向手风琴的切换效果

vue yekong 1148℃

vue项目开发时,要求一个类似手风琴的交互效果,但是自己并不擅长这一块,于是在github上搜索了一个相关的效果,最终找到一款比较符合的插件,再源插件的基础上,修改了一下,就可以用了。
vue实现类似横向手风琴的切换效果

源插件地址

源插件地址

使用到的插件

插件中使用到了@tweenjs/tween.js,对这个插件不熟,但是从这款插件对组件动画的增强,可以学习一下,以后应用到其他项目的动画中。

修改后

使用

<v-grid-accordion
          :width="1200"
          :height="680"
          :columns="5"
          :gutter="6"
          :images="imgs"
          :duration="5000"/>

修改后vGridAccordion

<template>
  <div class="gridWrap" :style="{width:width+'px',height:height+'px'}">
    <div class="gridWrapItem"
         v-for="(img, index) in images" @mouseover="getActive(index)" :key="index">
      <GridImage :index="index" :active="active" :class="{mr0:(index+1)==images.length,mr32:(index+1)<images.length}"
                 :transform="transform(index)"
                 :source="img" :title="img.title" :desc="img.desc"></GridImage>
    </div>
  </div>
</template>

<script>
import GridImage from './gridImage'

export default {
  name: 'vGridAccordion',
  components: {
    GridImage,
  },
  props: {
    width: {
      type: Number,
      default: 1200,
    },
    height: {
      type: Number,
      default: 589,
    },
    columns: {
      type: Number,
      default: 1,
    },
    gutter: {
      type: Number,
      default: 1,
    },
    images: {
      type: Array,
      default: [],
    },
    duration: {
      type: Number,
      default: 0,
    }
  },
  data() {
    return {
      active: 0,
      interval: null,
      wRatio: 3.8,
      hRatio: 1,
      setTime: null,
    };
  },
  computed: {
    cols() {
      return this.columns;
    },
    rows() {
      return Math.ceil(this.images.length / this.columns);
    },
  },
  mounted() {
    this.active = 0;
  },
  methods: {
    matrix(num) {
      return [num % this.cols, Math.floor(num / this.cols)];
    },
    getActive(e) {
      console.log(e)
      var that = this;
      setTimeout(() => {
        this.active = e
        this.transform(e)
      }, 300)
    },
    transform(i) {
      const {width: oWidth, height: oHeight, gutter, cols, rows} = this;
      const matrixIndex = this.matrix(i);
      const matrixActive = this.matrix(this.active);
      let width = (oWidth - gutter * (cols - 1)) / cols;
      let height = (oHeight - gutter * (rows - 1)) / rows;
      let left = 0;
      let top = matrixIndex[1] * (height + gutter);

      let minWidth = 300;
      let maxWidth = width * this.wRatio;
      let minHeight = (oHeight - height * this.hRatio - gutter * (rows - 1)) / (rows - 1);
      let maxHeight = height * this.hRatio;
      if (this.active !== null && this.active !== undefined) {
        if (matrixIndex[0] > matrixActive[0]) {
          width = minWidth;
          left = (minWidth + gutter) * (matrixIndex[0] - 1) + maxWidth + gutter;
        } else if (matrixIndex[0] < matrixActive[0]) {
          width = minWidth;
          left = (minWidth + gutter) * matrixIndex[0];
        } else {
          width = maxWidth;
          left = (minWidth + gutter) * matrixIndex[0];
        }

        if (matrixIndex[1] > matrixActive[1]) {
          height = minHeight;
          top = (minHeight + gutter) * (matrixIndex[1] - 1) + maxHeight + gutter;
        } else if (matrixIndex[1] < matrixActive[1]) {
          height = minHeight;
          top = (minHeight + gutter) * matrixIndex[1];
        } else {
          height = maxHeight;
          top = (minHeight + gutter) * matrixIndex[1];
        }
      }
      return {
        top,
        width
      }
    },
  }
}
</script>

<style scoped lang="scss">
.gridWrap {
  width: 100%;
  height: 100%;
  position: relative;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
  overflow: hidden;
  margin-top: 96px;
}

.gridWrapItem {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
  cursor: pointer;
}
</style>

修改后gridImage

<template>
  <div class="img" :class="{imgActive:index!=active}" :style="imgStyle">
    <div class="ItemTitle">
      <span>{{ source.title }}</span>
      <div class="line"></div>
    </div>
    <p>
      {{ source.desc }}
    </p>
    <img :src="source.url" alt="">
    <div class="mask" v-if="index!=active"></div>
  </div>
</template>

<script>
import TWEEN from '@tweenjs/tween.js';

export default {
  name: 'girdImage',

  props: {
    source: {
      type: Object,
      default: '',
    },
    transform: {
      type: Object,
      default: {},
    },
    titles: {
      type: String,
      default() {
        return '';
      }
    },
    index: {
      type: Number,
      default() {
        return 0
      }
    },
    active: {
      type: Number,
      default() {
        return 0
      }
    },
    desc: {
      type: String,
      default() {
        return '';
      }
    },
  },
  data() {
    return {
      l: this.transform.left,
      t: this.transform.top,
      w: this.transform.width,
      h: this.transform.height,
      act: false,
      show: false,
    };
  },
  computed: {
    imgStyle() {
      return {
        left: `${this.l}px`,
        top: `${this.t}px`,
        width: `${this.w}px`,
        height: `${this.h}px`,
      }
    }
  },
  watch: {
    transform: {
      handler(n, o) {
        this.trans(n, o);
      },
      deep: true,
    }
  },
  methods: {
    animate() {
      TWEEN.update();
      if (this.act) requestAnimationFrame(this.animate);
    },
    trans(newValue, oldValue) {
      this.act = true;
      let tween = new TWEEN.Tween(oldValue);
      tween.to(newValue, 380)
        .easing(TWEEN.Easing.Quadratic.InOut)
        .onUpdate(val => {
          this.l = val.left;
          this.t = val.top;
          this.w = val.width;
          this.h = val.height;
        })
        .onComplete(() => {
          this.act = false;
          this.show = this.showText;
        })
        .start();
      this.animate();
    }
  }
}
</script>

<style scoped lang="scss">
.img {
  width: 868px;
  height: 589px !important;
  background: #fff;
  overflow: hidden;
  position: relative;

  .ItemTitle {
    font-size: 28px;
    font-family: MicrosoftYaHei;
    color: #333333;
    margin-left: 32px;
    width: 170px;
    margin-top: 32px;
    position: relative;

    span {
      position: relative;
      z-index: 0;
    }

    .line {
      position: absolute;
      bottom: 0;
      background: #327DFF;
      box-shadow: 0px 0px 10px 0px rgba(216, 216, 216, 0.5);
      border-radius: 10px;
      opacity: 0.3;
      width: 100%;
      height: 12px;
      z-index: 1;
    }
  }

  p {
    font-size: 18px;
    font-family: MicrosoftYaHei;
    color: #333333;
    line-height: 36px;
    width: 804px;
    margin-left: 32px;
    margin-top: 24px;
  }

  img {
    width: 804px;
    height: 380px;
    margin-left: 32px;
    margin-top: 12px;
  }
}

.mask {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(29deg, rgba(33, 88, 216, 0.35) 0%, rgba(33, 88, 216, 0.92) 100%);
  box-shadow: 0px 0px 10px 0px rgba(153, 153, 153, 0.16);
  border-radius: 4px;
  opacity: 0.97;
  width: 100%;
  height: 100%;
  z-index: 1;
}

.img.imgActive {
  .ItemTitle {
    color: #fff;
    z-index: 10;
    position: relative;

    span {
      z-index: 2;
    }
  }

  p {
    color: #fff;
    z-index: 10;
    position: relative;
  }
}
</style>

喜欢 (0)