vue项目开发时,要求一个类似手风琴的交互效果,但是自己并不擅长这一块,于是在github上搜索了一个相关的效果,最终找到一款比较符合的插件,再源插件的基础上,修改了一下,就可以用了。
源插件地址
使用到的插件
插件中使用到了@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>