2.5 Elements -- useElementVisibility

2.5 Elements – useElementVisibility

https://vueuse.org/useElementVisibility

作用

追踪元素在视口中的可见性。

官方示例

<template>
  <div ref="target">
    <h1>Hello world</h1>
  </div>
</template>

<script>
import { ref } from 'vue'
import { useElementVisibility } from '@vueuse/core'

export default {
  setup() {
    const target = ref(null)
    const targetIsVisible = useElementVisibility(target)

    return {
      target,
      targetIsVisible,
    }
  }
}
</script>

无渲染组件代码如下:

<UseElementVisibility v-slot="{ isVisible }">
  Is Visible: {{ isVisible }}
</UseElementVisibility>

这个方法提供了指令性用法

<script setup lang="ts">
import { ref } from 'vue'
import { vElementVisibility } from '@vueuse/components'

const target = ref(null)
const isVisible = ref(false)

function onElementVisibility(state) {
  isVisible.value = state
}
</script>

<template>
  <div v-element-visibility="onElementVisibility">
    {{ isVisible ? 'inside' : 'outside' }}
  </div>

  <!-- with options -->
  <div ref="target">
    <div v-element-visibility="[onElementVisibility, { scrollTarget: target }]">
      {{ isVisible ? 'inside' : 'outside' }}
    </div>
  </div>
</template>

源码分析

实现此功能目前比较常用的API是IntersectionObserver接口,提供了一种异步观察目标元素与其祖先元素或顶级文档视窗交叉状态的方法。

https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver

但是此处没有使用这个API。

  • 先看hook的代码,这也是使用方式最简单的
export function useElementVisibility(
  element: MaybeComputedElementRef,
  { window = defaultWindow, scrollTarget }: UseElementVisibilityOptions = {},
) {
  const elementIsVisible = ref(false)

  const testBounding = () => {
    if (!window)
      return

    const document = window.document
    const el = unrefElement(element)
    if (!el) {
      elementIsVisible.value = false
    }
    else {
      const rect = el.getBoundingClientRect()
      elementIsVisible.value = (
        /**
        * 这个条件检查元素的顶部是否在视窗的高度内。
        * window.innerHeight提供了视窗的当前高度(包括滚动条),但如果它不可用(在某些旧版浏览器中),
        * 则使用document.documentElement.clientHeight作为回退,这表示整个文档的高度(在标准模式下等同于视窗高度)。
        */
        rect.top <= (window.innerHeight || document.documentElement.clientHeight)
          && rect.left <= (window.innerWidth || document.documentElement.clientWidth)
        /**
        * bottom 是元素底部到视口顶部的距离
        */
          && rect.bottom >= 0
          && rect.right >= 0
      )
    }
  }

  /**
  * 1 监听元素的变化,会触发testBounding
  *   post:时机是页面更新之后,因为这个是dom引用,需要dom渲染之后才能有值
  */
  watch(
    () => unrefElement(element),
    () => testBounding(),
    { immediate: true, flush: 'post' },
  )

  /**
  * 2 监听容器的滚动事件,触发testBounding,
  *		capture: false,在冒泡阶段执行,也是延迟触发时机的
  *   passive: true,显式告诉浏览器,回调中不会阻止默认事件。这有利于减少浏览器等待时间,提高性能。
  */
  if (window) {
    // 
    useEventListener(scrollTarget || window, 'scroll', testBounding, {
      capture: false, passive: true,
    })
  }

  return elementIsVisible
}

在这里插入图片描述

  • 再看一下指令的代码
// 符合自定义指令定义的规范 https://cn.vuejs.org/guide/reusability/custom-directives.html
export const vElementVisibility: ObjectDirective<
HTMLElement,
BindingValueFunction | BindingValueArray
> = {
  // 只注册了mounted这个钩子,使用变量是为了适配vue2
  [directiveHooks.mounted](el, binding) {
    if (typeof binding.value === 'function') {
      const handler = binding.value
      const isVisible = useElementVisibility(el)
      watch(isVisible, v => handler(v), { immediate: true })
    }
    else {
      /**
      * isVisible变化时,执行回调。在官方示例中,就是执行 isVisible.value = state
      */
      const [handler, options] = binding.value
      const isVisible = useElementVisibility(el, options)
      watch(isVisible, v => handler(v), { immediate: true })
    }
  },
}

⚠️:这个函数没有卸载清理的方法,会不会有问题?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值