vue 纵向滚动菜单, 点击滚动到选中菜单

1 背景

需要设计一个纵向滚动菜单,要求丝滑点,默认显示选中菜单

2 思路

  • 给定一个容器,样式包含overflow:hidden,默认高宽足够显示一个菜单(以下用图标代替菜单),鼠标悬浮时增大容器高度,显示更多图标
  • 设置两个div用于触发上下滚动(本意直接用每页第一和最后图标进行触发,但是这样会导致鼠标悬停时直接滚动,体验不好)
  • 鼠标点击时将点击图标滚动到当前页的第一个图标,鼠标没有点击,移出后菜单还原

3 实现

javascript 复制代码
<template>
  <div class="container" @mouseleave="handleMouseLeave">
    <div
      class="action up"
      v-if="scrollTop !== -1 * (totalHeight - pageHeight)"
      @mouseover="handleMouseOver('up')"
    ></div>
    <ul :style="{ transform: `translateY(${scrollTop}px)` }">
      <li
        v-for="(item, index) in imgs"
        :key="index"
        :style="{ padding: itemPadding + 'px' }"
      >
        <img
          :src="item"
          alt=""
          :style="{ width: iconSize[0] + 'px', height: iconSize[1] + 'px' }"
          @click="handleClick(index)"
        />
      </li>
    </ul>
    <div
      class="action dowm"
      v-if="scrollTop !== 0"
      @mouseover="handleMouseOver('down')"
    ></div>
  </div>
</template>

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

const props = defineProps({
  iconSize: {
    type: Array,
    default: () => [60, 60],
  },
  pageSize: {
    type: Number,
    default: 6,
  },
  itemPadding: {
    type: Number,
    default: 20,
  },
});
const list = [
  "vue.svg",
  "back.svg",
  "behance.svg",
  "down.svg",
  "hands.svg",
  "hdd.svg",
  "next.svg", //
  "one.svg",
  "snow.svg",
  "three.svg",
  "up.svg",
  "upload.svg",
  "vip.svg", //
  "dvi.svg",
  "bone.svg",
  "bird.svg",
  "ipad.svg",
  "duck.svg",
  "deer.svg", //
  "fish.svg",
  "clap.svg",
  "eagle.svg",
];
const imgs = ref(
  list.map((item) => new URL(`./assets/${item}`, import.meta.url).href)
);

const scrollTop = ref(0);
const baseIndex = ref(0);

const actionHeight = computed(() => {
  return props.itemPadding+ "px";
});

const itemHeight = computed(() => {
  return props.iconSize[1] + 2 * props.itemPadding;
});
const containerBaseSize = computed(() => {
  return itemHeight.value + "px";
});
const containerHeight = computed(() => {
  return itemHeight.value * props.pageSize + "px";
});
const pageHeight = computed(() => {
  return props.pageSize * itemHeight.value;
});
const totalHeight = computed(() => {
  return imgs.value.length * itemHeight.value;
});

const handleMouseOver = async (direction: string) => {
  if (direction === "up") {
    if (
      scrollTop.value + (totalHeight.value - pageHeight.value) >
      pageHeight.value
    ) {
      scrollTop.value += -1 * pageHeight.value;
    } else {
      scrollTop.value = -1 * (totalHeight.value - pageHeight.value);
    }
  } else {
    if (scrollTop.value + pageHeight.value >= 0) {
      scrollTop.value = 0;
    } else {
      scrollTop.value += pageHeight.value;
    }
  }
};

const handleClick = (index: number) => {
  scrollTop.value = -1 * index * itemHeight.value;
  baseIndex.value = index;
};
const handleMouseLeave = () => {
  handleClick(baseIndex.value);
};
</script>

<style scoped lang="scss">
.container {
  overflow: hidden;
  transition: all 0.5s;
  position: relative;
  width: v-bind(containerBaseSize);
  height: v-bind(containerBaseSize);
  &:hover {
    height: v-bind(containerHeight);
  }
  .action {
    cursor: pointer;
    position: absolute;
    width: 100%;
    height: v-bind(actionHeight);
    z-index: 10;
    &.up {
      top: 0;
    }
    &.dowm {
      bottom: 0;
    }
  }
  ul {
    box-sizing: content-box;
    margin: 0;
    padding: 0;
    height: 100%;
    transition: all ease-in-out 1s;
    list-style: none;
    li {
      line-height: 0;
      position: relative;

      img {
        cursor: pointer;
        transition: all 0.5s;
        &:hover {
          scale: 1.3;
        }
      }
    }
  }
}
</style>
相关推荐
ℋᙚᵐⁱᒻᵉ鲸落3 小时前
移动端滑动手势冲突:overflow: auto导致外层横向滑动失效
前端·javascript·css·vue.js·html
默_笙4 小时前
🎃 前端学了 Next.js,后端该学啥?NestJS 就是 Node 版的蜜雪冰城
前端·javascript
前端snow5 小时前
ai agent --- 实现 openclaw 定时间效果
前端
码云之上5 小时前
换模型之后,Chatbot 为什么要自己做 compact?
前端·agent·前端工程化
এ慕ོ冬℘゜5 小时前
navigator 导航对象 BOM制作方法
javascript
星栈6 小时前
自定义 UI-UX-Pro-Max 的 CSV 知识库,把 AI 生成的后台拉回行业该有的样子
前端·agent·weui
研☆香6 小时前
简单的图片上传 删除 预览
前端
ITresearchGuest6 小时前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·人工智能·信息可视化
慧一居士7 小时前
Vite项目中使用Less步骤,详细使用示例
前端·css
花间相见8 小时前
【LangChain组件03】—— LangChain Agents 执行与状态:工作流程与状态管理实战
java·前端·langchain