浮动吸顶、吸底vue指令

使用方式:
<footer v-fixed="{
      fixedPosition: 'bottom', // bottom浮动吸底、top浮动吸顶
      immediate: true, // 初始化页面是否启用浮动
      fixedStyle: { // 浮动后节点样式
        'border-radius': '5px 5px 0 0',
        'background-color': 'rgba(90, 90, 90, 0.4)'
      }
    }">
   <el-button @click="back">返回</el-button>
   <el-button type="primary">确定</el-button>
</footer>
源码:
/**
 *
 * description: dom吸顶、吸底
 *
 */

// 获取上个存在滚动条的节点
function getScrollDom(el) {
  let dom = el
    //  while (!(dom.scrollHeight > dom.clientHeight) && dom) {
    //    dom = dom.parentElement
    //  }
  do {
    dom = dom.parentElement
    dom?.scroll(0, dom.scrollTop + 1)
  } while (dom && !dom.scrollTop)

  dom?.scroll(0, dom.scrollTop - 1)

  return dom
}

// 监听节点是否完全处于视口内
class intersectionObserver {
  immediate = false
  intersectionObserver = null
  markNode = null
  constructor(scrollDom, markNode, { immediate }, revert, fixed) {
    this.markNode = markNode
    this.immediate = immediate
    this.intersectionObserver = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        if (this.immediate) revert()
        this.immediate = true
        return
      };
      if (this.immediate) {
        fixed()
      }
    }, {
      root: scrollDom,
      rootMargin: '0px',
      threshold: [0, 1] // 交叉阈值,这里设置为0和1,表示从完全不交叉到完全交叉的整个过程都会触发回调
    })
  }

  observe() {
    this.intersectionObserver.observe(this.markNode)
  }

  disconnect() {
    this.intersectionObserver.disconnect(this.markNode)
  }
}
export default {
    inserted: (el, binding, vnode) => {
    if (!IntersectionObserver) return
    /**
     * description: 指令参数说明
     * @param fixedPosition bottom下浮动、top上浮动
     * @param fixedStyle 浮动后附加样式
     * @param immediate 是否页面加载就启用浮动,默认false
     * @return {}
     *
     */
    const { fixedPosition, immediate, fixedStyle, fixedClassName } = {
      fixedPosition: 'bottom',
      immediate: true,
      ...binding.value,
      fixedStyle: { // 浮动后节点样式
        'z-index': '3000',
        ...(binding.value?.fixedStyle || {})
      }
    }
    if (fixedClassName) el = el.querySelector(fixedClassName)
    // 记录节点原有样式
    const oldStyle = [
      'transition',
      'zIndex',
      'position',
      'margin',
      'width',
      'left',
      'right',
      [fixedPosition],
      ...Object.keys(fixedStyle)
    ].reduce((old, val) => ({
      ...old,
      [val]: el.style.getPropertyPriority(val)
    }), {})
    el.oldStyle = oldStyle
    el.fixedStyle = fixedStyle
    const scrollDom = getScrollDom(el)

    // 创建锚点
    const markNode = document.createElement('div')
    markNode.style.setProperty('padding', '0')
    markNode.style.setProperty('margin', '0')
    markNode.style.setProperty('transform', `translateY(${el.offsetHeight / 4}px)`)
    el.insertAdjacentElement('beforebegin', markNode)

    // eslint-disable-next-line new-cap
    const domObserver = new intersectionObserver(
      scrollDom,
      markNode,
      { immediate },
      function () {
        // 样式还原
        Object.entries(el.oldStyle).forEach(([k, v]) => {
          el.style.setProperty(k, v)
        })

        el.scrollIntoView({ behavior: 'instant', block: { top: 'start', bottom: 'end' }[fixedPosition] })
        el.fixedOpen = false
      },
      async function () {
        const { clientWidth } = document.body
        const { left, right } = el.getBoundingClientRect()
        el.style.setProperty('transition', 'all 300ms')
        el.style.setProperty('z-index', 999)
        el.style.setProperty('position', 'fixed')
        el.style.setProperty(fixedPosition, '0px')
        el.style.setProperty('left', left + 'px')
        el.style.setProperty('right', clientWidth - right + 'px')
        el.style.setProperty('margin', '0px')
        el.style.setProperty('width', 'auto')
        // 添加浮动后样式
        Object.entries(el.fixedStyle).forEach(([k, v]) => {
          el.style.setProperty(k, v)
        })
        el.fixedOpen = true
      })
    // 执行监听
    domObserver.observe()
  },
  update: (el, binding, vnode) => {
    if (!IntersectionObserver) return
    el.fixedStyle = {
      ...(el?.fixedStyle || {}),
      ...(binding.value?.fixedStyle || {})
    }
    if (!el.fixedOpen) return
    Object.entries(binding.value?.fixedStyle || {}).forEach(([k, v]) => {
      el.style.setProperty(k, v)
    })
  }
}
备注:主要使用技术是IntersectionObserver监听节点是否处于视口,css position:fixed 进行吸底、吸顶浮动。代码有升级空间,可改为不使用fixed浮动,节点固定位置改为可滚动窗口的顶部或底部;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值