基于vue,编写多张图片滑动组件

619 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

需求

如何编写多张图片左右滑动的组件呢?效果如下所示:

动画.gif

首先容器的dom标签,习惯使用ul,里面每一张图片,则使用li,采用css3的transform: translate方式实现左右平移的效果

项目环境

本项目基于Vue2,但我这边使用jsx的方式实现Scroll组件,在项目下创建components文件夹,在该文件下,创建scroll文件夹,接着在scroll文件夹创建index.jsxstyle文件夹,目录结构如下图所示:

image.png

代码

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;
                }
            }
        }
    }
}