Element轮播图组件切换太单调?手把手带你重写切换效果

1,220 阅读2分钟

Video_2024-07-11_155021 00_00_00-00_00_30.gif

前言:

最近在逛山东博物馆网站的时候,发现该网站主页淡入淡出的轮播图非常的优雅,所以就想来复刻一下,也算是对组件进行了二次的封装和修改

工具准备:

Vue3+Element Plus走马灯组件

注意事项:

Element Plus的走马灯有以下几个属性需要注意下:

.el-carousel__item 所有轮播图元素的样式。

. is-active 样式来确定当前展示的元素 。

.is-animating 演示来实现过渡的间隔。

还有一个行内样式,组件内部是通过添加或修改行内样式实现切换的效果(这个比较重要)。

案例代码

HTML

<template>
    <div class="home-container">
        <div class="home-container_banner">
            <el-carousel height="100vh" @change="change" :interval="3000" :pause-on-hover="false">
                <el-carousel-item v-for=" item in bannerList" :key="item">
                    <div class="container-pic" :style="{ backgroundImage: `url(${getImage(item)})` }"></div>
                </el-carousel-item>
            </el-carousel>
        </div>
    </div>
</template>

JavaScript

这里的 getImage 是我自己封装的方法,因为背景图在本地所以需要用到绝对路径,所以就自己封装了一个路径拼接的方法,如果不想用 div + 背景图的话,也可以用 img 标签进行替换

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
const getImage = (imgUrl) => {
  return new URL(`../image/${imgUrl}`, import.meta.url).href
}
​
// 组件名称定义
defineOptions({
    name: 'MuseumDash'
});
​
// 轮播图图片列表
const bannerList = ref(['1.jpg', '2.jpg', '3.jpg', '4.jpg']);
​
// 幻灯片切换后的样式更改
const change = () => {
    nextTick(() => {
        const picItems = document.querySelectorAll('.el-carousel__item');
        picItems.forEach(item => {
            item.style.transform = 'scale(1)';
        });
        setTimeout(() => {
            const isActive = document.querySelector('.el-carousel__item.is-active');
            if (isActive) {
                isActive.style.transform = 'scale(1.08)';
            }
        }, 10);
    });
};
change()
// 使用 MutationObserver 监控并覆盖 translate 样式
const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
        if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
            mutation.target.style.transform = mutation.target.style.transform.replace(/translateX([^)]*)/, '');
        }
    });
});
​
// 监听页面加载和卸载事件
onMounted(() => {
    const items = document.querySelectorAll('.el-carousel__item');
    items.forEach(item => {
        observer.observe(item, {
            attributes: true,
            attributeFilter: ['style']
        });
    });
});
​
onBeforeUnmount(() => {
    observer.disconnect();
});
</script>

Css

<style lang="scss" scoped>
.home-container_banner {
    height: 100vh;
    width: 100vw;
    overflow: hidden;
    position: relative;
​
    .container-pic {
        height: 100%;
        width: 100%;
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
    }
}
​
.el-carousel__item {
    transition: opacity 0.5s linear, transform 2s ease-in-out !important;
    position: absolute !important;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0;
    z-index: 1;
}
​
.el-carousel__item.is-active {
    opacity: 1;
    z-index: 2;
}
</style>

解析:

  1. 修改 .el-carousel__item 样式,让元素本身实现放大的效果。这里使用 !important 进行加权,防止组件内部的样式文件对其进行覆盖。

  2. 通过 JS 控制覆盖掉组件自身逻辑添加的style(覆盖样式是实现功能重要的点之一),使用 nextTick 是为了保证先获取元素再进行逻辑执行,这里使用 setTimeout 是为了延迟执行放大效果,如果不使用的话,当页面刷新时幻灯片的第一个页面直接就会被放大,就没有逐渐放大的效果了。整体的逻辑是在 组件切换的时候执行的,为了让页面创建时就开始执行,要在 created 周期中执行一次,让第一张幻灯片实现动效。

    const change = () => {
        nextTick(() => {
            const picItems = document.querySelectorAll('.el-carousel__item');
            picItems.forEach(item => {
                item.style.transform = 'scale(1)';
            });
            setTimeout(() => {
                const isActive = document.querySelector('.el-carousel__item.is-active');
                if (isActive) {
                    isActive.style.transform = 'scale(1.08)';
                }
            }, 10);
        });
    };
    change()
    
  3. 通过 MutationObserver 监听 Dom 添加元素或属性事件,解决更改视口大小动效错乱的问题。因为 Element 组件会根据视口位置自动计算当前位置,计算完毕后会添加上 style 属性那么就会将我们修改后的 style 属性进行覆盖。(重要的优化逻辑)(这个方法不是很常用,大家可以去 MDN 查看对应的 Api)

    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                mutation.target.style.transform = mutation.target.style.transform.replace(/translateX([^)]*)/, '');
            }
        });
    });
    ​
    // 监听页面加载和卸载事件
    onMounted(() => {
        const items = document.querySelectorAll('.el-carousel__item');
        items.forEach(item => {
            observer.observe(item, {
                attributes: true,
                attributeFilter: ['style']
            });
        });
    });
    ​
    onBeforeUnmount(() => {
        observer.disconnect();
    });
    
  4. 在组件销毁前清除监听,防止内存泄漏