zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

day-ui – Affix 组件学习

2023-02-26 10:20:12 时间

固钉组件是把页面某个元素相对页面 HTML 或者某个 dom 内定位显示,例如固定页面顶部/底部显示,页面宽高改变也会保持原位置。如果进行滚动,超过定义的范围就会固定定位,否则会跟随页面滚动

day-ui - Affix 组件学习

(福利推荐:阿里云、腾讯云、华为云服务器最新限时优惠活动,云服务器1核2G仅88元/年、2核4G仅698元/3年,点击这里立即抢购>>>

上一节我们介绍了 DButtonDIcon 的实现,所以新建 affix 文件目录结构我们就不多介绍了。我们主要学习一下内部实现方式,本质就是位置定位,我们要看下用了哪些判断和第三方库,如果有哪里不对欢迎指正。

效果分析

  1. 第一种情况是没有设置容器,可以根据 position 位置设置固定定位,如果位置设置 top,那么当监听到页面滚动,如果当前元素的 top 值小于设置的偏移量,设置 fixed 定位(反之 bottom 是比较 bottom 值大于页面高度和偏移量的差值设置 fixed 定位)
  2. 第二种情况是设置容器,那么 top / bottom 的是只在容器内显示的,容器不在页面后,定位元素也就消失。如果设置的 top 值,那么当当前元素 top 值小于偏移量同时容器的 bottom 大于0,元素 fixed 定位(反之 bottom 偏移需要计算页面高度和 bottom 值得对比)。

最近学习了解到fixed 定位默认是相对与窗口的,但是如果给父节点定义属性 transform、filter、perspective,fixed 定位就会相对父集,大家感兴趣的话可以自行查看。

代码分析

dom 结构

<template>   <div ref="root" class="d-affix" :style="rootStyle">     <!-- 定位元素 滚动时监听 root 位置和页面可视区的关系设置 fixed,定位的时候设置样式-->     <div :class="{ 'd-affix--fixed': state.fixed }" :style="affixStyle">       <slot></slot>     </div>   </div> </template>

外层定义 d-affix 类,高度和内部的元素相同,为了当内部元素 fixed 定位脱离文档流时,页面占位结构不变;同时需要对比 d-affixtopbottom 值判断元素何时脱离文档,何时复位。

属性

props: {   // 定位元素的层级   zIndex: {     type: Number,     default: 100   },   // 在哪个容器内,没传就是视图   target: {     type: String,     default: ''   },   // 上下偏移量   offset: {     type: Number,     default: 0   },   // 距上边距下边距   position: {     type: String,     default: 'top'   } }, // 对外暴露两个方法,监听滚动和 fixed 状态改变 emits: ['scroll', 'change'],

setUp 核心

// 定位元素属性 const state = reactive({   fixed: false,   height: 0, // height of target 滚动时获取赋值   width: 0, // width of target   scrollTop: 0, // scrollTop of documentElement   clientHeight: 0, // 窗口高度   transform: 0 // 元素在 target 中定位时 y 方向移动 })  // 计算属性,滚动时才能具体获取  // d-affix 类一直存在文档流中,只要宽高,滚动位置判断是否 fixed const rootStyle = computed(() => {   return {     height: state.fixed ? `${state.height}px` : '',     width: state.fixed ? `${state.width}px` : ''   } }) // 定位元素属性 const affixStyle = computed(() => {   if (!state.fixed) return   const offset = props.offset ? `${props.offset}px` : 0   const transform = state.transform     ? `translateY(${state.transform}px)`     : ''    return {     height: `${state.height}px`,     width: `${state.width}px`,     top: props.position === 'top' ? offset : '',     bottom: props.position === 'bottom' ? offset : '',     transform: transform,     zIndex: props.zIndex   } })

滚动时定位属性的判断:

const updateState = () => {   // 获取 d-affix 节点信息   const rootRect = root.value.getBoundingClientRect()   // 获取 target 节点的信息   const targetRect = target.value.getBoundingClientRect()   state.height = rootRect.height   state.width = rootRect.width   // 没有 target 取 html 的 scrollTOP(有 target 在 target 中滚动)   state.scrollTop =     scrollContainer.value === window       ? document.documentElement.scrollTop       : scrollContainer.value.scrollTop    state.clientHeight = document.documentElement.clientHeight   // 设置上边距   if (props.position === 'top') {     if (props.target) {       // 定位元素在 target 元素中滑动距离,bottom 持续改变       const difference = targetRect.bottom - props.offset - state.height       // target 元素top在可视区外面,bottom在可视区进行定位       state.fixed = props.offset > rootRect.top && targetRect.bottom > 0       state.transform = difference < 0 ? difference : 0     } else {       // 以html为相对容器,页面滚动,固定定位(d-affix 在可视区外)       state.fixed = props.offset > rootRect.top     }   } else {   // 设置下边距     if (props.target) {       const difference =         state.clientHeight - targetRect.top - props.offset - state.height       state.fixed =         state.clientHeight - props.offset < rootRect.bottom &&         state.clientHeight > targetRect.top       state.transform = difference < 0 ? -difference : 0     } else {       // offset + bottom > 视图高度,元素进行定位       state.fixed = state.clientHeight - props.offset < rootRect.bottom     }   } }
const onScroll = () => {   updateState()   emit('scroll', {     scrollTop: state.scrollTop,     fixed: state.fixed   }) }  watch(   () => state.fixed,   () => {     emit('change', state.fixed)   } ) // 页面挂载的时候 onMounted(() => {   if (props.target) {     // 注意传的格式     target.value = document.querySelector(props.target)     if (!target.value) {       throw new Error(`target is not existed: ${props.target}`)     }   } else {     target.value = document.documentElement // html   }   // 下面我们分析辅助函数   scrollContainer.value = getScrollContainer(root.value)   // 函数式编程,on 改写的 addEventListener   on(scrollContainer.value, 'scroll', onScroll)   addResizeListener(root.value, updateState) }) // 页面即将关闭取消监听移除 onBeforeMount(() => {   off(scrollContainer.value, 'scroll', onScroll)   removeResizeListener(root.value, updateState) })

辅助函数

  • on
// 函数式编程处理元素监听 export const on = function(element, event, handler, useCapture = false) {   if (element && event && handler) {     element.addEventListener(event, handler, useCapture)   } }
  • off
export const off = function(element, event, handler, useCapture = false) {   if (element && event && handler) {     element.removeEventListener(event, handler, useCapture)   } }
  • getScrollContainer
/**  * 获取滚动容器  * @param {*} el 滚动的容器  * @param {*} isVertical 竖直滚动还是水平滚动  * @returns  */ export const getScrollContainer = (el, isVertical) => {   if (isServer) return   let parent = el   while (parent) {     // 都没有就是 window     if ([window, document, document.documentElement].includes(parent)) {       return window     }     // 容器是否可滚动     if (isScroll(parent, isVertical)) {       return parent     }     parent = parent.parentNode   }   return parent }
  • isSserver
export default typeof window === 'undefined'
  • isScroll
/**  *  * @param {*} el  * @param {*} isVertical 是否垂直方向 overflow-y  * @returns  */ export const isScroll = (el, isVertical) => {   if (isServer) return   const determineDirection = isVertical === null || isVertical === undefined   const overflow = determineDirection     ? getStyle(el, 'overflow')     : isVertical     ? getStyle(el, 'overflow-y')     : getStyle(el, 'overflow-x')    return overflow.match(/(scroll|auto)/) }
  • getStyle
// 获取元素的属性值 export const getStyle = function(element, styleName) {   if (isServer) return   if (!element || !styleName) return null   styleName = camelize(styleName)   if (styleName === 'float') {     /**      * ie6~8下:style.styleFloat         FF/chrome 以及ie9以上:style.cssFloat      */     styleName = 'cssFloat' // FF/chrome 以及ie9以上   float兼容性写法   }   try {     const style = element.style[styleName]     if (style) return style     // 获取window对象, firefox低版本3.6 才能使用getComputed方法,iframe pupup extension window === document.defaultView,否则指向错误     // https://www.cnblogs.com/yuan-shuai/p/4125511.html?userCode=wrvvs1rm     const computed = document.defaultView.getComputedStyle(element, '')     return computed ? computed[styleName] : ''   } catch (e) {     return element.style[styleName]   } }

resize-observer-polyfill 库

这个库是我第一次见到,如果不看源码都不知道的。觉得还是挺有意思的,这里做个简单介绍。

这个库主要作用是监听元素 size 改变。通常情况下我们监听大小改变只能使用 window.size 或者 window.orientationchange(移动端屏幕横向纵向显示)。resize 事件会在 1s内触发 60 次左右,所以很容易在改变窗口大小时候引发性能问题,所以当我们监听某个元素变化的时候就显得有些浪费。

ResizeObserver API 是新增的,在有些浏览器还存在兼容性,这个库可以很好的进行兼容。ResizeObserver 使用了观察者模式,当元素 size 发生改变时候触发(节点的出现隐藏也会触发)。

用法

const observer = new ResizeObserver(entries => {   entries.forEach(entry => {     console.log('大小位置', entry.contentRect)     console.log('监听的dom', entry.target)   }) }) // 监听的对象是body,可以改变浏览器窗口大小看打印效果 observer.observe(document.body)// dom节点,不是类名 id名

day-ui - Affix 组件学习

  • width:指元素本身的宽度,不包含 padding,border
  • height:指元素本身的高度,不包含 padding,border
  • top:指 padidng-top 的值
  • left:指 padding-left 的值
  • right:指 left + width 的值
  • bottom: 值 top + height 的值

方法

  • ResizeObserver.disconnect() 取消所有元素的监听
  • ResizeObserver.observe() 监听元素
  • ResizeObserver.unobserve() 结束某个元素的监听

组件使用

我们在 onMounted 中对 root 元素监听。页面滚动时候要监听,元素大小改变也要监听

import ResizeObserver from 'resize-observer-polyfill' import isServer from './isServer'  const resizeHandler = function(entries) {   for (const entry of entries) {     /**      * const {left, top, width, height} = entry.contentRect;      * 'Element:', entry.target         Element's size: ${ width }px x ${ height }px`         Element's paddings: ${ top }px ; ${ left }px`      */     const listeners = entry.target.__resizeListeners__ || []     if (listeners.length) {       // 元素改变直接执行方法       listeners.forEach(fn => fn())     }   } } // 监听element元素size改变,执行fn export const addResizeListener = function(element, fn) {   if (isServer || !element) return   if (!element.__resizeListeners__) {     element.__resizeListeners__ = []     /**      * https://github.com/que-etc/resize-observer-polyfill      *      */     element.__ro__ = new ResizeObserver(resizeHandler)     // 观察的对象     element.__ro__.observe(element)   }   element.__resizeListeners__.push(fn) } // 退出移除监听 export const removeResizeListener = function(element, fn) {   if (!element || !element.__resizeListeners__) return   element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1)   if (!element.__resizeListeners__.length) {     // 取消监听     element.__ro__.disconnect()   } }

以上就是对 affix 组件的学习。如有不对欢迎指正。

day-ui - Affix 组件学习


本站部分内容转载自网络,版权属于原作者所有,如有异议请联系QQ153890879修改或删除,谢谢!
转载请注明原文链接:day-ui – Affix 组件学习

你还在原价购买阿里云、腾讯云、华为云、天翼云产品?那就亏大啦!现在申请成为四大品牌云厂商VIP用户,可以3折优惠价购买云服务器等云产品,并且可享四大云服务商产品终身VIP优惠价,还等什么?赶紧点击下面对应链接免费申请VIP客户吧:

1、点击这里立即申请成为腾讯云VIP客户

2、点击这里立即注册成为天翼云VIP客户

3、点击这里立即申请成为华为云VIP客户

4、点击这里立享阿里云产品终身VIP优惠价

喜欢 (0)
[[email protected]]
分享 (0)