「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
需求
如何编写多张图片左右滑动的组件呢?效果如下所示:
首先容器的dom标签,习惯使用ul,里面每一张图片,则使用li,采用css3的transform: translate方式实现左右平移的效果
项目环境
本项目基于Vue2,但我这边使用jsx的方式实现Scroll组件,在项目下创建components文件夹,在该文件下,创建scroll文件夹,接着在scroll文件夹创建index.jsx、style文件夹,目录结构如下图所示:
代码
index.jsx代码如下图所示
import { Icon } from 'ant-design-vue'
import classNames from 'classnames';
import PropTypes from '../_util/vue-types';
const prefixCls = 'sfd-scroll'
const Scroll = {
name: 'SScroll',
props: {
selectedId: PropTypes.any.def(0),
images: PropTypes.array.def([]),
thumbWidth: PropTypes.number.def(120)
},
data() {
return {
pos: 0,
curPage: 1, // 第几页
scrollIndex: 0
}
},
watch: {
selectedId: {
handler(val) {
if (val || val === 0) {
this.updateThumbs('switch', val)
}
}
},
},
methods: {
clickThumb(item) {
this.$emit('clickThumb', item)
},
handleLeft() {
this.updateThumbs('left')
},
handleRight() {
this.updateThumbs('right')
},
updateThumbs(type, selectedIndex) {
if (!this.$refs.scroll) {
return;
}
const totalLen = this.images.length
const scrollWidth = this.$refs.scroll.clientWidth;
const perNum = Math.floor(scrollWidth / this.thumbWidth)
const SPACE = 15 // 每个li的间隔为15px
let distance = 0
let endFlag = false
const handleRightScroll = () => {
if (this.curPage * perNum < totalLen) {
this.scrollIndex = this.curPage * perNum
distance = this.scrollIndex * this.thumbWidth + perNum * SPACE * this.curPage
this.curPage++;
} else {
endFlag = true
}
}
const handleLeftScroll = () => {
if (this.curPage > 1) {
this.scrollIndex = (this.curPage-2) * perNum
distance = this.scrollIndex * this.thumbWidth + perNum * SPACE * (this.curPage - 2)
this.curPage--;
} else {
endFlag = true
}
}
if (type === 'right') {
handleRightScroll()
} else if (type === 'left') {
handleLeftScroll()
} else if (type === 'switch') {
const flipPage = Math.floor(selectedIndex / perNum ) + 1
if (flipPage === this.curPage) return
if (flipPage < this.curPage) {
handleLeftScroll();
} else {
handleRightScroll()
}
}
if (endFlag) return;
this.pos = -distance;
}
},
render() {
const {
images,
pos,
selectedId
} = this
if(!images || images.length <= 0) {
return null
}
const liList = images.map(item => {
const { index, url, title } = item
return (
<li
key={index}
class={classNames({
[`${prefixCls}-container-mid-pic`]: true,
[`${prefixCls}-container-mid-pic-active`]: index === selectedId
})}
onClick={() => this.clickThumb(item)}
>
<img src={url}/>
{
title ?
<div title={title} class={prefixCls + '-container-mid-pic-title'}>{title}</div>
:
null
}
</li>
)
})
return (
<div class={prefixCls} onClick={(e) => e.stopPropagation()}>
<div class={prefixCls+'-container'}>
<div class={prefixCls+'-container-left'}>
<Icon
class={prefixCls+'-left-icon'}
type="left"
onClick={this.handleLeft}
></Icon>
</div>
<div class={prefixCls+'-container-mid'}>
<div class={prefixCls+'-container-mid-sec'} ref='scroll'>
<ul style={{transform: `translate(${pos}px, 0)` }}>{liList}</ul>
</div>
</div>
<div class={prefixCls+'-container-right'}>
<Icon
class={prefixCls+'-right-icon'}
type="right"
onClick={this.handleRight}
></Icon>
</div>
</div>
</div>
)
}
}
Scroll.install = function(Vue) {
Vue.component(Scroll.name, Scroll);
};
export default Scroll
其中import PropTypes from '../_util/vue-types'代码,大家可以参考ant design vue源码
Scroll中有install方法,这是为了使用Vue.use(Scroll)注册该组件
style文件夹下创建index.less文件,样式代码如下所示:
.sfd-scroll {
margin: auto;
background: fade(@black, 35);
&-container {
overflow: hidden;
display: flex;
height: 116px;
width: 100%;
white-space: nowrap;
transition: all 200ms ease-in-out;
&-left,
&-right {
position: relative;
width: 4%;
height: 100%;
.anticon {
.common-icon();
font-size: 30px !important;
&:hover {
color: #1a8cff;
}
}
}
&-mid {
width: 92%;
height: 100%;
padding: 18px 15px 0;
margin: auto;
overflow: hidden;
background: fade(@black, 50);
&-sec {
position: relative;
height: 100%;
overflow: hidden;
}
ul {
position: absolute;
left: 0;
width: 4200px;
transition: .3s ease-in-out;
}
&-pic {
position: relative;
float: left;
margin-right: 15px;
width: 120px;
height: 80px;
cursor: pointer;
outline: none;
&-active {
border: 3px solid #3284fb;
}
&-title {
position: absolute;
left: 0;
bottom: 0;
padding-left: 2px;
padding-right: 2px;
width: 100%;
height: 20px;
color: #fff;
text-align: center;
font-size: @font-size-sm;
background: fade(@black, 70);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
img {
object-fit: cover;
width: 100%;
height: 100%;
outline: none;
}
}
}
}
}