console
let colors = ['red', 'blue', 'green', 'yellow', 'orange', 'pink', 'purple', 'skyblue']
colors.push(...colors)
Vue.component('card-slider', {
template: "#card-slider",
props: ['data', 'keys', 'sizes'],
data() {
return {
list: [1, 2, 3, 4, 5, 6, 7, 8,9,10,11,12,13,14,15,16],
active: 0,
o_sizes: {
width: 200,
height: 200
},
o_keys: {
cover: '',
array: ''
},
animating: false
}
},
computed: {
buttonBoxStyle() {
return {
width: this.o_sizes.width + 'px',
height: this.o_sizes.height + 'px',
zIndex: this.list.length + 1
}
}
},
methods: {
getOffset(index) {
if (index < this.active) {
return this.list.length + index - this.active;
} else {
return index - this.active;
}
},
getOpacity(index) {
let offset = this.getOffset(index);
let opacityOffset = 0.15;
if (offset > 4) {
return 0;
}
return 1 - opacityOffset * offset;
},
getZIndex(index) {
return this.list.length - this.getOffset(index) - 1;
},
getPosition(index) {
let offset = 10;
let _offset = this.getOffset(index);
return _offset * offset + 'px';
},
getTransform(index) {
let offset = this.getOffset(index);
let translateOffset = 8;
let scaleOffset = 0.05;
return `translateX(${offset * translateOffset}%) scale(${1 - offset * scaleOffset})`;
},
getCardStyle(index) {
let opacity = this.getOpacity(index);
let zIndex = this.getZIndex(index);
let left = this.getPosition(index);
return {
opacity,
zIndex,
background: colors[index],
width: this.o_sizes.width + 'px',
height: this.o_sizes.height + 'px',
transform: this.getTransform(index)
}
},
getData(src, keys) {
if (typeof keys === 'string' && src != undefined) {
let e = src;
keys = keys.split(/[\.\[\]]/).filter(key => {
return key.trim() != '';
}).map(key => {
return key.replace(/^['"]|["']$/g, '');
});
if (!keys.length) {
return;
}
for (let i = 0; i < keys.length; ++i) {
let key = keys[i];
let prop = e[key];
e = prop;
if (e === null || e === undefined) {
return;
}
}
return e;
}
},
selectiveCopy(target, source) {
if (!source) {
return;
}
for (let key in target) {
if (typeof source[key] == typeof target[key]) {
target[key] = source[key];
}
}
},
next() {
if (!this.animating) {
this.slideOut(this.active++, () => {
this.animating = false;
});
this.animating = true;
}
},
prev() {
if (!this.animating) {
this.slideIn(this.getPrevPos(this.active--), () => {
this.animating = false;
});
this.animating = true;
}
},
seek(index) {
let end = index - this.active;
let slides = this.$refs.cards;
let slideBox = this.$refs['card-box'];
if (end > 0) {
let container = slideBox.cloneNode(true);
container.replaceChildren();
container.style.zIndex = this.list.length - 1;
slideBox.after(container);
for (let i = this.active; i < this.active + end; ++i) {
let node = slides[i].cloneNode(true);
container.appendChild(node);
}
container.animate([
{
left: '-100%',
opacity: 1,
opacity: 1
}
], { duration: 200 }).onfinish = () => {
container.remove();
}
} else if (end < 0) {
let count = 0;
for (let i = this.active - 1; i >= this.active + end; --i) {
slides[i].animate([
{
left: '-200%',
zIndex: this.list.length + count,
opacity: 1
},
{
left: 0,
zIndex: this.list.length + count,
opacity: 1
}
],{duration: 300})
count++;
}
} else {
this.animating = false;
}
this.active = index;
},
slideIn(index, callback) {
this.$refs.cards[index].animate([
{
transform: 'translateX(-100%) scale(1)',
zIndex: this.list.length,
opacity: 1
},
{
transform: 'translateX(0%) scale(1)',
zIndex: this.list.length,
opacity: 1
}
], { duration: 150 }).onfinish = () => {
if (typeof callback === 'function') {
callback();
}
}
},
slideOut(index, callback) {
let slideBox = this.$refs['card-box'];
let slides = this.$refs.cards;
let elt = slides[index].cloneNode(true);
elt.animate([
{
transform: 'translateX(-100%) scale(1)',
zIndex: this.list.length,
opacity: 1
}
], { duration: 150 }).onfinish = () => {
if (typeof callback == 'function') {
callback();
}
elt.remove();
}
slideBox.appendChild(elt);
},
getNextPos(current) {
current++;
let max = this.list.length - 1;
if (current > max) {
current = 0;
}
return current;
},
getPrevPos(current) {
current--;
let max = this.list.length - 1;
if (current < 0) {
current = max;
}
return current;
}
},
watch: {
sizes: {
immediate: true,
handler() {
this.selectiveCopy(this.o_sizes, this.sizes);
}
},
keys: {
immediate: true,
handler() {
this.selectiveCopy(this.o_keys, this.keys);
}
},
active(newValue) {
let max = this.list.length - 1;
let min = 0;
if (newValue > max) {
this.active = min;
} else if (newValue < min) {
this.active = max;
}
}
}
});
let vm = new Vue({
el: "#root",
data: {
}
})
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=, initial-scale=">
<meta http-equiv="X-UA-Compatible" content="">
<title></title>
</head>
<body>
<div id="root">
<card-slider></card-slider>
</div>
<template id="card-slider">
<div class="card-slider">
<div class="card-box" ref="card-box">
<div class="card" ref="cards" :style="getCardStyle(index)" v-for="(item,index) in list" :key="index">{{item}}</div>
</div>
<div class="button-box" :style="buttonBoxStyle">
<div class="arrow-left" @click="prev"><</div>
<div class="arrow-right" @click="next">></div>
<div class="pagination">
<div @click="seek(index)" :class="[active == index ? 'active' : '']" v-for="(item,index) in list" :key="index"></div>
</div>
</div>
</div>
</template>
</body>
</html>
* {
margin: 0;
padding: 0;
}
#root {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #eee;
}
.card-slider {
margin-left: 100px;
position: relative;
width: calc(200px + 10px * 3);;
height: 200px;
overflow: hidden;
}
.card-slider .card-box {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.card-slider .button-box {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
}
.card-slider .button-box .arrow-left,
.card-slider .button-box .arrow-right {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 22px;
color: #eee;
font-family: "幼圆";
font-weight: 600;
cursor: pointer;
pointer-events: all;
user-select: none;
}
.card-slider .button-box .arrow-left {
left: 5px;
}
.card-slider .button-box .arrow-right {
right: 5px;
}
.card-slider .button-box .pagination {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10px;
pointer-events: all;
width: max-content;
}
.card-slider .button-box .pagination > div {
display: inline-block;
width: 6px;
height: 6px;
background: #fff;
border-radius: 3px;
margin: 0 3px;
cursor: pointer;
transition: width 0.2s;
}
.card-slider .button-box .pagination > div.active {
width: 20px;
}
.card-slider .card {
position: absolute;
left: 0;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
background: #fff;
transition: transform 150ms,opacity 150ms;
}