专栏首页鱼头的Web海洋像监听页面一样监听戈多的动态

像监听页面一样监听戈多的动态

  • 作者:陈大鱼头
  • github:KRISACHAN

不知道各位童鞋有木有看过 《等待戈多》 这部出名的荒诞戏剧 。其剧情大概就是 戈戈 与 狄狄 等待 戈多 的过程中发生的一些琐事,一共两幕。等了这么多年,也不知道 戈多 现在在哪,赴约了没有。

如果 戈戈 与 狄狄 像我们监听页面元素变化那样监听戈多的动态,是不是就不会出现空欢喜的状态?是不是就不用等得那么辛苦?是不是甚至可以主动去寻找戈多?

说起监听页面元素变化,那么你可知道有哪些方法可以实现这个功能?

Object.defineProperty

关于 Object.defineProperty 这个属性大家应该很熟(毕竟是各类面经中的常客),但还是要简单介绍下~

Object.defineProperty 允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来。

描述符可同时具有的键值:

configurable

enumerable

value

writable

get

set

数据描述符

Yes

Yes

Yes

Yes

No

No

存取描述符

Yes

Yes

No

No

Yes

Yes

所以我们有以下这种效果:

代码如下:

'use strict'
Object.defineProperty(godot, 'style', {
    get() {
        return this.getAttribute('style')
    },
    set(data) {
        this.setAttribute('style', data)
        const distance = (noLeftTree.offsetLeft - this.offsetLeft)
        console.log(distance >= 51 ? '戈多没来,我们先各自干各自的活吧' : '戈多快到了,走,我们集合去')
    }
})
const whereIsGodot = start => {
    if (start) {
        let d = 0
        const godotRun = () => {
            if (noLeftTree.offsetLeft - 51 >= d) {
                setTimeout(() => {
                    d++
                    godot.style = `left: ${d}px`
                    godotRun()
                }, 16)
            }
        }
        godotRun()
    }
}

简单来说就是使用 Object.defineProperty 监听戈多的位置变化,然后当戈多移动到集合地点附近时,等待戈多的俩哥们就可以去赴约了。通过上述的代码,我们可以知道 whereIsGodot 函数只负责戈多的位置移动,但是监听权在等待戈多的两个人那里,这样保证了代码语义化的同时,耦合度也尽可能地小。

MutationObserver

Mmmmm,我一直以为 MutationObserver 是个新属性,直到我膝盖中了一箭看了can i use 。

caniuse

本来鱼头我也不知道有这属性,但是最近在工作上遇到了需要监听页面元素变动的场景,然后就了解到了这个API。

于是鱼头便看了文档,发现是个好牛逼的API。

所以这到底是个啥?

简单来说就是一个可以监听 DOM Tree 变动的API,名字直译就是 “突变观察者”

按WHATWG的定义,它的执行逻辑如下:

  1. 先执行监听的微任务队列;
  2. 执行完微任务队列之后就把所监听的记录封装成一个数组来处理;
  3. 然后返回处理结果。

所以具体怎么用?

突变观察者 是个构造器,它接受一个回调并返回一个 节点记录列表(sequence <MutationRecord> 以及 构造的参数对象(MutationObersver)

它有以下三个方法:

  1. observe(target, options):监听对象,接受两个参数,一个是监听的对象(target),一个是观察的选项(options);
  2. disconnect():断开监听的功能;
  3. takeRecords():清空监听的队列,并返回结果。

options选项可选参数(以下属性可设置为true):

  1. childList:监听目标子节点的变化;
  2. attributes:监听目标属性的变化;
  3. characterData:监听目标数据的变化;
  4. subtree:监听目标以及其后代的变化;
  5. attributeOldValue:监听目标属性变化前的具体值;
  6. characterDataOldValue:监听目标数据变化前的具体值;
  7. attributeFilter:不需要监听的属性列表(此属性填入过滤的属性列表)。

如何监听戈多的位置?

下面我们就通过实际的代码来监听戈多的位置变化。

效果还是如同上图。

代码如下:

const godot = document.querySelector('#godot')
const config = {
    childList: true,
    attributes: true,
    characterData: true,
    subtree: true,
    attributeOldValue: true,
    characterDataOldValue: true
}
const mutationCallback = mutationsList => {
    const [
        {
            target: {
                offsetLeft: godotPos
            }
        }
    ] = mutationsList
    const distance = (noLeftTree.offsetLeft - godotPos)
    console.log(distance >= 51 ? '戈多没来,我们先各自干各自的活吧' : '戈多快到了,走,我们集合去')
}
const observer = new MutationObserver(mutationCallback)
observer.observe(godot, config)
const whereIsGodot = start => {
    if (start) {
        let d = 0
        const godotRun = () => {
            if (noLeftTree.offsetLeft - 51 >= d) {
                setTimeout(() => {
                    d++
                    godot.style = `left: ${d}px`
                    godotRun()
                }, 16)
            } else {
                observer.disconnect()
            }
        }
        godotRun()
    }
}

因为鱼头在业务需要对某个已经完善的功能在部分操作监听数据变动,如果对原来的代码进行改动,也不是一件轻松的事,而且这样子代码太冗长,耦合度也会较高,所以就选择了用 突变观察者 来实现,效果还是不错的。

Intersection Observer

除了监听元素的变动,还有什么方式可以知道戈多的位置呢?

如果有那就是 Intersection Observer 了。

这又是个啥?

戈多心想:“又来一个Observer ?别监听了,我去找你们就是了,嘤嘤嘤。”

IntersectionObserver 直译是 “交叉观察者” ,这个API使开发人员能够监听目标元素与根(祖先或视口)元素交叉状态的方法。

它的用法跟 MutationObserver 相似,同样是个构造器,它接受一个 回调函数(callback(entries)) 以及 可选参数对象(options)

所以又怎么用?

首先 callback 会返回一个 监听属性对象(IntersectionObserverEntry) ,其具体属性如下:

  1. time:可见性发生变化的时间,是个双精度的毫秒时间戳;
  2. rootBounds:根元素的盒子区域信息,有根元素则返回 getBoundingClientRect() 的值,没有则返回 null
  3. boundingClientRect:监听元素的盒子区域信息;
  4. intersectionRect:监听元素与根元素的交叉区域信息;
  5. isIntersecting:判断监听元素是否与根元素相交,返回布尔值;
  6. intersectionRatio:监听元素的可见比例,即intersectionRect / boundingClientRect 完全可见时为1,完全不可见时小于等于0;
  7. target:监听的目标元素。

options 可选参数如下:

  1. root:与监听对象相交的根元素,如果没有,返回隐式根;
  2. rootMargin:跟CSS的margin一样,发生交叉的偏移量;
  3. threshold:触发回调的阈值,填入数组,范围在0~1之间,决定发生监听事件的交叉比例。

可选择方法如下:

  1. IntersectionObserver.observe():开始监听;
  2. IntersectionObserver.disconnect():停止监听;
  3. IntersectionObserver.takeRecords():返回所有观察目标的 IntersectionObserverEntry 对象数组;
  4. IntersectionObserver.unobserve():使IntersectionObserver停止监听特定目标元素。

戈多,你今晚到底是来还是不来?

所以怎么用这个API来监听戈多的位置呢?

先看效果(真特么简陋)

代码如下:

<style>
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    html,
    body {
        width: 100%;
        height: 200%;
    }
    noLeftTree {
        position: fixed;
        left: 0;
        top: 0;
        width: 100%;
        height: 100px;
        background: #FFF;
    }
    godot,
    estragon,
    vladimir {
        position: absolute;
        width: 50px;
        height: 50px;
        border-radius: 50%;
        border: 1px solid;
        text-align: center;
    }
    estragon {
        top: 0;
        left: 0;
    }
    vladimir {
        top: 0;
        right: 0;
    }
    godot {
        left: calc(50% - 25px);
        top: 1000px;
    }
</style>
<noLeftTree id="noLeftTree">
    <estragon id="estragon">戈戈</estragon>
    <vladimir id="vladimir">狄狄</vladimir>
</noLeftTree>
<godot id="godot">戈多</godot>
<script>
    'use strict'
    const godot = document.querySelector('#godot')
    const noLeftTree = document.querySelector('#noLeftTree')
    const ioCallback = entries => {
        console.log(entries[0].intersectionRatio <= 0 ? '戈多没来,我们先各自干各自的活吧' : '戈多快到了,走,我们集合去')
    }
    const ioConfig = {
        threshold: [0, 0.25, 0.5, 0.75, 1]
    }
    const io = new IntersectionObserver(ioCallback, ioConfig)
    io.observe(godot)
</script>

后记

其实如果肯花时间去研究,利用好上述三个API,是可以实现很多很有趣的效果的,上面的只是一个初尝的DEMO,真正在项目里是可以实现很多很重要的功能。

不过戈戈 与 狄狄也等待戈多快70年了,就像痴情的女生等待远走的渣男一样,就是不来好歹也给个音信啊。

戈多心想:“我不过是迷路了么,嘤嘤嘤”

如果你、喜欢探讨技术,或者对本文有任何的意见或建议,你可以扫描下方二维码,关注微信公众号“ 鱼头的Web海洋 ”,随时与鱼头互动。欢迎!衷心希望可以遇见你。

本文分享自微信公众号 - 鱼头的Web海洋(krissarea),作者:陈大鱼头

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 那个炒鸡有趣的H5标签 —— <dataList>

    按照whatwg文档所展示的,截至到本文截稿之前,一共有 113 个HTML标签。

    陈大鱼头
  • 『1W7字中高级前端面试必知必会』终极版

    如果两个 URL 的 protocol 、 port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。

    陈大鱼头
  • 前端进阶之setTimeout 为什么会出现误差?

    讲到线程,那么肯定也得说一下进程。其实在本质上,两个名词都是 CPU 工作时间片的一个描述。

    陈大鱼头
  • 每日算法系列【LeetCode 714】买卖股票的最佳时机含手续费

    给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。

    godweiyang
  • 基于 RMAN 的同机数据库克隆

    Oracle数据库克隆,也叫着Oracle数据库复制,可以通过基于用户管理的方式来完成,也可以基于RMAN方式来实现。而且Oracle建议使用RMAN方式来实现...

    Leshami
  • 锐捷路由技术 | NTP配置

    RSR路由器可以做为NTP的服务器,但是现网大部分情况下并非以路由器做为NTP服务器。

    网络技术联盟站
  • 专访易宝支付联合创始人、总裁余晨:未来的支付存在于无形之中

    图丨易宝支付联合创始人、总裁余晨 作为第三方支付行业的领路者和探路人,相信业内人士对易宝支付都不陌生。发展至今,每三张机票里就有一张是通过易宝支付提供的服务,为...

    数据猿
  • oracle系列--第二篇 oracle下载

    对于很多新手来说,包括我之前也是这样,知道oracle数据库,但是就是不知道在哪里下载。有时候,上到oracle官方网站上面都找不到下载的地方。

    Hongten
  • 2017年前端工程师应该学习什么

    在我们所生活的这个快节奏的世界里,人们都倾向于把自己的时间用在进行一些新的创造上,然后再互联网上讨论它们。 我并不是说不该这样做,而是我认为我们应该适当的慢一...

    用户1667431
  • 使用双buffer无锁化

    所谓双buffer技术,其实就是准备两个Obj,一个用来读,一个用来写。写完成之后,原子交换两个Obj;之后的读操作,都放在交换后的读对象上,而原来的读对象,在...

    awk

扫码关注云+社区

领取腾讯云代金券