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

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

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

wait

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

Object.defineProperty

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

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

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

configurable enumerable value writable get set
数据描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes

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

gd1

代码如下:

'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。

dc

所以这到底是个啥?

简单来说就是一个可以监听 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来监听戈多的位置呢?

先看效果(真特么简陋)

godot3

代码如下:

<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年了,就像痴情的女生等待远走的渣男一样,就是不来好歹也给个音信啊。

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

img

如果你喜欢探讨技术,或者对本文有任何的意见或建议,非常欢迎加鱼头微信好友一起探讨,当然,鱼头也非常希望能跟你一起聊生活,聊爱好,谈天说地。
鱼头的微信号是:krisChans95
也可以扫码添加好友,备注“SF”就行
https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/base/wx-qrcode1.jpg


鱼头的Web海洋
鱼头的Web海洋

Kris不只是一只鱼头

959 声望
3.1k 粉丝
0 条评论
推荐阅读
用神奇的 form 验证 API 来优化你的表单验证
鱼头曾在 『极限版』不掺水,用纯 CSS 来实现超飒的表单验证功能 一文中分享过一个花里胡哨的 纯 CSS 的表单验证功能 。虽然仅仅依赖 CSS 是无法满足我们的日常开发需求的,但是配合着各种原生的 form 验证 API ...

陈大鱼头5阅读 2.2k

从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木140阅读 11.9k评论 10

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木60阅读 5.9k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.1k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木39阅读 7k评论 6

【关于Javascript】--- 正则表达式篇
基础知识一、元字符 {代码...} 二、量词 {代码...} 三、集合 字符类 {代码...} 四、分支 {代码...} 五、边界 开始结束 {代码...} 六、修饰符 {代码...} 七、贪婪模式和非贪婪模式js默认贪婪模式即最大可能的匹配...

Jerry35阅读 2.9k

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木32阅读 6k评论 9

Kris不只是一只鱼头

959 声望
3.1k 粉丝
宣传栏