携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天
实现的功能
-
自动滚动,一个卡片一个卡片的滚动
-
无限滚动,无限循环滚动
-
左右箭头点击滚动,一个卡片一个卡片的滚动
-
鼠标拖拽滚动
-
下面的按钮点击切屏
6.鼠标移入停止滚动,移出继续滚动
依赖的第三方库
- vue.js
- jquery.js
布局
- html:左边切换箭头、中间卡片滚动区域(卡片和下面的切屏按钮)、右边切换箭头
<div id="app" class="fd-card-box" ref="cardBox">
<!-- 左边的三个箭头 可以不要 -->
<div class="fd-switch-box left" @click="gotToPrev">
<span class="fd-arrow fd-arrow1"></span>
<span class="fd-arrow fd-arrow2"></span>
<span class="fd-arrow fd-arrow3"></span>
</div>
<!-- 中间主要部分 -->
<!-- 需要移动的盒子 -->
<div class="fd-list-main">
<ul id="List"
ref="List"
class="fd-list-content">
<!-- 卡片 -->
<li v-for="(item,index) in cardList"
:key="item.code + index"
:class="['fd-list-item',
{'fd-left-model':index<startIndex-1},
{'fd-left-model':index===startIndex-1},
{'fd-center-model':index===startIndex||index===startIndex+1},
{'fd-right-model':index===startIndex+2},
{'fd-right-model':index>startIndex+2}
]"
@click.stop ="changeCenter(index)">
<p class="fd-title">{{item.name}}</p>
<div class="fd-content" :class="'fd-code-'+item.code">内容{{index + 1}}</div>
</li>
</ul>
<!-- 翻页 -->
<ul class="fd-fy-list">
<li v-for="item in screens"
@click.stop="goToPage(item)"
:class="['fd-fy-list-li', activePage===item ? 'active' : '']"
:key="item">
</li>
</ul>
</div>
<!-- 右边的三个箭头 -->
<div class="fd-switch-box right" @click="goToNext">
<span class="fd-arrow fd-arrow1"></span>
<span class="fd-arrow fd-arrow2"></span>
<span class="fd-arrow fd-arrow3"></span>
</div>
</div>
- 样式
- 中间区域的外框需要设置视距
- 中间内容区域需要3d旋转
- 左右两边卡片需要旋转
- 左右两边的卡片样式使用样式实现,不必过多考虑
/* 主要样式 */
.fd-card-box {
display: flex;
}
- 左右两边箭头样式代码
/* 箭头 */
.fd-switch-box {
position: relative;
width: 250px;
height: 170px;
cursor: pointer;
}
.fd-arrow {
position: absolute;
display: inline-block;
height: 100%;
vertical-align: top;
background-position: 0 43px;
background-repeat: no-repeat;
}
.fd-arrow3 {
right: -10px;
width: 88px;
background-image: url("../img/switchBtn/icon02.png");
}
.fd-arrow2 {
right: 70px;
width: 65px;
background-image: url("../img/switchBtn/icon03.png");
}
.fd-arrow1 {
right: 133px;
width: 54px;
background-image: url("../img/switchBtn/icon04.png");
background-position: 0 40px;
}
.left {
text-align: right;
}
.right {
text-align: left;
transform: rotateY(180deg);
}
.animate .fd-arrow {
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
.animate .fd-arrow3 {
animation-name: opacity3;
}
@keyframes opacity3 {
0% {
opacity: 1;
}
40% {
opacity: 0;
}
80% {
opacity: 1;
}
}
.animate .fd-arrow2 {
animation-name: opacity2;
}
@keyframes opacity2 {
20% {
opacity: 1;
}
50% {
opacity: 0;
}
90% {
opacity: 1;
}
}
.animate .fd-arrow1 {
animation-name: opacity1;
}
@keyframes opacity1 {
40% {
opacity: 1;
}
60% {
opacity: 0;
}
100% {
opacity: 1;
}
}
- 中间区域样式
/* 中间区域 */
.fd-list-main {
flex: 1;
height: 100%;
overflow: hidden;
font-size: 0;
white-space: nowrap;
/* 设置视距离为1000 */
perspective: 1000px;
user-select: none;
}
.fd-list-content {
position: relative;
left: 0;
width: 0;
height: 198px;
/* //设置旋转为3d旋转 */
transform-style: preserve-3d;
}
.fd-list-item {
display: inline-block;
margin: 0 6px;
padding-top: 20px;
width: 300px;
height: 100%;
overflow: hidden;
color: #fff;
font-size: 30px;
text-align: center;
vertical-align: top;
background: url("../img/card/card-bg.png") center no-repeat;
cursor: pointer;
}
.fd-left-model {
position: relative;
padding: 0 20px;
/* //设置y轴的旋转角度 */
transform: rotateY(336deg) rotateZ(2deg) scale(1.41, 1);
/* //设置旋转的轴心 */
transform-origin: right;
}
.fd-right-model {
position: relative;
padding: 0 20px;
/* //设置y轴的旋转角度 */
transform: rotateY(24deg) rotateZ(-2deg) scale(1.41, 1);
/* //设置旋转的轴心 */
transform-origin: left;
}
.fd-center-model {
position: relative;
width: 350px;
height: 100%;
background-image: url("../img/card/card-bg-big.png");
}
.fd-title {
color: #fbae03;
font-size: 16px;
font-weight: bold;
}
.fd-center-model .fd-title {
font-size: 18px;
}
.fd-right-model .fd-title,
.fd-left-model .fd-title {
padding-top: 24px;
}
.fd-fy-list {
height: 30px;
font-size: 0;
text-align: center;
}
.fd-fy-list-li {
display: inline-block;
margin: 14px 16px;
width: 64px;
height: 8px;
vertical-align: top;
border-radius: 4px;
background-color: #209cff;
cursor: pointer;
}
.fd-fy-list-li.active {
background: linear-gradient(to left, #209cff, #68e0cf);
}
功能实现的步骤
- 自动滚动
- 使用定时器setInterval;定义一个记录卡片位置的参数,根据定时器自动加1;然后执行滚动
- 无限滚动
- 无限滚动就需要不停的往后面追加元素(需要删除前面的元素,不然DOM就越来越多),这里有一个技巧就是执行动画使用animate;不然删除元素再移动元素的过程就会表现出来
- 左右箭头点击滚动;直接执行滚动方法就好了
- 鼠标拖拽滚动;根据鼠标移动的距离,判断是左移还是右移就可以了
- 下面的按钮点击切屏,这个将移动的距离设置为一屏的距离就可以了
(function () {
const vm = new Vue({
el:'#app',
data () {
return {
cardList:[{
name:'卡片1',
code:'card1'
},{
name:'卡片2',
code:'card2'
},{
name:'卡片3',
code:'card3'
},{
name:'卡片4',
code:'card4'
},{
name:'卡片5',
code:'card5'
},{
name:'卡片6',
code:'card6'
},{
name:'卡片7',
code:'card7'
},{
name:'卡片8',
code:'card8'
}],
// 最左侧显示的卡片
startIndex:1,
// 展示的是第几页
activePage:1,
// 一共几屏
screens:0,
// 定时器,每隔5秒切换一屏
timer:null
}
},
created() {
// 初始化一些数据
this.initData();
},
mounted () {
// 初始化定时器
this.initTime();
// 绑定鼠标移动事件
this.mouseMove();
},
methods: {
// 初始化一些数据
initData() {
// 数据的长度
this.cardLength = this.cardList.length;
// 每一个li的宽度
this.oneLiW = 348;
// 每屏展示4个
this.everyScreenNum = 4;
// 展示几屏
this.screens = Math.ceil(this.cardLength / this.everyScreenNum);
},
// 初始化定时器
initTime() {
this.$refs.cardBox.classList.add('animate');
const time = 3500;
this.timer = setInterval(() => {
this.goToNext();
}, time);
},
// 下布局移入事件
mouseoverBottom() {
clearInterval(this.timer);
this.$refs.cardBox.classList.remove('animate');
},
// 绑定鼠标移动事件
mouseMove() {
const _this = this;
const dom = this.$refs.List;
// 是否按下
let isDown = false;
// 横向移动的距离
let x = 0;
// 鼠标移动的距离
let nl = 0;
dom.onmousedown = function (e) {
isDown = true;
// 获取x坐标和y坐标
x = e.clientX;
nl = 0;
};
dom.onmousemove = function (e) {
if (isDown === false) {
return;
}
const nx = e.clientX;
nl = nx - x;
};
dom.onmouseup = function () {
isDown = false;
// 鼠标抬起的时候根据移动的正负数判断是向左移动还是向右移动
// 向右移动
if (nl>0) {
_this.gotToPrev();
} else if (nl<0) {
_this.goToNext();
}
};
},
gotToPrev(){
// 如果最左侧显示的图形不为第一个,直接翻滚
if (this.startIndex > 1) {
this.startIndex--;
} else {
// 如果显示的是第一个,把最后一个复制到前面
const lastItem = this.cardList.slice(this.cardLength - 1);
this.cardList.unshift(lastItem[0]);
// 设置bottomList的left值为-312
$('#List').css('left', `-${this.oneLiW}px`);
this.$nextTick(() => {
this.cardList = this.cardList.slice(0, this.cardLength);
});
}
this.scrollInto();
},
goToNext(){
this.startIndex++;
this.toNextLi();
},
goToPage(item){
const _index = (item - 1) * this.everyScreenNum + 1;
if (_index + this.everyScreenNum> this.cardLength) {
this.startIndex = this.cardLength - this.everyScreenNum + 1;
} else {
this.startIndex = _index;
}
this.toNextLi();
},
toNextLi(){
// 如果后面剩不到一屏
if (this.startIndex === (this.cardList.length - this.everyScreenNum + 1)) {
// 复制第一个到最后一个
const firstItem = this.cardList.slice(0, 1);
this.cardList.push(firstItem[0]);
// 设置left往前一点这样才有动画
const left = this.oneLiW * (this.startIndex - 3);
$('#List').css('left', `-${left}px`);
this.startIndex--;
this.$nextTick(() => {
this.cardList = this.cardList.slice(1, this.cardList.length);
});
}
this.scrollInto();
},
scrollInto() {
// 设置当前高亮的页码
this.activePage = Math.ceil((this.startIndex + 2) / this.everyScreenNum);
const dom = $('#List');
const left = this.oneLiW * (this.startIndex - 1);
dom.animate({left:`${-left}px`});
}
},
beforeDestroy() {
this.mouseoverBottom();
}
})
})()
总结
- 实现功能的能力就是将功能拆分的能力
- 需要无限滚动的都需要注意删除DOM元素的时候要将动画暂停
- 这里的视觉效果主要使用transform的3d效果(兼容性差)
- 移动的过程中只需要考虑当前展示的卡片就行,样式用css控制就好