20230317----重返学习-js盒子模型-getBoundingClientRect-图片懒加载

88 阅读5分钟

day-029-twenty-nine-20230317-js盒子模型-getBoundingClientRect-图片懒加载

js盒子模型

  • JavaScript盒子模型的13个属性

    • offsetParent: 当前DOM元素设置了定位最近直系祖先级DOM元素

      • 或者最近的table元素,最近的td元素,最近的th元素,最近的body元素
      • offsetParent不一定是当前DOM元素的父元素
      • offsetParent根元素body元素
      • body元素offsetParentnull,而不是document.documentElement
    • offsetTop: 当前DOM元素距离最近直系祖先定位元素上内边距距离

    • offsetLeft: 当前DOM元素距离最近直系祖先定位元素左内边距距离

      • 得到当前DOM元素文档顶端高度

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Document</title>
            <style>
                *{
                    margin:0;
                    padding:0;
                }
                #outer{
                    height:500px;
                    width:500px;
                    background-color: blue;
                    margin:100px;
                    border:10px solid red;
                    position: relative;
                }
                #inner{
                    height:300px;
                    width:300px;
                    background-color: aquamarine;
                    margin:100px;
                    border:10px solid red;
                    position: relative;
                }
                #box{
                    height:100px;
                    width:100px;
                    background-color:coral;
                    margin:100px;
                    border:10px solid red;
                }
            </style>
        </head>
        <body>
            <div id="outer">
                <div id="inner">
                    <div id="box"></div>
                </div>
            </div>
        
            <script>
                let t1=box.offsetTop;
                console.log(t1);//100
                let p1=box.offsetParent;
                console.log(p1);//inner
                let w1=p1.clientTop;
        
                let t2=p1.offsetTop;
                console.log(t2);//100
                let p2=p1.offsetParent;
                console.log(p2);//outer
                let w2=p2.clientTop;
        
                let t3=p2.offsetTop;
                console.log(t3);//100
                let p3=p2.offsetParent;
                console.log(p3);//body
        
                console.log(p3.offsetParent);//null
        
                console.log(t1+w1+t2+w2+t3);
            </script>
        </body>
        </html>
        
    • clientTop: 当前DOM元素上边框宽度

    • clientLeft: 当前DOM元素左边框宽度

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Document</title>
          <style>
              #box{
                  height:100px;
                  width:100px;
                  background-color:coral;
                  margin:100px;
                  border-top:10px solid red;
                  border-left:20px solid red;
              }
          </style>
      </head>
      <body>
          <div id="box"></div>
        <script>
          console.log(box.clientTop);//10
          console.log(box.clientLeft);//20
        </script>
      </body>
      </html>
      
      • 拿到当前元素body的上边框高度

        • 以及当前元素body的左边框宽度

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Document</title>
              <style>
                  *{
                      margin:0;
                      padding:0;
                  }
                  #outer{
                      height:500px;
                      width:500px;
                      background-color: blue;
                      margin:100px;
                      border:10px solid red;
                      position: relative;
                  }
                  #inner{
                      height:300px;
                      width:300px;
                      background-color: aquamarine;
                      margin:100px;
                      border:10px solid red;
                      position: relative;
                  }
                  #box{
                      height:100px;
                      width:100px;
                      background-color:coral;
                      margin:100px;
                      border:10px solid red;
                  }
              </style>
          </head>
          <body>
              <div id="outer">
                  <div id="inner">
                      <div id="box"></div>
                  </div>
              </div>
          
              <script>
                function offset(ele){//ele-->box
                    let top=ele.offsetTop;//初始值(t1)
                    let left=ele.offsetLeft;
                    let parent=ele.offsetParent;//初始父级(inner/p1)
                    while(parent){//1.(inner/p1--true)  //2.(outer/p2)  //3.(body)  //4.null(false)
                        //1. top=t1+p1.offsetTop+p1.clientTop==>t1+t2+w1
                        //2. top= t1+t2+w1 + p2.offsetTop + p2.clientTop===》t1+t2+w1+t3+w2
                        //3. top= t1+t2+w1+t3+w2 + body.offsetTop(0)+body.clientTop(0)===》t1+t2+w1+t3+w2
                        top=top+parent.offsetTop+parent.clientTop;
                        left=left+parent.offsetLeft+parent.clientLeft;
                        //1.parent=p1.offsetParent===》(outer/2)
                        //2.parent=p2.offsetParent===》(body)
                        //3.parent=body.offsetParent==》null
                        parent=parent.offsetParent;
                    }
                    return {
                      top:top,
                      left:left
                    }
                }
          
                console.log(offset(box).top)//320
                console.log(offset(inner).left)//210
              </script>
          </body>
          </html>
          
    • offsetWidth: 内容宽度content+左内边距宽度padding-left+右内边距宽度padding-right+左边框宽度border-left+右边框宽度border-right

      • 标签盒模型: 内容宽度content=width
      • IE怪异盒模型: 内容宽度content=width -: 左内边距宽度padding-left -: 右内边距宽度padding-right
    • offsetHeight: 内容高度contentHeight+上内边距宽度padding-top+下内边距宽度padding-bottom+上边框宽度border-top+下边框宽度border-bottom

    • clientWidth 内容content宽度+左右padding-纵向滚动条占用的宽度

      • 不同浏览器滚动条宽度不太一样
    • clientHeight 内容content高度+上下padding-横向滚动条占用的高度

    • scrollWidth 当前DOM元素滚动条总宽度

      • 如果滚动内容未超出盒子,scrollWidth=clientWidth
      • 如果滚动内容超出盒子,实际内容就是多少scrollWidth就是多少
    • scrollHeight 当前DOM元素滚动条总高度

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <style>
              *{
                  margin:0;
                  padding:0;
              }
              #box{
                  height:200px;
                  width:100px;
                  background-color:coral;
                  margin:100px;
                  padding:20px;
                  border:10px solid red;
                  overflow: auto;
              }
          </style>
      </head>
      <body>
          <pre id="box">
              #box{
                  height:200px;
                  width:100px;
                  background-color:coral;
                  margin:100px;
                  padding:20px;
                  border:10px solid red;
                  overflow: auto;
              }
      
      
      
      
      
      
      
      
          </pre>
        <script>
          //标准盒模型 content--->width/height
          //IE盒子模型 content---》content
          // (11)
          //  offsetWidth:content+左右padding+左右border
          //  offsetHeight:contet+上下padding+上下border
          //  clientWidth:content+左右padding(-滚动条)
          //  clientHeight:contet+上下padding(-滚动条)
          //  scrollWidth:如果滚动内容未超出盒子scrollWidth=clientWidth
          //               如果滚动内容超出盒子,实际内容是多少scrollWidth就是多少
          //  scrollHeight:如果滚动内容未超出盒子scrollHeight=clientHeight
          //               如果滚动内容超出盒子,实际内容是多少scrollHeight就是多少
      
          console.log(box.offsetWidth);//160//100+40+20
          console.log(box.offsetHeight);//260//200+40+20
      
          console.log(box.clientWidth);//123//100+40-(纵向滚动条占用的宽度)
          console.log(box.clientHeight);//223//200+40-(横向滚动条占用的高度)
      
          console.log(box.scrollWidth);//285//横向滚动内容超出盒子后的实际宽度
          console.log(box.scrollHeight);//310//纵向滚动内容超出盒子后的实际高度
        </script>
      </body>
      </html>
      
    • scrollTop 滚动条滚动过去的上下距离

      • 文档的已滚动高度

        • (document.documentElement||document.body).scrollTop

          • 一般浏览器document.documentElement,页面滚动条就在它里面

            • IE可能用document.bodyIEdocument.documentElement
          • 一般浏览器也有document.body,但页面滚动条不在它里面

          • document.documentElement要放前面

    • scrollLeft 滚动条滚动过去的左右距离

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Document</title>
          <style>
              *{
                  margin:0;
                  padding:0;
              }
              body{
                  height:3000px;
                  background-image: linear-gradient(to bottom,red,blue,green);
              }
              #box{
                  height:100px;
                  width:100px;
                  background-color:coral;
                  margin:100px;
                  padding:20px;
                  border:10px solid red;
                  overflow: auto;
                  position: fixed;
                  right:50px;
                  bottom: 50px;
                  /* display: none; */
                  opacity:0;
                  transition: all 1s;
              }
          </style>
      </head>
      <body>
          <div id="box">
              返回顶部
          </div>
        <script>
          // scrollTop:滚动过去的距离(上下滚动)
          // scrollLeft:滚动过去的距离(左右滚动)
      
          let html=document.documentElement||document.body;
          box.onclick=function(){
              html.scrollTop=0
          }
      
          //window/document
          //滚动事件 scroll
          window.onscroll=function(){
            if(html.scrollTop>=150){//滚动距离大于150,显示盒子
                box.style.opacity=1
            }else{//滚动距离小于150,隐藏盒子
                box.style.opacity=0
            }
          }
          
        </script>
      </body>
      </html>
      
  • 对象中,keyvalue一致,可以省略一个

    • 键值对属性名作为属性值的变量名一致,那么可以省略为一个
  • getBoundingClientRect()

    • 当前DOM元素可视窗口的信息

      • ES6新增
    • 返回值DOMRect{}对象

      • .getBoundingClientRect().x

        • 值可以是负数
      • .getBoundingClientRect().y

      • .getBoundingClientRect().width

      • .getBoundingClientRect().height

      • .getBoundingClientRect().top:元素上边到视窗上边的距离;

      • .getBoundingClientRect().right:元素右边到视窗左边的距离;

      • .getBoundingClientRect().bottom:元素下边到视窗上边的距离;

      • .getBoundingClientRect().left:元素左边到视窗左边的距离

    • 数值之间的关系

      • DOM元素对象.getBoundingClientRect().right===DOM元素对象.getBoundingClientRect().left+DOM元素对象.getBoundingClientRect().width //true
      • DOM元素对象.getBoundingClientRect().bottom===DOM元素对象.getBoundingClientRect().top+DOM元素对象.getBoundingClientRect().height //true
      • DOM元素对象.getBoundingClientRect().top===DOM元素对象.getBoundingClientRect().y //true
      • DOM元素对象.getBoundingClientRect().left===DOM元素对象.getBoundingClientRect().x //true
  • 滚动事件

    • 页面目标对象 window、document
    • window.onscroll
  • 获取样式

    • DOM元素对象.style.xxx

      • 通过DOM元素对象.style.xxx只能获取行内样式
    • window.getComputedStyle(DOM元素对象)

      • 获取元素的任何形式的计算后的样式,返回一个CSSStyleDeclaration对象

      • 中括号语法的适用范围要比点语法更广

        • 中括号语法中的属性名可以用驼峰命名法,也能使用-连接符命名式
        • 点语法一般只能用驼峰命名法来访问对象成员
        • 方法封装中,一般能用中括号语法,就不要使用点语法
        <!DOCTYPE html>
        <html lang="en">
          <head>
            <meta charset="UTF-8" />
            <title>复习</title>
            <style>
              .box {
                background-color: blue;
              }
            </style>
          </head>
          <body>
            <div style="color: red" class="box">12345</div>
          </body>
        </html>
        
        <script>
          // 通过DOM元素对象.style.xxx只能获取行内样式
          let box = document.querySelector(".box");
          console.log(box.style.color);
          console.log(box.style.backgroundColor);
        
          //获取元素的任何形式的样式
          // 样式是计算后的
          // []中括号语法的适用范围更广
          console.log(getComputedStyle(box).color);
          console.log(window.getComputedStyle(box).backgroundColor);
          console.log(window.getComputedStyle(box)["backgroundColor"]); //中括号语法可以用驼峰命名法
          console.log(window.getComputedStyle(box)["background-color"]); //中括号语法也可以使用-分隔符连接法
        
          // IE获取元素的任何计算后的样式
          // console.log(box.currentStyle("background-color"));//一般用于兼容IE,但2023年应该不用兼容了
        
          // 封装获取样式
          function getCss(element, styleName) {
            return window.getComputedStyle(element)[styleName];
          }
          console.log(getCss(box, "background-color"));
        
          //封装设置样式
          const setCss = function setCss(element, styleObject) {
            for (let key in styleObject) {
              element.style[key] = styleObject[key];
            }
          };
          let obj = {
            fontSize: "50px",
            border: "10px solid red",
            borderRadius: "50%",
          };
          setCss(box, obj);
        </script>
        
  • 设置样式

    • 使用DOM元素的style属性,直接设置元素的内联样式

      const element = document.getElementById('my-element');
      element.style.color = 'red';
      
    • 使用DOM元素的classList属性,通过添加/移除类名的方式来设置样式

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <title>设置css样式</title>
          <style>
            .active{
              color: red;
              font-size: 16px;
            }
          </style>
        <body>
          <div id="my-element">
            555
          </div>
        </body>
      <script>
      const element = document.getElementById('my-element');
      element.classList.add('active');
      </script>
      </html>
      
    • 使用setAttribute()方法,通过设置元素的style属性来添加CSS样式

      const element = document.getElementById('my-element');
      element.setAttribute('style', 'color: red; font-size: 16px;');
      
    • 动态创建一个style元素,将CSS样式插入到其中

      const style = document.createElement('style');
      style.innerHTML = `
        #my-element {
          color: red;
          font-size: 16px;
        }
      `;
      document.head.appendChild(style);
      
  • 封装获取样式方法

    // 封装获取样式
    function getCss(element, styleName) {
      return window.getComputedStyle(element)[styleName];
    }
    console.log(getCss(document.body, "background-color"));
    
  • 封装设置样式

    //封装设置样式
    const setCss = function setCss(element, styleObject) {
      for (let key in styleObject) {
        element.style[key] = styleObject[key];
      }
    };
    let obj = {
      fontSize: "50px",
      border: "10px solid red",
      borderRadius: "50%",
    };
    setCss(document.body, obj);
    

图片懒加载

  • 总体思路

    1. 不立刻给src赋值,到达某个条件后再加载。

      • 因为只是给图片标签的src属性赋值,图片就会立即加载,占用带宽。
    2. 先设置一个自定义属性如data-src存储真实的图片地址

    3. 设置一个父级盒子给图片占位置,父级盒子设置背景颜色或图片,未来存放图片

    4. 隐藏裂掉的图片 img[src=""]{ display: none; }

    5. 时机到了之后,将自定义属data-src的属性值赋值给src

      • 时机举例

        • 页面内容都加载完毕后,最后加载图片—window.onload事件

          console.log(document.querySelector('body'))
          window.onload=function(){
            console.log(document.querySelector('body'))
          }
          
        • 点击图片所在格子后加载

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>9.图片懒加载1</title>
        <style>
          * {
            margin: 0;
            padding: 0;
          }
          .img-box {
            width: 300px;
            height: 400px;
            background-color: #ccc;
            background-image: url("");
          }
          .img-box img {
            width: 300px;
            height: 400px;
          }
          img[src=""] {
            display: none;
          }
        </style>
      </head>
      <body>
        <div class="box"></div>
        <!-- 1. 因为只是给图片的src属性赋值,图片就会立即加载,占用带宽。 故而不立刻给src赋值,到达某个条件后再加载。
        2. 先设置一个自定义属性如 data-src 存储真实的图片地址
        3. 设置一个盒子,给图片占位置(设置背景颜色或图片),未来存放图片
        4. 隐藏裂掉的图片 img[src=""]{ display: none; }
        5. 时机到了之后,将自定义属data-src的属性值赋值给src
        (如时机:页面内容都加载完毕后,最后加载图片---window.onload事件)
        (如时机:点击页面时加载)
      -->
        <!-- <img src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/458adc268050c17312da7c12328395e8.jpg" alt=""> -->
        <!-- <img src data-src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/458adc268050c17312da7c12328395e8.jpg" alt=""> -->
        <div class="img-box">
          <img
            src
            data-src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/458adc268050c17312da7c12328395e8.jpg"
            alt=""
          />
        </div>
        <h1>h1标签</h1>
      </body>
    </html>
    
    <script>
      const getImage = () => {
        let img = document.querySelector(".img-box img");
        var data = img.getAttribute("data-src");
    
        console.log(data);
        // img.setAttribute("src", data);//核心DOM操作-无兼容问题,繁琐
        img.src = data; //HTML DOM操作,可能有兼容问题,2023目前暂时无了,操作简单
        img.removeAttribute("data-src");
      };
      window.onload = function () {
        getImage();
      };
      /* let imgBox = document.querySelector(".img-box");
      imgBox.onclick = () => {
        getImage();
      }; */
    </script>
    
  • 升级图片懒加载

    • 如果data-src的图片路径是错误的,后面不再执行。

    • 判断图片路径是对的

      • 先创建一个新的img元素对象,利用新的img元素对象的src属性进行测试,之后利用img元素对象的onload事件。

        • 图片的onload事件,图片路径正确并请求完图片才会执行。
    • 判断图片路径是图片路径错误的

      • 先创建一个新的img元素对象,利用新的img元素对象的src属性进行测试,之后利用img元素对象的onerror事件。

        let newImage = document.createElement('img')
        // 图片的onload事件,图片路径正确并请求完图片才会执行。
        newImage.onload=()=>{
          console.log('图片路径正确')
        }
        newImage.onerror=()=>{
          console.log('图片路径错误')
        } 
        newImage.src = '//www.baidu.com/img/flexible/logo/pc/result@2.png'
        
        let newImage = document.createElement('img')
        newImage.onload=function(){
          console.log('图片路径正确')
          newImage=null
        }
        newImage.onerror=function(e,e1){
          console.log('图片路径错误',e,this,e1,newImage.src)//可以在这把图片的路径报告给后端
          newImage=null
        
        } 
        newImage.src = '//www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png1'
        
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
            <style>
              .imgbox{
                  width:300px;
                  height:400px;
                  background: #ccc url(img/default.gif) center no-repeat;
              }
              .imgbox img{
                  width:300px;
                  height:400px;
              }
              img[src=""]{
                display: none;
              }
            </style>
        </head>
        <body>
            <div class="imgbox">
                <img src="" data-src="img/1.jpg" alt="">
            </div>
        
            <script>
                //升级,如果data-src的图片路径是错误的,后面不在执行
                //怎么判断图片路径是对的那?
                //先创建一个新的img,利用新的img 的src属性测试(探兵)
                // var newimg=document.createElement("img");
                // newimg.src="img/1.jpg"//图片路径正确,继续执行代码 //路径错误报错,不在执行
                // //图片的onload 的事件,图片路径正确才会执行
                // newimg.οnlοad=function(){
                //     console.log("图片路径正确")
                // }
                
        
                var img=document.querySelector(".imgbox img");
                window.onload=function(){
                    var dataSrc=img.getAttribute("data-src");
                    var newimg=new Image();
                    newimg.src=dataSrc;
                    newimg.onload=function(){
                        img.src=dataSrc;
                        img.removeAttribute("data-src");
                        newimg=null;
                    }
                }
            </script>
        
        </body>
        </html>
        
  • 升级图片懒加载

    • 视口底部超过当前元素后再懒加载

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <style>
              *{
                  margin:0;
                  padding:0;
              }
              body{
                  height:3000px;
              }
            .imgbox{
                width:300px;
                height:400px;
                margin-top:1300px;
                margin-left:100px;
                background: #ccc url(img/default.gif) center no-repeat;
            }
            .imgbox img{
                width:300px;
                height:400px;
            }
            img[src=""]{
              display: none;
            }
          </style>
      </head>
      <body>
          <div class="imgbox">
              <img src="" data-src="img/1.jpg" alt="">
          </div>
      
          <script>
            var html=document.documentElement||document.body;
            var imgbox=document.querySelector(".imgbox");
            var img=document.querySelector(".imgbox img");
            window.onscroll=function(){
                var ch=html.clientHeight;
                var boxh=imgbox.getBoundingClientRect().bottom;
                if(ch>=boxh){//加载图片
                  //加载图片之前,判断一下是否有flag 属性,值为true
                  //说明图片已将加载成功了,不需要再加载
                  if(img.flag){return}
                  showimg()
                }
            }
      
            function showimg(){
                console.log("111");
                var dataSrc=img.getAttribute("data-src");
                var newimg=new Image();
                newimg.src=dataSrc;
                newimg.onload=function(){
                  img.src=dataSrc;
                  img.removeAttribute("data-src");
                  newimg=null;
                  img.flag=true;//如果第一设置上图片后,给img设置一个自定义的属性flag=true
                }
            }
          </script>
      </body>
      </html>
      

进阶参考