纵向控制的横向滚动效果实现

1. 场景

通过鼠标上下滚动控制横向滚动,先看下面效果:

2. 具体实现

接下来看一下具体实现过程:

2.1 页面布局

定义一个基本的页面布局,用于包裹滚动内容。这个布局包括一个外部的 scroll-container 和内部的 v-scroll(实际进行横向滚动的区域,通过 CSS 变换将其旋转和位移,以模拟纵向滚动控制横向滚动的效果) 以及实际的内容容器 content。使用slot方便外部内容传入。

js 复制代码
<div v-size="size" class="scroll-container">
    <div class="v-scroll">
        <div class=" content">
            <slot></slot>
        </div>
    </div>
</div>

为了实时获取内容的宽高,写一个自定义指令进行获取。

2.2 获取元素的宽高自定义指令

使用了自定义指令 v-size 来动态获取 scroll-container 的宽高,返回宽高信息。

  • ResizeObserverStore 类:用于管理 ResizeObserver 实例,确保在组件卸载时能够正确移除监听。
  • useResizeObserverremoveResizeObserver 函数:分别用于添加和移除 ResizeObserver 实例。
js 复制代码
class ResizeObserverStore {
    observer = new Map()

    get(target){
        return this.observer.get(target) || []
    }

    set(target,observer){
        const observers = this.observer.get(target)
        if(observers){
            observers.push(observer)
        }else{
            this.observer.set(target,[observer])
        }
    }

    remove(target){
        const observers = this.observer.get(target)
        if(observers){
            observers.forEach(observer => {
                observer.disconnect()
            });
            this.observer.delete(target)
        }
    }
}

const resizeObserverStore = new ResizeObserverStore()


export const useResizeObserver = (target,callback)=>{
    const observer = new ResizeObserver(callback)
    observer.observe(target)
    resizeObserverStore.set(target,observer)
    return observer
}

export const removeResizeObserver = (target)=>{
    resizeObserverStore.remove(target)
}

指令代码:

js 复制代码
import { useResizeObserver, removeResizeObserver } from './resize'

export const vSize = {
    mounted(el, binding) {
        useResizeObserver(el, (entries) => {
            const { width, height } = entries[0].contentRect
            binding.value.width = width
            binding.value.height = height
        })
    },

    beforeUnmount(el) {
        removeResizeObserver(el)
    }
}

全局引入:

js 复制代码
import { vSize } from './directives'
app.directive('size', vSize)

局部引入,组件内引入即可:

js 复制代码
import { vSize } from './directives'

2.3 CSS 样式与变换

  1. 设置scroll-container容器宽高为百分之百
  2. 获取content中内容的宽高,外部容器设置固定宽高,此时展示为
  3. 将内容根据v-scroll定位和transfer旋转展示

针对css进行说明:

scss 复制代码
.v-scroll {
    --w: calc(v-bind(size.width) * 1px);
    --h: calc(v-bind(size.height) * 1px);
    width: var(--w);
    height: var(--h);
    position: relative;
    overflow: auto;
}

将元素内容设置滚动和宽高,效果如下:

进行左上角旋转,此时会看不见元素,需要向下平移高度

scss 复制代码
 transform-origin: left top ;
transform: translateY(var(--h)) rotate(-90deg);

变成:

此时将内容的进行旋转展示,去除滚动条

scss 复制代码
.v-scroll::-webkit-scrollbar {
    display: none;
}
.content {
    position: absolute;
    width: var(--w);
    height: var(--h);
    top: 0;
    left: var(--h);
    transform-origin: left top;
    transform: rotate(90deg);
}

完整css:

scss 复制代码
.scroll-container {
    width: 100%;
    height: 100%;
}

.v-scroll {
    --w: calc(v-bind(size.width) * 1px);
    --h: calc(v-bind(size.height) * 1px);
    width: var(--w);
    height: var(--h);
    position: relative;
    overflow: auto;
    transform-origin: left top ;
    transform: translateY(var(--h)) rotate(-90deg);
}

.v-scroll::-webkit-scrollbar {
    display: none;
}

.content {
    position: absolute;
    width: var(--w);
    height: var(--h);
    top: 0;
    left: var(--h);
    transform-origin: left top;
    transform: rotate(90deg);
}

最后使用:

ini 复制代码
<XScroll>
     <div style="display: flex;">
         <div v-for="item in imgList">
             <img :src="item" alt="" srcset=""/>
         </div>
     </div>
 </XScroll>
const imgList = ref([])

实现效果的关键点:

  • .v-scroll:通过 transform 属性将其旋转 -90deg 并向下位移其高度,这样原本的垂直滚动就变成了水平滚动。
  • .content:通过 transform 属性将其旋转 90deg,恢复内容的正常显示方向。同时,将其位置调整至 .v-scroll 的右侧(通过 left: var(--h)),匹配旋转后的布局。

完整XScroll组件代码:

vue 复制代码
<template>
    <div v-size="size" class="scroll-container">
        <div class="v-scroll">
            <div class=" content">
                <slot></slot>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const size = ref({
    width: 0,
    height: 0
})

</script>

<style scoped lang="scss">
.scroll-container {
    width: 100%;
    height: 100%;
}

.v-scroll {
    --w: calc(v-bind(size.width) * 1px);
    --h: calc(v-bind(size.height) * 1px);
    width: var(--w);
    height: var(--h);
    position: relative;
    overflow: auto;
    transform-origin: left top ;
    transform: translateY(var(--h)) rotate(-90deg);
}

.v-scroll::-webkit-scrollbar {
    display: none;
}

.content {
    position: absolute;
    width: var(--w);
    height: var(--h);
    top: 0;
    left: var(--h);
    transform-origin: left top;
    transform: rotate(90deg);
}
</style>

3. 总结

最后总结一下: 实现一个通过鼠标上下滚动控制横向滚动的功能,关键点在于:

  1. 动态获取尺寸 :使用自定义指令 v-size 和 ResizeObserver,动态获取并绑定容器的宽高。
  2. CSS 变换 :通过 CSS 的 transform 属性,实现了将垂直滚动转换为水平滚动的效果,并保持了内容的正常显示方向。

如有错误,请指正O^O!

相关推荐
Moment1 天前
Vibe Coding 时代,到底该选什么样的工具来提升效率❓❓❓
前端·后端·github
IT_陈寒1 天前
SpringBoot性能飙升200%?这5个隐藏配置你必须知道!
前端·人工智能·后端
小时前端1 天前
React性能优化的完整方法论,附赠大厂面试通关技巧
前端·react.js
Nicko1 天前
Jetpack Compose BOM 2026.02.01 解读与升级指南
前端
小蜜蜂dry1 天前
nestjs学习 - 控制器、提供者、模块
前端·node.js·nestjs
优秀稳妥的JiaJi1 天前
基于腾讯地图实现电子围栏绘制与校验
前端·vue.js·前端框架
前端开发呀1 天前
从 qiankun(乾坤) 迁移到 Module Federation(模块联邦),对MF只能说相见恨晚!
前端
没想好d1 天前
通用管理后台组件库-10-表单组件
前端
恋猫de小郭1 天前
你用的 Claude 可能是虚假 Claude ,论文数据告诉你,Shadow API 中的欺骗性模型声明
前端·人工智能·ai编程
_Eleven1 天前
Pinia vs Vuex 深度解析与完整实战指南
前端·javascript·vue.js