第二十四章 JS盒子模型

141 阅读7分钟

一、JS设置元素样式

  • box.style.xxx = xxxx //写在元素的行内样式上
  • box.style.cssText = "width:100px;height:100px;" //批量加
  • box.className = xxx //给元素设置样式类名,从而让其拥有某些样式
  • box.classList.add(xxx) //为使样式类名不覆盖原有的设置方法
    box.classList.remove(xxx) //移除类名
    box.classList.replace(xxx,xxxx) //替换
    box.classList.contains(xxx) //包含
    box.classList.toggle(xxx) //原来有删除,没有添加

二、JS获取元素样式

1、box.style.xxx

获取写在元素"行内"上的样式【特点:样式只有写在行内上才可以获取】

2、window.getComputedStyle([element],[伪类 after/before])

返回的是包含当前元素所有“经过浏览器计算后”的样式对象【但凡元素经过浏览器渲染,所有样式都是被计算的;所以无论写在行内还是样式表中,写或者不写样式,样式都可以获取到】

3、ele.getBoundingClientRect()

  • 作用: 返回了一个对象,包含当前元素和可视窗口的位置信息\
    • left/right 盒子左边框/右边框距离可视窗口“左边”的距离\
    • top/bottom 盒子上边框/下边框距离可视窗口“上边”的距离

三、JS盒子模型属性:获取元素的相关样式

1、clientWidth/clientHeight

获取元素的可视区域宽高【真实内容+左右padding】,不受内容溢出的影响

2、clientTop/clientLeft

获取元素上边框和左边框的宽度

3、offsetWidth/offsetHeight

clientWidth/clientHeight基础上加上“左右/上下边框”即【真实内容+左右padding+左右border】。

4、scrollWidth/scrollHeight

在没有内容溢出的情况下,获取的值和clientWidth/clientHeight相同;

在有内容溢出的情况下,获取的是真实宽高(包含溢出内容),获取的是约等于的值【因为根据浏览器设置overflow的值不同,获取的结果也不尽相同;不同浏览器下获取到的值也不尽相同】。

5、scrollTop/scrollLeft

获取盒子(或者页面)卷去的高度和宽度【这两个属性是13个属性中唯一“可读写”的,其余11个属性只是“可读”的】。

6、offsetTop/offsetLeft

获取元素距离其父级参照物的距离【上边距/左边距 距离范围是:元素的外边框-->父级参照物的内边框】

7、offsetParent

获取元素的父级参照物【同一个平面(文档流)中,最外层元素是内层所有元素的父级参照物】。

  • 默认情况下,元素的父级参照物是body【body.offsetParent===null】
  • 想要修改父级参照物,可以修改元素的“定位规则”【目的:让其脱离文档流】(利用定位)

8、助理解

  • clientWidth...

9、总结:

1)只有scrollTop/scrollLeft 是“可读写”的,其余属性都是“只读”的;

2)clientWidth/clientHeight、clientTop/clientLeft、offsetWidth/offsetHeight、scrollWidth/scrollHeight 都是获取元素本身的样式(例如:宽高和边框等);而offsetTop/offsetLeft/offsetParent获取的都是元素距离别人的位置

3)操作浏览器的盒模型都是基于 document.documentElement(HTML元素)进行操作的;

4)获取到的样式都是经过四舍五入的整数值,不会出现小数!!

// 获取元素距离BODY的左偏移/上偏移「不论元素父参照物是谁」
const offset = function offset(ele) {
    // 先获取本身的偏移
    let left = ele.offsetLeft,
        top = ele.offsetTop,
        parent = ele.offsetParent;
    // 只要父参照物还在,并且不是BODY,则重复下述步骤
    while (parent && parent.tagName !== "BODY") {
        // 在之前的基础上把父参照物的边框和偏移加上
        let { clientLeft, offsetLeft, clientTop, offsetTop } = parent;
        left += clientLeft + offsetLeft;
        top += clientTop + offsetTop;
        // 获取父参照物的父参照物
        parent = parent.offsetParent;
    }
    return {
        left,
        top
    };
};

四、IntersectionObserver(ES6新增的一个内置类)

  • 作用:监听事件
  • 用法:创建绑定事件后直接调用 ob.observe(监视元素) ob.unobverse(移除监听)
  • 不兼容IE浏览器
  • 执行过程:\
    • new InteresectionObserver(callback)创建一个它的实例;\
    • 创建一个监听器,用来监听一个或者多个DOM元素和浏览器可视窗口的交叉状态和信息;\
    • 当交叉状态(出现/离开可视窗口)发生改变【默认是“一露头”或者“完全出去”,可以基于第二个Options配置项中的threshold来指定规则:threshold:[0|1];0表示:一露头&玩完全出去;1表示:完全出去&出去一点】,都会触发监听器的回调函数callback执行;\
    • 在回调函数中可以获取所有监听的DOM和可视窗口的交叉信息;\
    • 回调函数:\
    • 形参changes:传的实参是一个数组,记录了每一个监听元素和可视窗口的交叉信息(解构赋值使用)\
    • boundingClienrRect:记录当前监听元素的getBoundingClientRect获取的值\
    • isInteresecting:true/false true代表出现在可视窗口中,false则反之\
    • target:储存当前监听的这个DOM元素对象\
    • ....
// 创建监听器
let ob = new IntersectionObserver((changes) => {
/* 
   回调函数执行:
       + 创建监听器、且监听了DOM元素会立即执行一次(连续监听多个DOM只触发一次,但是如果监听是分隔开的,每新监听一个元素都会触发执行一次)
       + 当监听的元素和可视窗口交叉状态改变,也会触发执行「默认是“一露头”或者“完全出去”,会触发;当然可以基于第二个Options配置项中的threshold来指定规则;」 
       + threshold: [0]  一露头&完全出去
       + ...
       + threshold: [1]  完全出现&出去一点
----
    changes:是一个数组,记录了每一个监听元素和可视窗口的交叉信息
        + boundingClientRect:记录当前监听元素的getBoundingClientRect获取的值
        + isIntersecting:true/false  true代表出现在可视窗口中,false则反之
        + target:存储当前监听的这个DOM元素对象
        + ...
            */
            console.log(changes);
        }, { threshold: [1] });

        // 监听某个DOM元素和可视窗口的交叉状态改变;unobserve移除监听;
        ob.observe(box1);
        ob.observe(box2);

五、图片懒加载(优化必做)

思路:

  • 最开始加载页面的时候,IMG的SRC不赋值(目的:不会加载真实照片,把真实图片的地址赋值给IMG的自定义属性,方便后期想要加载真实图片时候获取)
  • 如果SRC不赋值或者加载图片是错误的,会显示“碎图”,导致样式不美观,所以我们在最开始的时候让IMG隐藏(可以通过display、也可以设置透明度opacity;透明度改变可以设置过渡效果);
  • 给图片所在的盒子设置背景占位图(或者背景颜色),在真实图片没有加载之前,用其占位(盒子宽高事先设置好);
  • 啥时候加载?\
    • 当页面第一次渲染完(其他资源加载完成,例如:window.onload)\
    • 把出现在当前可是窗口内的图片进行加载
  • 如何加载?\
    • 获取图片的自定义属性值,拿到真实图片地址\
    • 给图片的SRC赋值真实地址:如果图片可以正常加载成功,则让IMG显示
// 图片懒加载
        const lazy = function lazy() {
            let trueImg = imgBox.getAttribute('data-img');
            imgBox.src = trueImg;
            imgBox.onload = () => {
                imgBox.style.opacity = 1;
            };
            // 设置自定义属性:当前图片已经处理过延迟加载了
            picBox.isLoad = true;
        };

        // 计算何时加载{完全出现}
        const computed = function computed() {
            // 如果之前已经处理过,则无需再次处理了
            if (picBox.isLoad) return;
            let C = HTML.clientHeight,
                { top: B, bottom: A } = picBox.getBoundingClientRect();
            if (A <= C && B >= 0) {
                lazy();
            }
        };
// 第一次其它资源加载完计算一次 & 页面滚动中随时计算
        //   + scroll事件会在浏览器滚动条滚动中进行触发,并且按照浏览器最快反应时间(一般5~7ms)的频率进行触发!!例如:我们滚动100ms,按照5ms触发一次,一共触发20次!! “触发频率太快了,造成了没必要的计算和性能消耗”
        //   + 此时我们需要“降低”触发频率「我们不是降低浏览器的触发频率,而是把computed函数执行的频率降下来」,我们此操作称之为“函数节流”!!
window.onload = computed;
window.onscroll = utils.throttle(computed, 100);	//throttle:utils的节流函数

 

  const picBox = document.querySelector('.pic-box'),
            imgBox = picBox.querySelector('img');
        // 延迟加载
        const lazy = () => {
            let trueImg = imgBox.getAttribute('data-img');
            imgBox.src = trueImg;
            imgBox.onload = () => {
                imgBox.style.opacity = 1;
            };
        };
        // 创建监听器监听pic-box,控制延迟加载:无需再进行复杂运算、也无需考虑函数节流...
        const ob = new IntersectionObserver(([item]) => {
            if (item.isIntersecting) {
                // 完全出现在视口中:延迟加载
                lazy();
                // 处理过的需要移除监听
                ob.unobserve(item.target);
            }
        }, { threshold: [1] });
        ob.observe(picBox);

utils防抖节流代码

📎utils.js