vue实现电梯锚点导航

news2024/11/27 22:28:17

1、目标效果

        最近喝了不少的咖啡、奶茶,有一个效果我倒是挺好奇怎么实现的:

        (1)点击左侧分类菜单,右侧滚动到该分类区域

        (2)右侧滑动屏幕,左侧显示当前所处的分类区域

        这种功能会出现在商城项目中或者分类数量较多的项目中,专业名称称电梯导航 

        目标效果:

        (1)点击左侧的分类,右侧滑动到指定区域

       

        (2)滚动右侧区域,左边分类显示当前所处的分类区域

2、原理

(1)这要用到原生js关于偏移量和位置相关的api,这些api建立在你的布局是定位的基础上,父亲相对定位左边分类和右边商品都是绝对定位

(2)左边分类要与右侧商品模块数量一一相等(数量和位置都要对应相等),否则实现不了电梯导航效果

(3)点击左侧分类,右侧跳转到对应模块;这用到了window.scrollTo(水平方向距离,竖直方向距离)  

(4)右侧滑动,左侧发生相应的变化,这要用到滚动事件,vue中使用滚动事件需要再onMounted()生命周期注册一下滚动事件

onMounted(() => {
    window.addEventListener('scroll', handleScroll);
})

(5)如何判断滚动到什么程度左侧才显示对应的模块?

  • dom.offsetTop:每个dom元素有该属性,表示距离顶部窗口的距离

  • document.documentElement.scrollTop:表示页面滚动的距离

  • document.documentElement.scrollTop >= dom.offsetTop:显示对应的模块,可以通过遍历商品模块数组,拿到对应的索引,然后设置左边分类对应的dom为激活状态

(6) 出现一个问题:window.scrollTo()将模块滚动至某一位置  与页面滚动事件 发生了冲突,此时可以添加一个互斥变量isLock,等window.scrollTo()滚动结束之后,再放开锁

    // 获取选中的dom元素
    const typeItemDom = shop.value[val]

    // 开锁
    isLock.value = true

    // 第一个参数为水平方向,第二个参数为纵轴方向
    window.scrollTo(0, typeItemDom.offsetTop)

    setTimeout(() => {
        //关锁
        isLock.value = false
    }, 0)

(7)为什么放开锁要在setTimeout里面?根据js事件循环机制,同步任务(主线程代码、new Promise里面的代码)执行速度快于异步任务(setTimeout、setInterval、ajax、promise.then里面的任务 )这样才能确保锁是在 window.scrollTo() 执行完毕之后才打开的

3、源代码

        App.vue


<template>
  <div>
    <ClassifyByVue></ClassifyByVue>
  </div>
</template>
<script setup>
// import ClassifyByJs from './components/ClassifyByJS.vue';
import ClassifyByVue from './components/ClassifyByVue.vue';
</script>

<style>
* {
  padding: 0;
  margin: 0;
}
</style>

        ClassifyByVue.vue

<template>
    <div class="classify">
        <div class="left">
            <div class="item" :class="{ active: currentIndex == index }" v-for="(item, index) in types"
                @click="changeType(index)">{{ item }}
            </div>
        </div>
        <div class="right" @scroll="handleScroll">
            <div v-for="(item, index) in shops" :key="index" ref="shop">
                <div class="title">{{ item.category }}</div>
                <div class="item" v-for="(i, j) in item.data" :key="j">
                    <div class="photo">
                        <img src="/vite.svg" class="logo" alt="Vite logo" />
                    </div>
                    <div class="info">
                        <div class="name">{{ i.name }}</div>
                        <div class="type">{{ i.type }}</div>
                        <div class="desc">{{ i.desc }}</div>
                        <div class="buy">购买</div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
let isLock = ref(false)
// 分类
let types = ref([
    '人气Top',
    '爆款套餐',
    '大师咖啡',
    '小黑杯',
    '中国茶咖',
    '生椰家族',
    '厚乳拿铁',
    '丝绒拿铁',
    '生酪拿铁',
    '经典拿铁',
])

// 商品
let shops = ref([
    {
        category: '人气Top',
        data: [
            {
                name: '冰吸生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '摸鱼生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '茉莉花香拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            },
            {
                name: '小甘橘美式',
                type: '咖啡',
                desc: '咖啡'
            },

        ]
    },
    {
        category: '爆款套餐',
        data: [
            {
                name: '2杯套餐',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '3杯套餐',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '4杯套餐',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '5杯套餐',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '不喝咖啡套餐',
                type: '咖啡',
                desc: '咖啡'
            },
            {
                name: '必喝套餐',
                type: '咖啡',
                desc: '咖啡'
            },

        ]
    },
    {
        category: '大师咖啡',
        data: [
            {
                name: '美式',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '加浓美式',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '橙C美式',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '澳瑞白',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '卡布奇诺',
                type: '咖啡',
                desc: '咖啡'
            },
            {
                name: '玛奇朵',
                type: '咖啡',
                desc: '咖啡'
            },

        ]
    },
    {
        category: '小黑杯',
        data: [
            {
                name: '云南小柑橘',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '广东小柑橘',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '广西小柑橘',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '福建小柑橘',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '湖南小柑橘',
                type: '咖啡',
                desc: '咖啡'
            },
            {
                name: '江西小柑橘',
                type: '咖啡',
                desc: '咖啡'
            },

        ]
    },
    {
        category: '中国茶咖',
        data: [
            {
                name: '碧螺知春拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '茉莉花香拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '菊花香拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '梅花香拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '兰花香拿铁',
                type: '咖啡',
                desc: '咖啡'
            },
            {
                name: '玫瑰花香拿铁',
                type: '咖啡',
                desc: '咖啡'
            },

        ]
    },
    {
        category: '生椰家族',
        data: [
            {
                name: '冰吸生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '摸鱼生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '椰云拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            },
            {
                name: '陨石拿铁',
                type: '咖啡',
                desc: '咖啡'
            },
        ]
    },
    {
        category: '厚乳拿铁',
        data: [
            {
                name: '厚乳拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '茉莉花香拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '椰云拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            },
            {
                name: '海盐拿铁',
                type: '咖啡',
                desc: '咖啡'
            },
        ]
    },
    {
        category: '丝绒拿铁',
        data: [
            {
                name: '丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '生椰丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '黑糖丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '椰云丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '香草丝绒拿铁',
                type: '咖啡',
                desc: '咖啡'
            }
        ]
    },
    {
        category: '生酪拿铁',
        data: [
            {
                name: '生酪拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '绿豆拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '红豆拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '黑豆拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '黄豆拿铁',
                type: '咖啡',
                desc: '咖啡'
            }
        ]
    },
    {
        category: '经典拿铁',
        data: [
            {
                name: '拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '陨石拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '焦糖拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '生椰拿铁',
                type: '咖啡',
                desc: '咖啡'
            }, {
                name: '美式',
                type: '咖啡',
                desc: '咖啡'
            }
        ]
    },
])

// 获取右侧商品的ref实例
let shop = ref(null)

// 用来表示当前选中处于激活状态的分类的索引
let currentIndex = ref(0)

// 切换类型
const changeType = val => {
    currentIndex.value = val

    // 获取选中的dom元素
    const typeItemDom = shop.value[val]

    // 开锁
    isLock.value = true

    // 第一个参数为水平方向,第二个参数为纵轴方向
    window.scrollTo(0, typeItemDom.offsetTop)

    setTimeout(() => {
        //关锁
        isLock.value = false
    }, 0)
}

// 监听页面滚动
const handleScroll = () => {
    // 锁关了滚动事件才有效
    if (!isLock.value) {
        types.value.forEach((item, index) => {
            // console.dir(shop.value[index]);
            const shopItemDom = shop.value[index]

            // 每个模块距离顶部的距离
            const offsetTop = shopItemDom.offsetTop

            // 页面滚动的距离
            const scrollTop = document.documentElement.scrollTop

            if (scrollTop >= offsetTop) {
                // 给左边分类设置激活的效果
                currentIndex.value = index
            }
        })
    }
}

onMounted(() => {
    window.addEventListener('scroll', handleScroll);
})
onBeforeUnmount(() => {
    window.removeEventListener('scroll', handleScroll);
})
</script>

<style scoped lang="less">
.classify {
    display: flex;
    position: relative;

    .left {
        display: flex;
        flex-direction: column;
        align-items: center;
        position: fixed;
        left: 0;
        top: 0;
        bottom: 0;
        width: 92px;
        overflow-y: scroll;
        border-right: 1px solid #C1C2C4;

        .item {
            display: flex;
            justify-content: center;
            align-items: center;
            width: 67px;
            height: 29px;
            font-size: 15px;
            font-family: PingFang SC-Semibold, PingFang SC;
            font-weight: 600;
            color: #333333;
        }

        .active {
            color: #00B1FF;
        }

        .item:not(:last-child) {
            margin-bottom: 25px;
        }
    }

    .right {
        flex: 1;
        position: absolute;
        top: 0;
        right: 17px;
        overflow-y: scroll;

        .title {
            font-size: 18px;
            margin-bottom: 5px;
        }

        .item {
            display: flex;
            justify-content: space-between;
            margin-bottom: 17px;
            width: 246px;
            height: 73px;

            .photo {
                width: 58px;
                height: 58px;

                img {
                    width: 100%;
                    height: 100%;
                    border-radius: 12px;
                    border: 1px solid gray;
                }
            }

            .info {
                display: flex;
                flex-direction: column;
                position: relative;
                width: 171px;
                height: 73px;
                box-shadow: 0px 1px 0px 0px rgba(221, 221, 221, 1);

                .name {
                    padding-left: 0;
                    font-size: 17px;
                    font-weight: 600;
                    color: #333333;
                }

                .type,
                .desc {
                    font-size: 14px;
                    font-weight: 400;
                    color: #999999;
                }

                .buy {
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    position: absolute;
                    right: 0;
                    top: 17px;
                    width: 67px;
                    height: 29px;
                    background: #E7E8EA;
                    border-radius: 21px;
                    font-size: 15px;
                    font-family: PingFang SC-Semibold, PingFang SC;
                    font-weight: 600;
                    color: #05AFFA;
                }
            }
        }
    }
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jmeter进阶使用:BeanShell实现接口前置和后置操作

一、背景 我们使用Jmeter做压力测试或者接口测试时&#xff0c;除了最简单的直接对接口发起请求&#xff0c;很多时候需要对接口进行一些前置操作&#xff1a;比如提前生成测试数据&#xff0c;以及一些后置操作&#xff1a;比如提取接口响应内容中的某个字段的值。举个最常用…

XDC约束技巧 之 I/O篇 (上)

《XDC约束技巧之时钟篇》中曾对I/O约束做过简要概括&#xff0c;相比较而言&#xff0c;XDC中的I/O约束虽然形式简单&#xff0c;但整体思路和约束方法却与UCF大相径庭。加之FPGA的应用特性决定了其在接口上有多种构建和实现方式&#xff0c;所以从UCF到XDC的转换过程中&#x…

都别吹牛逼了,2个英语指令简单评测便知ChatGPT、博弈Ai、文心一言、通义千问、讯飞星火真实水平

一、博弈Ai&#xff1a;GPT3.5版 演示&#xff1a;https://chat.bo-e.com/ 1、充当英语发言助手 评分&#xff1a;10分 总结&#xff1a;完整满足了指令需求 2、充当英汉互译器 评分&#xff1a;8分 总结&#xff1a;基本满足了我的指令需求。但是有点啰嗦&#xff0c;扣…

MySQL---视图(定义、修改、更新、重命名、删除)

1. 定义视图 视图&#xff08;view&#xff09;是一个虚拟表&#xff0c;非真实存在&#xff0c;其本质是根据SQL语句获取动态的数据集&#xff0c;并为其命 名&#xff0c;用户使用时只需使用视图名称即可获取结果集&#xff0c;并可以将其当作表来使用。 数据库中只存放了…

SIR模型与R模拟

SIR病毒模型R模拟 文章目录 SIR病毒模型R模拟[toc]1.SIR病毒模型2.R模拟 1.SIR病毒模型 SIR病毒模型的的三个字母分别为病毒传播过程中的三种状态&#xff0c;其中 S&#xff0c;表示易感染者&#xff0c;即没有被感染病毒的人群I&#xff0c;表示已感染者&#xff0c;即被感…

Spring_jdbcTemplate基本使用

文章目录 一、导入spring-jdbc和spring-tx坐标二、创建数据库表和实体在applicationContext.xml中配置连接池和JdbcTemplate在test数据库中创建account表 三、创建JdbcTemplate对象四、执行数据库操作 一、导入spring-jdbc和spring-tx坐标 <dependency><groupId>o…

Vue.js快速入门

文章目录 一、Vue基础1.1 渐进式框架​1.2 第一个Vue程序1.3 el 挂载点1.4 data 数据对象 二、Vue 指令2.1 v-text 文本值2.2 v-html 标签元素2.3 v-on 绑定事件2.4 v-show 隐藏2.5 v-if 消除2.6 v-bind 属性值2.7 v-for 生成列表2.8 v-model 双向数据绑定 三、axios 网络请求库…

EXCEL数组公式的理解和技巧(未完成)

1 小心特例 frenquce 会划分为n1个区间 SUMPRODUCT(IF(FREQUENCY(B5:B18,B5:B18)>0,1,0)*IF(VALUE(MID(A5:A18,6,1))5,1,0)) 2 用0/ 和1/0 数组公式来解决问题 SUMPRODUCT(1/COUNTIF(B5:B18,B5:B18)) 这个只返回了B列里不重复的数据个数&#xff0c;确实是10个 SUMPRODU…

在 Windows 上安装 kind

一、前言 个人主页: ζ小菜鸡大家好我是ζ小菜鸡&#xff0c;让我们一起学习在 Windows 上安装 kind。如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连) 二、 kind是什么 kind 是一个使用 Docker 容器“节点”运行 Kubernetes 集群的工具。使用 kind 工具搭建的 Kubernetes…

【Linux Network】应用层协议——HTTP

目录 1. 认识URL 2. urlencode和urldecode urlencode例子&#xff1a; urldecode例子&#xff1a; 3. HTTP协议格式 3.1 HTTP请求&#xff1a; 3.2 HTTP响应&#xff1a; 3.3 HTTP的方法&#xff1a; 3.4 GET方法和POST方法的区别 3.5 HTTP的状态码&#xff1a; 3.6 HTTP常见He…

Python学习20:温度转换 II(python123)

温度的刻画有两个不同体系&#xff1a;摄氏度&#xff08;Celsius&#xff09;和华氏度&#xff08;Fabrenheit&#xff09;。‪‪‪‪‪‪‫‪‪‪‪‪‪‪‪‪‪‪‫‪‪‪‪‪‫‪‪‪‪‪‫‪‪‪‪‪‪‫‪‪‪‪‪‫‪ 请编写程序将用户输入华氏度转换为摄氏度&#xff…

参会记录|春日研学 · 踏歌前行 —— MAS 实验室内部学术研讨会

前言&#xff1a;2023年5月12日&#xff08;周五&#xff09;晚&#xff0c;实验室在江苏苏州天街附近举行了一次内部研讨会&#xff0c;主题聚焦当今学术界研究前沿和实验室下一阶段发展规划。会议期间&#xff0c;首先是各位与会博士生畅所欲言&#xff0c;探讨当前学术前沿&…

数据结构学习记录——什么是图(抽象数据类型定义、常见术语、邻接矩阵表示法、邻接表表示法)

目录 什么是图 抽象数据类型定义 常见术语 无向图 有向图 网络 邻接点 度&#xff08;出度、入度&#xff09; 稀疏图 稠密图、完全图 边密度 邻接矩阵表示法 用二维数组存储 用一维数组存储 邻接矩阵的好处 邻接矩阵的坏处 邻接表表示法 指针数组-链表存储…

ES的可视化工具-Kibana的安装和使用

【前言】 Kibana 是为 Elasticsearch设计的开源分析和可视化平台。你可以使用 Kibana 来搜索&#xff0c;查看存储在 Elasticsearch 索引中的数据并与之交互。你可以很容易实现高级的数据分析和可视化&#xff0c;以图表的形式展现出来。 下面是Kibana的安装步骤&#xff0c;控…

Vue项目的搭建和启动

文章目录 一、安装配置 node.js1.1 下载安装1.2 配置环境变量1.3 修改模块下载位置1.4 设置淘宝镜像 二、创建启动 Vue三、开发环境 VSCode3.1 开发插件3.2 Vue 项目结构 提示&#xff1a;以下是本篇文章正文内容&#xff0c;前端系列学习将会持续更新 一、安装配置 node.js …

【重新定义matlab强大系列六】利用matlab进行一维滤波or二维滤波

&#x1f517; 运行环境&#xff1a;matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

NVM —— 你把我玩明白

前期回顾 纯前端 根据目录解析word,拆分不同段落_彩色之外的博客-CSDN博客纯前端 解析 wordhttps://blog.csdn.net/m0_57904695/article/details/130503211?spm1001.2014.3001.5501 &#x1f44d; 本文专栏&#xff1a;开发技巧 目录 &#x1f37a; 下载 及 配置镜像 …

如何设计测试用例以及常用的黑盒测试方法

目录 如何设计测试用例&#xff1f; 设计测试用例的万能公式 ①功能测试 (用户的基本功能需求&#xff09; ②性能测试 ③易用性&#xff08;考虑用户体验&#xff09;测试 ④兼容性测试 ⑤界面&#xff08;UI,外观&#xff09;测试 ⑥安全测试 常用黑盒测试方法 …

MySQL_4 常见函数汇总及演示

目录 一、统计函数 1.基本语法 : 2.代码演示 : 演示Ⅰ—— 演示Ⅱ—— 二、合计函数 1.基本语法 : 2.代码演示 : 三、平均函数 1.基本语法 : 2.代码演示 : 四、最值函数 1.基本语法 : 2.代码演示 : 五、字符串函数 1.常用字符串函数 : 2.代码演示 : 六、数学…

Linux基础学习---6、系统定时任务、软件包管理、虚拟机克隆

1、系统定时任务 1.1 crontab 服务管理 1、重新启动crond服务1.2 crontab 定时任务设置 1、基本语法crontab [选项] 2、选项说明选项功能-e编辑crontab定时任务-l查询crontab任务-r删除当前用户所有的crontab任务 3、参数说明 &#xff08;1&#xff09;进入crontab编辑页面…