木厶笔记存放站木厶笔记存放站
首页
文章
首页
文章
  • Web

    • 网络

      • Cookie 和 Session
      • HTTP 优化
      • HTTP
      • HTTP1.1 首部字段
      • HTTPS
      • TCP 和 UDP
      • URI 和 URL
      • 输入URL_至页面显示的过程
    • 安全

      • CSRF 攻击
      • JSON Web Token
      • sql 注入
      • XSS 攻击
    • 浏览器

      • 提升SEO
      • 浏览器内核
      • 浏览器的进程和线程
      • 浏览器缓存机制
      • 跨域
    • 解决方案

      • 优化大量图片加载
      • 扫码登录
    • 性能优化

      • 性能分析
      • 资源请求优化
      • 运行时优化
      • 重排重绘
      • 静态资源优化
  • HTML

    • 基础

      • Doctype
      • src 和 href
      • title 和 alt
    • HTML5

      • Audio 和 Video
      • Canvas
      • cookie session Storage localStorage
      • Drag
      • Svg
      • WebSocket
      • WebWorker
      • 标签
      • 离线缓存manifest
  • CSS

    • 基础

      • BFC
      • CSS 动画
      • CSS 架构
      • display float position
      • float 浮动
      • 元素不可见
      • 块级元素和行内元素
      • 层叠顺序与堆栈上下文
      • 引入外部CSS
      • 盒模型
      • 雪碧图
    • 布局

      • flex 布局--实用
      • flex 布局
      • Grid 布局--实用
      • Grid 布局
      • 三栏布局
      • 两栏布局
      • 响应式布局
      • 移动端布局
    • 实用

      • 修改svg图片颜色
      • 初始化 CSS
      • 换肤
      • 文本省略号
      • 消除 inline-block 间隙
      • 自定义字体
    • CSS-揭秘

      • 2-1 半透明边框
      • 2-2 多重边框
      • 2-3 灵活的背景定位
      • 2-4 边框内圆角
      • 2-5 条纹背景
      • 2-6 复杂的背景图案
      • 2-8 连续的图像边框
      • 3-1 自适应的椭圆
      • 3-2 平行四边形
      • 3-3 菱形图片
      • 3-4 切角效果
      • 3-5 梯形标签页
      • 3-6 简单的饼图
      • 4-1 单侧投影
      • 4-2 不规则投影
      • 4-3 染色效果
      • 4-4 毛玻璃效果
      • 4-5 折角效果
      • 5-1 连字符断行
      • 5-2 插入换行
      • 5-3 文本行的斑马线条
      • 5-6 华丽的 & 符号
      • 5-7 自定义下划线
      • 5-8 现实中的文字效果
      • 5-9 环形文字
      • 6-1 选用合适的鼠标光标
      • 6-2 扩大可点击区域
      • 6-3 自定义复选框
      • 6-4-5 通过阴影(模糊)来弱化背景
      • 6-6 滚动提示
      • 6-7 交互式的图片对比控件
      • 7-1 自适应内部元素
      • 7-2 精确控制表格列宽
      • 7-3 根据兄弟元素的数量来设置样式
      • 7-4 满幅的背景,定宽的内容
      • 7-5 垂直居中
      • 7-6 紧贴底部的页脚
      • 8-1 缓动效果
      • 8-2 逐帧动画
      • 8-3 闪烁效果
      • 8-4 打字动画
      • 8-5 状态平滑的动画
      • 8-6 沿环形路径平移的动画
    • CSS 选择器

      • 伪类
      • 属性选择器
      • 数学函数
      • 树结构伪类
      • 选择器优先级
      • 选择符
      • 逻辑选择器
    • 奇妙属性

      • @property
      • mask
  • JavaScript

    • 基础

      • 2 script标签
      • 3-1 语法
      • 3-3 var let const
      • 3-4-1 typeof 和 instanceof
      • 3-4-2 Undefined 类型
      • 3-4-3 Null 类型
      • 3-4-4 Boolean 类型
      • 3-4-5 Number 类型
      • 3-4-6 String 类型
      • 3-4-7 Symbol 类型
      • 3-4-8 Object 类型
      • 3-4-9 BigInt 类型
      • 3-5 操作符
      • 3-6 隐式转换
      • 4-1 原始值和引用值
      • 4-2 执行上下文与作用域
      • 4-3 垃圾回收
      • 5-1 Date
      • 5-2 RegExp
      • 5-3 原始值包装类型
      • 5-4-1 Global 单例内置对象
      • 5-4-2 Math 单例内置对象
      • 6-2 Array
      • 6-3 Map
      • 6-4 WeakMap
      • 6-5 Set
      • 6-6 WeakSet
      • 7-1 迭代器与生成器
      • 8-1-1 Object 属性
      • 8-1-2 Object 构造函数方法
      • 8-1-3 Object 语法增强和解构
      • 8-2 Object 创建
      • 8-3 原型和继承
      • 8-4 class 类
      • 8-5 this
      • 8-6 可选链和空值合并运算符
      • 9 proxy 代理与反射
      • 10-1 Function 函数
      • 10-2 apply call bind
      • 10-3 闭包
      • 10-4 私有变量
      • 10-5 扩展运算符和 rest
      • 11-1 event loop 事件循环机制
      • 11-2 Promise 期约
      • 11-3 async await
      • 12-1 BOM
      • 14-1-1 DOM 节点
      • 14-1-2 DOM Document 类型
      • 14-1-3 DOM Element 类型
      • 14-1-4 DOM Text 和 Comment 类型
      • 14-2 动态脚本和动态样式
      • 14-3 MutationObserver 监听节点变化
      • 14-4 Intersection Observer
      • 16-2-1 操控样式
      • 16-2-2 元素尺寸
      • 16-3 DOM 深度优先遍历
      • 17-1 事件流
      • 17-2 事件处理程序
      • 17-3 事件对象
      • 17-4-1 UI 和焦点事件
      • 17-4-2 鼠标和滚轮事件
      • 17-4-3 键盘与输入事件
      • 17-4-4 HTML5 事件
      • 17-4-5 设备和触摸事件
      • 17-6 模拟事件
      • 18-1 requestAnimationFrame&requestIdleCallback
      • 18-2 canvas
      • 19-1 表单基础
      • 19-2 文本框编程
      • 19-3 选择框编程
      • 19-5 富文本编辑器
      • 20-10 Perfromance API
      • 20-11-1 HTML 模板
      • 20-11-2 影子 DOM
      • 20-11-3 自定义元素
      • 20-14 MessageChannel和BroadcastChannel
      • 20-15 AbortController
      • 20-4 File API 与 Blob API
      • 20-7 Notifications API
      • 20-8 Page Visibility API
      • 20-9 Streams API
      • 23 JSON
      • 24-1 XMLHttpRequest
      • 24-5 Fetch API
      • 26-1 模块语法
      • 26-2 CommonJs 与 ES6 Module 的差异
    • 设计模式

      • 单例模式
      • 发布订阅模式
      • 工厂模式
      • 策略模式
      • 装饰器模式
      • 观察者模式
      • 适配器模式
    • 手写实现

      • apply call bind
      • flat
      • instanceof
      • JSONP
      • new
      • Promise API
      • trim
      • 数组去重
      • 柯里化
      • 深拷贝
      • 防抖和节流
    • 场景题

      • 映射 URL 参数
      • 模拟红绿灯
      • 请求-丢弃旧时序的请求
      • 请求-并发请求
      • 通过 value 找 key
      • 闭包题
    • 移动端

      • touch 事件
      • visualViewport
      • 像素
      • 移动端布局
      • 视口 Viewport
    • 实用

      • better-scroll 滚动组件
      • Proxy实现英文字母升降序
      • 区别数组和对象
      • 图片懒加载
      • 按首字母排序的列表
      • 控制粘贴板
    • PIXI

      • 1 Application
      • 2 Graphics
      • 3 loader
      • 4 Sprite
      • 5 Spine
      • 6 事件
      • 7 Renderer
  • Node

    • 基础

      • crypto 加密模块
      • fs 模块
      • http 模块
      • mysql 模块
      • redis 模块
    • 框架

      • express
      • koa2
    • 实用

      • @elasticelasticsearch
      • restful Mock 数据
      • 自定义 Mock 数据
    • 错误

      • pm2-watch报错 502
      • spawn 中文乱码
  • Jquery

    • 基础

      • 事件
      • 动画
      • 工具方法
      • 操作 dom
      • 获取元素
  • TypeScript

    • 环境

      • config
      • 环境
    • 基础

      • 01 原始类型和特殊类型
      • 02 字面量和类型拓宽
      • 03 interface 和 type
      • 04 数组和元组
      • 05 class(类)
      • 06 函数和重载
      • 07 联合类型和交叉类型
      • 08 泛型
      • 09 类型推断和类型断言
      • 10 匹配提取
      • 11 重新构造
      • 12 递归循环
      • 13数组长度做计数
      • 14 特殊特性
      • 15 内置高级类型
      • 16 inter extends
      • 17 协变和逆变
  • Vue

    • 环境

      • 安装
      • 自定义环境变量
    • 基础(2.x)

      • data
      • keep-alive
      • nextTick
      • props和sync
      • ref
      • v-for和v-if
      • 事件绑定
      • 动态组件
      • 动画
      • 循环渲染
      • 插槽 slot
      • 条件渲染
      • 样式绑定
      • 模板语法
      • 生命周期
      • 组件通讯
      • 自定义指令
      • 表单绑定
      • 计算属性和监听器
    • 基础(3.x)

      • Composition API
      • Script setup
      • Suspense
      • sync 语法糖
      • Teleport
      • vue3的升级点
      • 常用动画
      • 生命周期
    • router

      • vue-router 3
      • vue-router 4
    • vuex

      • pinia
      • vuex
      • vuex4
      • 刷新不丢失 vuex
    • 底层

      • MVVM
      • 双向绑定
      • 响应式
      • 模板编译
      • 渲染过程
      • 虚拟DOM和diff算法
    • 应用

      • 权限管理
  • React

    • 环境

      • 安装
      • 自定义环境变量
    • 基础

      • JSX
      • ref
      • SCU
      • 不可变数据 setState
      • 事件
      • 动画
      • 异步组件
      • 插槽
      • 条件
      • 生命周期
      • 组件公共逻辑抽离
      • 组件通讯
      • 表单
      • 逃离组件 portals
    • hooks

      • hooks
      • react-query
      • useClickOutSide —— 点击外面
      • useDebounce —— 防抖
      • useURLQueryParam —— 输入框值与search绑定
    • router

      • react-router-com 6
      • react-router-dom hooks
      • react-router-dom
    • redux

      • react-redux
      • redux 与 hooks
      • redux-persist
      • redux-thunk
      • redux-toolkit
      • redux
    • 底层

      • JSX本质
      • setState 原理
      • 合成事件
    • 实用

      • CSS-in-JS
      • 字体库
      • 数组优化为哈希表
      • 组件的子元素只能是规定的元素
  • Echarts

    • 基础

      • 仪表盘
      • 基础
      • 折线图
      • 散点图
      • 柱状图
      • 雷达图
      • 饼图
  • Electron

    • 环境

      • 基本安装
      • 集合 react
    • 基础

      • Dialog
      • 原生菜单
      • 右键菜单
      • 进程
  • 前端工程化

    • babel

      • babel7 实践
      • 工作原理
      • 生态
    • Browserslist

      • 基础
    • npm

      • npm模块安装机制
      • npm脚本
    • qiankun

      • 隔离原理
    • Vite

      • css
      • gzip-打包
      • vite config 常见配置
      • 环境
      • 静态资源
    • webpack

      • css环境
      • js&ts环境
      • loader
      • loader和plugin的区别及编写
      • plugin
      • splitChunks
      • webpack5--模块联邦
      • 代码压缩
      • 优化性能
      • 图片
      • 性能分析工具
      • 提高构建速度
      • 构建性能--并行
      • 构建性能--持久化缓存
      • 热更新
    • 代码提交规范

      • husky&lint-staged
  • Java

    • 环境

      • idea 创建 maven-archetype-webapp
      • spring boot web 的配置参考
      • spring 从初始配置
      • 与 git 集合
    • Web 基础

      • mybatis pager的应用
      • [] 和 List 和 Set
      • 全局 cors 跨域
      • 接口例子——登录
      • 文件接口——图片
      • 有效时间的唯一字符串
      • 自定义类
      • 通用的泛型服务端响应对象
  • Elastic

    • 基础

      • 分词规则
      • 基础概念
      • 基础语法
      • 环境搭建
    • 实用

      • canal——数据库准实时导入
      • logstash——数据库基于时间轴导入
  • Mysql

    • 基础

      • 字段操作
      • 数据库操作
      • 查询操作
      • 表操作
  • Python

    • 基础

      • 1-1-Number
      • 1-2-String
      • 1-3-list和tuple
      • 1-4-序列
      • 1-5-set
      • 1-6-dict
      • 2-1-运算符
      • 2-2-对象比较
      • 3-1-条件判断
      • 3-2-循环
      • 4-1-模块-包
      • 4-2-模块-__init__
      • 4-3-模块-内置变量
      • 4-4-模块-导入
      • 5-1-解包
      • 5-2-函数参数
      • 5-3-作用域
      • 5-4-高阶函数&三元表达式
      • 6-1-类
      • 6-2-类的继承
      • 6-3-枚举
      • 7-1-json
  • Flutter

    • 环境

      • 创建及运行项目
      • 第三方库
      • 运行环境
      • 静态资源
    • Dart

      • dynamic var object
      • List
      • Map
      • Number
      • Set
      • String
      • URI
      • 函数
      • 变量
      • 库
      • 异步
      • 流程控制语句
      • 类
      • 运算符
    • 基础

      • 1 Widget
      • 2 StatelessWidget & StatefulWidget
      • 3 State生命周期
      • 4 状态管理
      • 5 路由
    • 基础组件

      • Button
      • Text
      • 单选开关和复选框
      • 图片及ICON
      • 表单
      • 输入框
      • 进度指示器
    • 布局组件

      • 1 布局约束
      • 2 线性布局(Row & Column)
      • 3 弹性布局(Flex)
      • 4 流式布局(Wrap & Flow)
      • 5 层叠布局(Stack & Positioned)
      • 6 绝对定位(Align)
      • 7 LayoutBuilder & AfterLayout
    • 容器类组件

      • Container
      • Scaffold
      • 变换-Transform
      • 空间适配-FittedBox
      • 裁剪-Clip
      • 装饰-DecoratedBox
    • 可滚动组件

      • SingleChildScrollView
      • 通用属性
  • Git

    • 基础

      • Git commit message 规范
    • 实用

      • cherry-pick
      • stash
      • 分支&修改最近一次commit
      • 撤销
  • 算法

    • 基础

      • leetcode
      • 时间复杂度
    • 收录

      • 阿拉伯数字转中文
    • 数据结构

      • 哈希表、集合
      • 并查集
      • 数组、链表、跳表
      • 栈、队列
      • 树、二叉树、二叉搜索树
    • 算法

      • DFS 和 BFS
      • 二分查找
      • 二叉树路径问题题目
      • 动态规划2--基础题
      • 动态规划2--背包问题
      • 区间类题目
      • 岛屿类题目
      • 排列组合子集类题目
      • 排序算法
      • 摩尔投票
      • 递归
      • 链表
  • 部署

    • centos8

      • bitwarden_rs
      • ElasticSearch
      • git
      • https及http2
      • mac 向 centos 传输文件
      • nginx
      • nvm
    • 其他

      • docker
      • sitemap
      • 重装系统
  • 图形学

    • 基础

      • 1 三角函数
      • 2 斜率k
      • 3 向量
      • 4 矩阵
  • AI

    • langgraph

      • 1-01-图
      • 1-02-1-状态
      • 1-02-2-消息状态
      • 1-03-节点
      • 1-04-边
      • 1-05-Send
      • 1-06-Command
      • 1-07-配置-configurable
      • 1-08-1-内存持久性
      • 1-08-2-删除持久消息
      • 1-08-3-向量数据库
      • 1-08-4-持久上下文token优化
      • 1-09-1-工具
      • 1-09-2-大量工具优化
      • 1-10-1-人机交互-interrupt
      • 1-10-2-人机交互-interrupt_before
      • 1-11-1-流式输出
      • 1-11-2-自定义流式传输
      • 1-11-3-输出特定的流式消息
      • 1-12-1-子图
      • 1-13-1-ReAct
    • mcp

      • 服务器——python
      • 服务器——ts

按首字母排序的列表

数据解析

  1. 后端

    • 数据格式

      [
          {
      		title: 'A',
              list: [
                  {
                      id: 1,
                      name: 'alin',
                      pic: 'http://www.xxx.com/pic/alin.png'
                  },
                  {
                      id: 2,
                      name: '阿杜',
                      pic: 'http://www.xxx.com/pic/adu.png'
                  }
              ]
          },
          {
      		title: 'B',
              list: [
                  {
                      id: 3,
                      name: 'BY2',
                      pic: 'http://www.xxx.com/pic/BY2.png'
                  }
              ]
          },
      ]
      
    • 数据模拟

      npm i mockjs --save-dev
      
    • 获取首字母

      npm i pinyin --save-dev
      

封装滚动组件

  • 需要的库
npm i @better-scroll/core @better-scroll/observe-dom --save
  • UI 组件
<template>
    <div ref="rootRef">
        <slot></slot>
    </div>
</template>

<script>
import useScroll from './useScroll'

export default {
    name: 'scroll',
    props: {
        // 0~3,数字越大,滚动事件触发的频率越频繁
        probeType: {
            type: Number,
            default: 0
        }
    },
    emits: ['scroll'],
    setup(props, { emit }) {
        const { scroll, rootRef } = useScroll(props, emit)

        return { scroll, rootRef }
    },
}
</script>
  • 逻辑组件
import BScroll from '@better-scroll/core'
import ObserveDOM from '@better-scroll/observe-dom'
import { onMounted, onUnmounted, onActivated, onDeactivated, ref } from 'vue'

// 使用监听,当监听到 dom 发生变化,重新构造滚动组件
BScroll.use(ObserveDOM)

export default function useScroll(options, emit) {
    const rootRef = ref(null)
    const scroll = ref(null)

    onMounted(() => {
        const scrollVal = scroll.value = new BScroll(rootRef.value, {
            observeDOM: true,
            ...options
        })

        // 当 probeType 大于 0,则派发 scroll 事件
        if (options.probeType > 0) {
            scrollVal.on('scroll', position => {
                emit('scroll', position)
            })
        }
    })

    onUnmounted(() => {
        scroll.value.destroy()
    })

    onActivated(() => {
        scroll.value.enable()
        scroll.value.refresh()
    })

    onDeactivated(() => {
        scroll.value.disable()
    })

    return {
        rootRef, scroll
    }
}
  • 使用
<template>
  <scroll
   class="wrapper"
   probeType="3"
   @scroll="handleScroll"
  >
    <div>......</div>
  </scroll>
</template>

<script>
import Scroll from './components/scroll/scroll.vue'

export default {
  components: { Scroll },
  setup() {
    const handleScroll = (pos) => {
      console.log(pos)
    }
    return { handleScroll }
  }
}
</script>

<style lang="less">
// 给容器一个高度
.wrapper {
  height: 100vh;
  width: 100%;
  margin: 0 auto;
  overflow: hidden;
}
</style>

基础样式参考

  • 只放比较重要的代码
<template>
  <scroll
   class="wrapper"
   probeType="3"
   @scroll="handleScroll"
  >
    <ul>
      <li v-for="item of dataRef">
        <div class="title">{{ item.title }}</div>
        <ul>
          <li v-for="singer of item.list">
            <img :src="singer.pic" />
            <span>{{ singer.name }}</span>
          </li>
        </ul>
      </li>
    </ul>
  </scroll>
</template>

<script>
import { onMounted, ref } from 'vue'
import Scroll from './components/scroll/scroll.vue'
import axios from 'axios'

export default {
  components: {
    Scroll
  },
  setup() {
    const dataRef = ref([])

    onMounted(async () => {
      const { data: { data } } = await axios.get('/api/list')
      dataRef.value = data
    })

    const handleScroll = (pos) => {
      console.log(pos)
    }

    return { dataRef, handleScroll }
  }
}
</script>

顶部显示当前正在处于哪个首字母区域

创建 useIndex.js 封装逻辑

export default function useIndex() {}

计算每一个区域相对视口顶部的距离

每次滚动事件都判断,当前滚动距离数据哪两个区域之间,则可知道要显示那个区域的首字母

import { ref, watch, computed, nextTick } from "vue";
/*
* dataRef: 列表数据的 ref 对象
* contentRef: 所有区域的父节点
*/
export default function useIndex(dataRef, contentRef) {
    // 每个区域相对视口顶部距离的合集
    const heights = ref([])
    const currentIndex = ref(0)
	// 当 dataRef 发生变化时,应该重新计算 heights
    watch(dataRef, () => {
        nextTick(init)
    })

    // 重新计算每个 heights
    function init() {
        const heightsVal = heights.value = []
        const lis = contentRef.value.children

        for (let i = 0; i < lis.length; i++) {
            heightsVal.push( lis[i].getBoundingClientRect().top )
        }
        heightsVal.push(Number.MAX_SAFE_INTEGER)
    }

    function handleScroll (position) {
        const scrollY = -position.y
        const heightsVal = heights.value

        for (let i = 0; i < heightsVal.length - 1; i++) {
            const heightBottom = heightsVal[i + 1]
            if (scrollY >= heightsVal[i] && scrollY <= heightBottom) {
                currentIndex.value = i
                break;
            }
        }
    }

    return { handleScroll, }
}

使用计算属性,计算对应 currentIndex 的 title

import { ref, watch, computed, nextTick } from "vue";

export default function useIndex(dataRef, contentRef) {
    const heights = ref([])
    const currentIndex = ref(0)
    
    watch(dataRef, () => {
        nextTick(init)
    })
    
    // 当前标题,通过 currentIndex 计算得出
    const currentTitle = computed(() => {
        return currentIndex.value === -1 ? '' : (dataRef.value[currentIndex.value]?.title || '')
    })

    function init() {
        const heightsVal = heights.value = []
        const lis = contentRef.value.children

        for (let i = 0; i < lis.length; i++) {
            heightsVal.push( lis[i].getBoundingClientRect().top )
        }
        heightsVal.push(Number.MAX_SAFE_INTEGER)
    }

    function handleScroll (position) {
        const scrollY = -position.y
        // 因为 better-scroll 允许滚动到最顶部后,继续向上拉取一段距离
        // 而向上拉取时,应当不显示 index DOM,且不用向下继续计算
        // 当出现以下情况时,设置成 -1,方便使用
        if (scrollY < 0) {
            currentIndex.value = -1
            return
        }
        
        const heightsVal = heights.value

        for (let i = 0; i < heightsVal.length - 1; i++) {
            const heightBottom = heightsVal[i + 1]
            if (scrollY >= heightsVal[i] && scrollY <= heightBottom) {
                currentIndex.value = i
                break;
            }
        }
    }

    return { handleScroll, currentTitle }
}

增强动画效果

import { ref, watch, computed, nextTick } from "vue";

export default function useIndex(dataRef, contentRef) {
    // translate 功能发生大的最小间隔,一般设置为 index DOM 的 clientHeight
    const MIN_DISTANCE = 30
    const heights = ref([])
    const currentIndex = ref(0)
    const transformStyle = ref({
        transform: 'translateY(0)'
    })

    const currentTitle = computed(() => {
        return currentIndex.value === -1 ? '' : (dataRef.value[currentIndex.value]?.title || '')
    })

    watch(dataRef, () => {
        nextTick(init)
    })

    function init() {
        const lis = contentRef.value.children
        const heightsVal = heights.value = []

        for (let i = 0; i < lis.length; i++) {
            heightsVal.push( lis[i].getBoundingClientRect().top )
        }
        heightsVal.push(Number.MAX_SAFE_INTEGER)
    }

    function handleScroll (position) {
        const scrollY = -position.y
        if (scrollY < 0) {
            currentIndex.value = -1
            return
        }

        const heightsVal = heights.value

        for (let i = 0; i < heightsVal.length - 1; i++) {
            const heightBottom = heightsVal[i + 1]
            if (scrollY >= heightsVal[i] && scrollY <= heightBottom) {
                currentIndex.value = i
                // 把增强动画效果,封装到一个函数中
                changeTransformStyle(scrollY, heightBottom)
                break;
            }
        }
    }

    // 动态改变 index DOM 的 translate,产生更好看的动画效果
    function changeTransformStyle(scrollY, heightBottom) {
        const distance = heightBottom - scrollY
        const transformY = (distance > 0 && distance <= MIN_DISTANCE) ? distance - MIN_DISTANCE : 0
        transformStyle.value = {
            transform: `translateY(${transformY}px)`
        }
    }

    return { handleScroll, currentTitle, transformStyle }
}

使用

<template>
  <scroll
   :probeType="3"
   @scroll="handleScroll"
  >
    <ul ref="contentRef">...</ul>

    <div 
      class="index" 
      :style="transformStyle"
      v-show="currentTitle"
    >{{ currentTitle }}</div>
  </scroll>
</template>

<script>
import { onMounted, ref } from 'vue'
import Scroll from './components/scroll/scroll.vue'
import useIndex from './components/index/useIndex'
import axios from 'axios'

export default {
  components: {
    Scroll
  },
  setup() {
    const contentRef = ref(null)
    const dataRef = ref([])

    onMounted(async () => {
      const { data: { data } } = await axios.get('/api/list')
      dataRef.value = data
    })
    
    const { handleScroll, currentTitle, transformStyle } = useIndex(dataRef, contentRef)

    return { 
      dataRef, contentRef,
      handleScroll, currentTitle, transformStyle 
    }
  }
}
</script>

<style lang="less">
	.index {
        position: fixed;
        top: 0;
        left: 0;
    }
</style>

辅助点击的首字母导航栏

产生对应的 DOM 结构

  1. 生成首字母列表
export default function useLetterJump(dataRef) {
    const letters = computed(() => dataRef.value.map(item => item.title))
    return { letters }
}
  1. 循环生成 DOM
<template>
  <scroll>
    <ul>...</ul>
    <div class="index">...</div>

    <ul class="letter-wrapper">
      <li
        v-for="(letter, index) of letters"
        :key="letter"
        :data-index="index"
        :class="{'letter-active': currentIndex === index}"
      >{{ letter }}</li>
    </ul>
  </scroll>
</template>

<script>
export default {
  setup() {
    // 需要导出 currentIndex,用以判断哪个字母需要高亮
    const { currentIndex, ... } = useIndex(dataRef, contentRef)
    const { letters } = useLetterJump(dataRef, contentRef)

    return { dataRefcurrentIndex,letters }
  }
}
</script>

<style lang="less">
.letter-wrapper {
    position: fixed;
    top: 50%;
    right: 4px;
    transform: translateY(-50%);
    width: 20px;
    padding: 10px 0;
    text-align: center;
    color: #fff;
    background: rgba(0, 0, 0, .7);
    border-radius: 10px;
    list-style: none;
}
.letter-active {
  	color: pink;
}
</style>

点击某个字母,并跳到对应区域

  1. 把点击事件绑定在父容器中,代理每个字母的事件
  2. 获取点击字母的 data-index
  3. 获取 dataRef 对应 data=index 的 dom,通过 better-scroll 提供的 scrollToElement 跳转
import { ref, computed } from "vue"

export default function useLetterJump(dataRef, contentRef) {
    // 为获取 better-scroll 实例
    const scrollRef = ref(null)

    const letters = computed(() => dataRef.value.map(item => item.title))

    function handleClick(e) {
        const index = Number(e.target.dataset.index)
        // 有可能点到父容器,父容器上没有 dataset.index,所以会返回 NaN,此时什么都不用做
        if (index) {
            scrollTo(index)
        }
    }

    function scrollTo(index) {
        const scroll = scrollRef.value.scroll
        const targetEl = contentRef.value.children[index]
        scroll.scrollToElement(targetEl)
    }

    return { letters, scrollRef, handleClick }
}
  1. 在组件中使用
<template>
  <scroll ref="scrollRef">
    <ul @touchstart.stop.prevent="handleClick">
      <li></li>
    </ul>
  </scroll>
</template>

<script>
export default {
  setup() {
    const { letters, scrollRef, handleClick } = useLetterJump()
    return { letters, scrollRef, handleClick }
  }
}
</script>

移动首字母导航栏,也可以跳转到滑动时对应的区域

  1. 点击时,记录点击的位置,和点击对应的 index
  2. 移动时,计算移动时的位置,减去点击时的位置,再除于字母的高度,既可以知道移动了多少个 index
  3. 再加上点击时的 index,即可得出移动后的 index
import { ref, computed } from "vue"

export default function useLetterJump(dataRef, contentRef) {
    const ANCHOR_HEIGHT = 20
    const scrollRef = ref(null)

    const letters = computed(() => dataRef.value.map(item => item.title))

    const touch = {
        y1: -1
    }

    function handleClick(e) {
        const index = Number(e.target.dataset.index)
        if (index) {
            // 记录位置
            touch.y1 = e.touches[0].pageY
            touch.startIndex = index
            scrollTo(index)
        }
    }

    function handleMove(e) {
        if (touch.y1 >= 0) {
            // 计算跳转到的 index
            touch.y2 = e.touches[0].pageY
            const distance = Math.floor( (touch.y2 - touch.y1) / ANCHOR_HEIGHT )
            const endIndex = touch.startIndex + distance
            scrollTo(endIndex)
        }
    }

    function handleEnd() {
        touch.y1 = -1
    }

    function scrollTo(index) {
        const scroll = scrollRef.value.scroll
        const targetEl = contentRef.value.children[index]
        scroll.scrollToElement(targetEl)
    }

    return { letters, scrollRef, handleClick, handleMove, handleEnd }
}

使用

<template>
  <scroll
   class="wrapper"
   :probeType="3"
   @scroll="handleScroll"
   ref="scrollRef"
  >
    <ul 
      @touchstart.stop.prevent="handleClick"
      @touchmove.stop.prevent="handleMove"
      @touchend.stop.prevent="handleEnd"
    >
      <li
        v-for="(letter, index) of letters"
        :key="letter"
        :data-index="index"
        :class="{'letter-active': currentIndex === index}"
      >{{ letter }}</li>
    </ul>
  </scroll>
</template>

<script>
export default {
  setup() {
    const { currentIndex } = useIndex(dataRef, contentRef)
    const { letters, scrollRef, handleClick, handleMove, handleEnd } = useLetterJump(dataRef, contentRef)

    return { currentIndex, letters, scrollRef, handleClick, handleMove, handleEnd }
  }
}
</script>

最近更新:: 2023/3/20 00:07
Contributors: kingmusi
Prev
图片懒加载
Next
控制粘贴板