当前位置: 首页 > article >正文

JS | JS之元素偏移量 offset 系列属性详解

目录

一、offset 概述

定位父级

offsetParent 

偏移量

offsetWidth

offsetHeight

offsetLeft

offsetTop

计算页面偏移

注意事项

二、offset 与 style 区别

偏移offset

样式style

三、案例

★ 案例:获取鼠标在盒子内的坐标

★ 案例:模态框拖拽

★ 案例:仿京东放大镜

四、番外:元素视图方法


偏移量(offset dimension)是javascript中的一个重要的概念。涉及到偏移量的主要是offsetLeft、offsetTop、offsetHeight、offsetWidth这四个属性。当然,还有一个偏移参照——定位父级offsetParent。本文将详细介绍该部分内容。

一、offset 概述

offset 翻译过来就是偏移量, 我们使用 offset系列相关属性可以动态的得到该元素的位置(偏移)、大小等。

  • 获得元素距离带有定位父元素的位置

  • 获得元素自身的大小(宽度高度)

注意返回的数值都不带单位

♠ offset 系列常用属性 ♠

以上五个偏移量属性,它们都是只读属性。如果元素设置了display:none,则它的偏移量属性均为0。每次访问偏移量属性都需要重新计算,重复访问偏移量属性需耗费大量性能,如需重复访问,应将其值保存在变量中,以提高性能。 

参考分析图

示例代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      .father {
        margin: 100px;
        width: 200px;
        height: 200px;
        background-color: pink;
      }

      .son {
        margin-left: 30px;
        width: 100px;
        height: 100px;
        background-color: purple;
      }

      .box {
        margin: 0 100px;
        width: 200px;
        height: 200px;
        padding: 20px;
        background-color: skyblue;
        border: 10px solid red;
      }
    </style>
  </head>

  <body>
    <div class="father">
      <div class="son"></div>
    </div>
    <div class="box"></div>
    <script>
      // offset 系列
      let father = document.querySelector(".father");
      let son = document.querySelector(".son");

      // 1. offsetParent返回带有定位的父亲,否则返回的是body
      console.log(son.offsetParent); // 返回<body>...</body>
      // 注意:offsetParent与parentNode的值不一定相等
      // 返回最近一级的父亲(亲爸爸) 不管父亲有没有定位
      console.log(son.parentNode); // 返回<div class="father">...</div>

      // 2. 可以得到元素的偏移量(位置),返回的是不带单位的数值
      console.log(father.offsetTop); // 100 ,因为css为其设置的margin为100
      console.log(father.offsetLeft); // 100 ,因为css为其设置的margin为100
      // 它以带有定位的父亲为准,如果没有父亲或者父亲没有定位 则以 body 为准
      console.log(son.offsetLeft); // 130 ,因为虽然其有父亲,但是其父亲没有定位,所以以body为准,即100+30=130
     
     // 3. 可以得到元素的大小(宽度和高度),是包含padding + border + width/height的,不包括margin
      let box = document.querySelector(".box");
      console.log(box.offsetWidth); // 260
      console.log(box.offsetHeight); // 260
    </script>
  </body>
</html>

下面我们把offset的5个偏移属性,分别一一介绍一下

定位父级

offsetParent 

offsetParent属性是一个偏移参照,翻译为定位父级,即当前元素最近的有定位的父级元素(position不等于static)。定位父级分为下列四种情况。

在理解偏移大小之前,首先要理解offsetParent。人们并没有把offsetParent翻译为偏移父级,而是翻译成定位父级,很大原因是offsetParent与定位有关。

(1)当前元素自身有fixed定位,offsetParent返回null

当元素自身有fixed固定定位时,我们知道固定定位的元素相对于视口进行定位,此时没有定位父级,offsetParent的结果为null。

注意:firefox浏览器有兼容性问题——firefox没有考虑固定定位问题,返回body元素

<!-- 元素自身有fixed定位,offsetParent的结果为null -->
<div id="box" style="position:fixed;"></div>
<script>
    let box = document.querySelector('#box');
    console.log(box.offsetParent); // null
    //兼容问题:firefox浏览器没有考虑固定定位的问题,返回body元素,其他浏览器都返回null
</script>

(2)当前元素自身无fixed定位,且不存在有定位的父级元素,offsetParent返回body元素

<!-- 元素自身无fixed定位,且父级元素都未经过定位,offsetParent的结果为<body> -->
<div>
    <span id="box"></span>
</div>
<script>
    let box = document.querySelector('#box');
    console.log(box.offsetParent); // <body>...</boby>
</script>

(3)当前元素自身无fixed定位,且存在有定位的父级元素,offsetParent返回最近的有定位的父级元素

<!--元素自身无fixed定位,且父级元素存在经过定位的元素,offsetParent的结果为离自身元素最近的经过定位的父级元素-->
<div id="div0" style="position:absolute;">
  <div id="div1" style="position:absolute;">
    <span id="box"></span>
  </div>
</div>
<script>
    let box = document.querySelector('#box');
    console.log(span.offsetParent); // <div id="div1" style="position:absolute;">...</div>
</script>

(4)body元素无父元素节点,offsetParent返回null 

<!-- <body>元素的parentNode是null -->
<script>
    console.log(document.body.offsetParent); // null
</script>

★ IE7 浏览器Bug

对于定位父级offsetParent来说,IE7-浏览器存在以下bug

【bug1】元素本身经过绝对定位或相对定位,且父级元素无定位时,IE7-浏览器下offsetParent返回的结果是<html>

<div id="test" style="position:absolute;"></div>    
<script>
//IE7-浏览器返回<html>,其他浏览器返回<body>
let box = document.querySelector('#test');
console.log(box.offsetParent);
</script>
<div id="test" style="position:relative;"></div>    
<script>
//IE7-浏览器返回<html>,其他浏览器返回<body>
let box = document.querySelector('#test');
console.log(box.offsetParent);
</script>
<div id="test" style="position:fixed;"></div>    
<script>
// firefox并没有考虑固定定位的问题,返回<body>,其他浏览器都返回null
let box = document.querySelector('#test');
console.log(box.offsetParent);
</script>

 【bug2】如果父级元素存在触发haslayout的元素或有定位的元素,则offsetParent的结果为离自身元素最近的有定位或触发haslayout的父级元素

注意:关于haslayout的详细信息haslayout详解 - 小火柴的蓝色理想 - 博客园

<div id="div0" style="display:inline-block;">
    <div id='test'></div>    
</div>
<script>
let box = document.querySelector('#test');
//IE7-浏览器返回<div id="div0">,其他浏览器返回<body>
console.log(box.offsetParent);
</script>
<div id="div0" style="position:absolute;">
    <div id="div1" style="display:inline-block;">
        <div id='test'></div>    
    </div>    
</div>
<script>
let box = document.querySelector('#test');
//IE7-浏览器返回<div id="div1">,其他浏览器返回<div id="div0">
console.log(box.offsetParent);
</script>
<div id="div0" style="display:inline-block;">
    <div id="div1" style="position:absolute;">
        <div id='test'></div>    
    </div>    
</div>
<script>
let box = document.querySelector('#test');
//所有浏览器都返回<div id="div1">
console.log(box.offsetParent);
</script>

偏移量

偏移量共包括offsetHeight、offsetWidth、offsetLeft、offsetTop这四个属性

参考:彻底搞懂clientHeight、offsetHeight、scrollHeight的区别

offsetWidth

获取dom元素视觉上的宽度,返回结果是一个数字,单位像素。(不包含外边距

offsetWidth表示元素在水平方向上占用的空间大小,无单位(以像素px计)

<script>
    // 边框/内边距/宽/垂直滚动条
    element.offsetWidth = border + padding + width + scroll 
<script>
offsetHeight

获取dom元素视觉上的高度,返回结果是一个数字,单位像素。(不包含外边距

offsetHeight表示元素在垂直方向上占用的空间大小,无单位(以像素px计)

<script>
    // 边框/内边距/高/水平滚动条
    element.offsetWidth = border + padding + height + scroll; 
<script>

示例:offsetWidth / offsetHeight

<!DOCTYPE html>
<html lang="en">
  <head>
    <title></title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      div {
        width: 100px;
        height: 200px;
        padding: 10px;
        margin: 20px;
        border: 5px solid black;
      }
    </style>
  </head>

  <body>
    <div id="box"></div>
    <script>
      let box = document.querySelector("#box");
      // element.offsetWidth =  border-left-width + padding-left + width + padding-right + border-right-width; 
      console.log(box.offsetWidth); // 130=5*2+10*2+100
      // element.offsetHeight =  border-top-width + padding-top + height + padding-bottom + border-bottom-width
      console.log(box.offsetHeight); // 130=5*2+10*2+200
    </script>
  </body>
</html>

注意:如果存在垂直滚动条,offsetWidth也包括垂直滚动条的宽度;如果存在水平滚动条,offsetHeight也包括水平滚动条的高度

有滚动条时,offsetWidth和offsetHeight包含滚动条的宽度。IE8浏览器将垂直和水平滚动条的宽度计算在width宽度和height高度中,所以出现滚动条时width和height不变,而其他浏览器则是把垂直滚动条的宽度从width宽度中移出,把水平滚动条的高度从height高度中移出。

<!DOCTYPE html>
<html lang="en">
  <head>
    <title></title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      #box {
        width: 100px;
        height: 100px;
        padding: 10px;
        margin: 10px;
        border: 1px solid black;
        overflow: scroll;
      }
    </style>
  </head>

  <body>
    <div id="box"></div>
    <script>
      //IE8-浏览器将垂直滚动条的宽度计算在width宽度和height高度中,width和height的值仍然是100px;
      //而其他浏览器则把垂直滚动条的宽度从width宽度中移出,把水平滚动条的高度从height高度中移出,因为滚动条默认宽度为17px,所以width宽度和height高度为剩下的83px
      let box = document.querySelector("#box");
      if (window.getComputedStyle) {
         // 83px 83px
        console.log(
          getComputedStyle(box).width, 
          getComputedStyle(box).height
        );
      } else {
        //ie8及以下版本的浏览器
        console.log(box.currentStyle.width, box.currentStyle.height); //100px
      }
      // 122=border*2+padding*2+100
      console.log(box.offsetWidth, box.offsetHeight); 
    </script>
  </body>
</html>

执行结果,如下图:

有童鞋可能会疑惑,宽度和高度不应该是83px吗?怎么各自多出了0.2px?我们看一下浏览器中呈现的样式。

盒子模型中,元素宽高的确是多出了0.2px,而且边框少了0.2,这是因为我的电脑系统缩放与布局调的是125%,这就是症结所在。所以将系统缩放调整到100%,这样浏览器就可以正确显示‌了。

补:currentStyle是一个在旧版本的IE浏览器中引入的属性,用于获取元素计算后的样式。‌ 它现在已经被废弃,仅支持IE浏览器使用‌。

currentStyle属性用于获取指定元素的当前样式,返回的是一个样式属性对象。它可以查询元素的宽度、高度、文本对齐方式、位置等所有CSS属性值。然而,这个属性仅能在IE浏览器中使用,如果需要在其他浏览器(如Firefox)中实现相同的效果,应使用getComputedStyle属性‌。

补:浏览器滚动条默认的宽度和高度通常是17px。‌ 这是因为在IE7及以上版本的IE浏览器、Chrome和Firefox中,滚动栏所占用的宽度都是17px‌。

不同浏览器的默认滚动条宽度可能有所不同,例如在Chrome或Safari中,可以通过设置::-webkit-scrollbar的宽度来调整滚动条的宽度。例如:

::-webkit-scrollbar {
  width: 4px;
}

这段代码将Chrome或Safari浏览器的滚动条宽度设置为4px‌。

在Firefox中,可以通过设置scrollbar-width属性来隐藏或显示滚动条。例如:

scrollbar-width: none;

这段代码将隐藏Firefox浏览器的滚动条‌。

在IE浏览器中,可以通过设置-ms-overflow-style属性来隐藏滚动条。例如:

-ms-overflow-style: none;

这段代码将隐藏IE浏览器的滚动条‌

番外:网页尺寸offsetHeight、offsetWidth
offsetHeight和offsetWidth,获取网页内容高度和宽度(包括滚动条等边线,会随窗口的显示大小改变)。

① 值

offsetHeight = clientHeight + 水平滚动条 + 边框。
offsetWidth = clientWidth + 滚动条 + 边框。

② 浏览器兼容性

var w= document.documentElement.offsetWidth || document.body.offsetWidth;
var h= document.documentElement.offsetHeight || document.body.offsetHeight;

offsetLeft

获取当前元素与其最近的有定位父级元素之间的x轴距离(计算左上顶点)。若当前元素没有有定位父级元素,获取其距离文档的x轴距离。返回结果是一个数字,单位像素。

offsetLeft表示元素的左外边框至offsetParent元素的左内边框之间的像素距离

offsetTop

获取当前元素与其最近的有定位父级元素之间的y轴距离(计算左上顶点)。若当前元素没有有定位的父级元素,获取其距离文档的y轴距离。返回结果是一个数字,单位像素。

offsetTop表示元素的上外边框至offsetParent元素的上内边框之间的像素距离

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      div#out {
        padding: 5px;
        position: relative;
        background-color: red;
        margin: 6px;
        border: 1px solid black;
      }

      div#inline {
        width: 50px;
        height: 50px;
        margin: 10px;
        background-color: green;
      }
    </style>
  </head>

  <body>
    <div id="out">
      <div id="inline"></div>
    </div>
    <script>
      //parseInt(getComputedStyle(inline,null)['marginTop']) +
      //parseInt(getComputedStyle(out,null)['paddingTop'])=10+5=15
      console.log(inline.offsetTop); //15
      //parseInt(getComputedStyle(inline,null)['marginLeft']) +
      //parseInt(getComputedStyle(out,null)['paddingLeft'])=10+5=15
      console.log(inline.offsetLeft); //15
    </script>
  </body>
</html>

★ IE7 浏览器Bug

IE7-浏览器在offsetTop属性的处理上存在bug

【1】若父级设置position: relative,则在IE7-浏览器下,offsetTop值为offsetParent元素的paddingBottom值

<div id="out" style="padding: 5px;position: relative;">
    <div id="test" style="width:100px; height:100px; margin:10px;"></div>        
</div>
<script>
// 其他浏览器返回15(5+10),而IE7-浏览器返回5
console.log(test.offsetTop);
</script>

【2】若父级设置position: aboslute(或其他触发haslayout的条件),offsetTop值为offsetParent元素的paddingBottom值和当前元素的marginTop值的较大值

<div id="out" style="padding: 5px;position:absolute;">
    <div id="test" style="width:100px; height:100px; margin:10px;"></div>        
</div>
<script>
// 其他浏览器返回15(5+10),而IE7-浏览器返回10(10和5的较大值)
console.log(test.offsetTop);
</script>

—— 小结:偏移量 ——

偏移量:包括元素在屏幕上占用的所有可见空间,元素的可见大小有其高度,宽度决定,包括所有内边距,滚动条和边框大小(注意,不包括外边距)。

以下4个属性可以获取元素的偏移量

1. offsetHeight:元素在垂直方向上占用的空间大小,以像素计。包括元素的高度(可见的),水平滚动条的高度,上边框高度和下边框高度。

2. offsetWidth:元素在水平方向上占用的空间大小,以像素计。包括元素的宽度(可见的),垂直滚动条的宽度,左边框宽度和右边框宽度。

3: offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。

4: offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。

其中offsetLeft,offsetTop属性与包含元素有关,包含元素的引用保存在offsetParent中,请注意offsetParent与parentNode的值不一定相等

计算页面偏移

计算元素在页面上的偏移量只需将当前元素的offsetLeft和offsetTop与其offsetParent的相同属性相加,再加上offsetParent相应方向的边框,如此一直循环到根元素,就可以得到元素到页面的偏移量。

注意:在默认情况下,IE8 浏览器下如果使用currentStyle()方法获取<html>和<body>(甚至普通div元素)的边框宽度都是medium,而如果使用clientLeft(或clientTop)获取边框宽度,则是实际的数值

function getElementLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while(current != null){
        actualLeft += current.offsetLeft + current.clientLeft;
        current = current.offsetParent;
    }
    return actualLeft + 'px';
}
function getElementTop(element){
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while(current != null){
        actualTop += current.offsetTop + current.clientTop;
        current = current.offsetParent;
    }
    return actualTop + 'px';
}

完整示例代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      html,
      body {
        border: 0;
      }

      body {
        margin: 0;
      }
    </style>
  </head>

  <body>
    <div style="padding: 20px; border: 1px solid black; position: absolute">
      <div id="test" style="width: 100px; height: 100px; margin: 10px"></div>
    </div>
    <script>
      function getElementLeft(element) {
        var actualLeft = element.offsetLeft;
        var current = element.offsetParent;
        while (current != null) {
          actualLeft += current.offsetLeft + current.clientLeft;
          current = current.offsetParent;
        }
        return actualLeft + "px";
      }

      function getElementTop(element) {
        var actualTop = element.offsetTop;
        var current = element.offsetParent;
        while (current != null) {
          actualTop += current.offsetTop + current.clientTop;
          current = current.offsetParent;
        }
        return actualTop + "px";
      }

      //其他浏览器返回31(10+20+1),而IE7-浏览器返回21((20和10的较大值)+1)
      console.log(getElementTop(test));
      //所有浏览器返回31(10+20+1)
      console.log(getElementLeft(test));
    </script>
  </body>
</html>

注意事项

【1】所有偏移量属性都是只读的

<div id="test" style="width:100px; height:100px; margin:10px;"></div>        
<script>
console.log(test.offsetWidth);//100
//IE8-浏览器会报错,其他浏览器则静默失败
test.offsetWidth = 10;
console.log(test.offsetWidth);//100
</script>

【2】如果给元素设置了display:none,则它的偏移量属性都为0

<div id="test" style="width:100px; height:100px; margin:10px;display:none"></div>
<script>
console.log(test.offsetWidth);//0
console.log(test.offsetTop);//0
</script>

 【3】每次访问偏移量属性都需要重新计算

<div id="test" style="width:100px; height:100px; margin:10px;"></div>        
<script>
console.time("time");
for(var i = 0; i < 100000; i++){
    var a = test.offsetWidth;
}
console.timeEnd('time');//65.129ms
</script>
<div id="test" style="width:100px; height:100px; margin:10px;"></div>        
<script>
console.time("time");
var a = test.offsetWidth;
for(var i = 0; i < 100000; i++){
    var b = a;
}
console.timeEnd('time');//1.428ms
</script>

由上面代码对比可知,重复访问偏移量属性需要耗费大量的性能,所以要尽量避免重复访问这些属性。如果需要重复访问,则把它们的值保存在变量中,以提高性能。


—— 小结:‌offset偏移属性 ——

‌offset偏移主要包括以下几个属性:​ offsetLeft、offsetTop、offsetWidth、offsetHeight ​和offsetParent。‌

  • offsetLeft‌:表示元素的左外边框至包含元素的左内边框之间的像素距离。
  • offsetTop‌:表示元素的上外边框至包含元素的上内边框之间的像素距离。
  • offsetWidth‌:表示元素在水平方向上占用的空间大小,包括元素的宽度(可见的)、垂直滚动条的宽度、左边框宽度和右边框宽度。
  • offsetHeight‌:表示元素在垂直方向上占用的空间大小,包括元素的高度(可见的)、水平滚动条的高度、上边框高度和下边框高度。
  • offsetParent‌:表示当前元素最近的有定位的父级元素,即position不等于static的父元素。如果元素自身有fixed定位,offsetParent结果为null;如果元素自身无fixed定位且父级元素存在经过定位的元素,offsetParent的结果为离自身元素最近的经过定位的父级元素。

这些属性用于获取或设置元素相对于其最近祖先或文档边界框的偏移量,常用于动态获取元素的位置和大小信息‌。 

二、offset 与 style 区别

偏移offset

  • offset 可以得到任意样式表中的样式值

  • offset 系列获得的数值是没有单位的

  • offsetWidth 包含padding+border+width,不含外边距

  • offsetWidth 等属性是只读属性,只能获取不能赋值

所以,我们想要获取元素大小位置,用offset更合适

样式style

  • style 只能得到行内样式表中的样式值

  • style.width 获得的是带有单位的字符串

  • style.width 获得不包含padding和border 的值

  • style.width 是可读写属性,可以获取也可以赋值

所以,我们想要给元素更改值,则需要用style改变

<body>
    <div class="box" style="width: 200px;"></div>
    <script>
        // offset与style的区别
        var box = document.querySelector('.box');
        console.log(box.offsetWidth); // 不带单位的数字 paddiing+border+width
        console.log(box.style.width); // 只能得到行内样式 带单位的字符串 width
        // box.offsetWidth = '300px'; // 只能获取不能赋值
        box.style.width = '300px'; // 可以获取可以赋值
        // offset更合适获取元素大小位置
        // style更适合给元素更改值
    </script>
</body>

三、案例

★ 案例:获取鼠标在盒子内的坐标

  1. 我们在盒子内点击,想要得到鼠标距离盒子左右的距离。
  2. 首先得到鼠标在页面中的坐标(e.pageX, e.pageY)
  3. 其次得到盒子在页面中的距离 ( box.offsetLeft, box.offsetTop)
  4. 用鼠标距离页面的坐标减去盒子在页面中的距离,得到 鼠标在盒子内的坐标
  5. 如果想要移动一下鼠标,就要获取最新的坐标,使用鼠标移动
var box = document.querySelector(".box");
box.addEventListener("mousemove", function (e) {
  var x = e.pageX - this.offsetLeft;
  var y = e.pageY - this.offsetTop;
  this.innerHTML = "x坐标是" + x + " y坐标是" + y;
});

:e.pageX、e.clientX、e.screenX、e.offsetX的区别

e.pageX,e.pageY:返回的值是相对于文档的定位,文档的左上角为(0,0),向右为正,向下为正,IE不支持(不包含上方工具栏);

e.screenX,e.screenY:返回的是相对于屏幕的坐标,浏览器上面的工具栏(包含上方工具栏);

e.clientX,e.clientY:返回的值是相对于屏幕可见区域的坐标,如果页面有滚动条,呗滚动条隐藏的那部分不进行计算,也可以说是相对于屏幕的坐标,但是不计算上方的工具栏(不包含上方工具栏);

e.offsetX,e.offsetY:返回的是元素距离带有定位的父元素的位置,如果没有定位的父元素就是对body,和e.pageX,e.pageY作用相同,但是只有IE支持(不包含上方工具栏)。 

clientWidth, clientHeight:包含width+padding,不包含border;
scrollWidth,scrollHeight: 包含width+padding,不包含border;
offsetWidth, offsetHeight: 包含border+padding+width;
style.width, style.height: 包含width,不包含border+padding


★ 案例:模态框拖拽

弹出框,我们也称为模态框。

案例需求:

  1. ​ 点击弹出层,会弹出模态框, 并且显示灰色半透明的遮挡层。
  2. ​ 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层。
  3. ​ 鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
  4. ​ 鼠标松开,可以停止拖动模态框移动

案例分析:

  1. 点击弹出层, 模态框和遮挡层就会显示出来 display:block;
  2. 点击关闭按钮,模态框和遮挡层就会隐藏起来 display:none;
  3. 在页面中拖拽的原理:鼠标按下并且移动, 之后松开鼠标
  4. 触发事件是鼠标按下mousedown,鼠标移动mousemove 鼠标松开 mouseup
  5. 拖拽过程: 鼠标移动过程中,获得最新的值赋值给模态框的left和top值,这样模态框可以跟着鼠标走了
  6. 鼠标按下触发的事件源是最上面一行,就是 id 为 title
  7. 鼠标的坐标减去 鼠标在盒子内的坐标, 才是模态框真正的位置。
  8. 鼠标按下,我们要得到鼠标在盒子的坐标。
  9. 鼠标移动,就让模态框的坐标 设置为 :鼠标坐标 减去盒子坐标即可,注意移动事件写到按下事件里面。
  10. 鼠标松开,就停止拖拽,就是可以让鼠标移动事件解除

代码实现:

// 1. 获取元素
var login = document.querySelector(".login");
var mask = document.querySelector(".login-bg");
var link = document.querySelector("#link");
var closeBtn = document.querySelector("#closeBtn");
var title = document.querySelector("#title");
// 2. 点击弹出层这个链接 link  让mask 和login 显示出来
link.addEventListener("click", function () {
  mask.style.display = "block";
  login.style.display = "block";
});
// 3. 点击 closeBtn 就隐藏 mask 和 login
closeBtn.addEventListener("click", function () {
  mask.style.display = "none";
  login.style.display = "none";
});
// 4. 开始拖拽
// (1) 当我们鼠标按下, 就获得鼠标在盒子内的坐标
title.addEventListener("mousedown", function (e) {
  var x = e.pageX - login.offsetLeft;
  var y = e.pageY - login.offsetTop;
  // (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
  document.addEventListener("mousemove", move);

  function move(e) {
    login.style.left = e.pageX - x + "px";
    login.style.top = e.pageY - y + "px";
  }
  // (3) 鼠标弹起,就让鼠标移动事件移除
  document.addEventListener("mouseup", function () {
    document.removeEventListener("mousemove", move);
  });
});

完整代码整理:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- css样式 -->
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      .login-bg {
        display: none;
        width: 100%;
        height: 100%;
        position: fixed;
        top: 0;
        left: 0;
        background: rgba(0, 0, 0, 0.3);
      }
      .login-header {
        font-size: 20px;
        margin: 10px 0;
        text-align: center;
      }
      a {
        text-decoration: none;
        color: #333;
      }
      .login {
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        display: none;
        width: 480px;
        padding-bottom: 40px;
        background-color: #fff;
        border: 1px solid #ebebeb;
        box-shadow: 0px 0px 20px #ddd;
        z-index: 3;
      }
      .login-title {
        position: relative;
        text-align: center;
        padding: 20px 0 30px;
        font-size: 18px;
        cursor: move;
      }
      .close-login {
        position: absolute;
        top: -20px;
        right: -20px;
        display: block;
        text-align: center;
        line-height: 40px;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        font-size: 12px;
        border: 1px solid #ccc;
        background-color: #fff;
      }

      .login-input-content {
        margin-left: 20px;
        font-size: 14px;
      }
      .login-input:nth-child(1) label {
        margin-left: 14px;
      }
      .login-input-content input {
        width: 340px;
        height: 30px;
        outline: none;
        border: none;
        border: 1px solid #ccc;
        margin-bottom: 30px;
        padding-left: 5px;
      }
      .login-button {
        padding-bottom: 30px;
        width: 260px;
        height: 30px;
        border: 1px solid #ccc;
        margin: 0 auto;
        text-align: center;
        line-height: 30px;
      }
    </style>
  </head>
  <body>
    <!-- html结构 -->
    <div class="login-header">
      <a href="javascript:;">点击,弹出登录框</a>
    </div>
    <div class="login">
      <div class="login-title">
        登录会员
        <span><a href="javascript:;" class="close-login">关闭</a></span>
      </div>
      <div class="login-input-content">
        <div class="login-input">
          <label for="">用户名:</label>
          <input
            type="text"
            placeholder="请输入用户名"
            name="info[username]"
            id="username"
            class="username"
          />
        </div>
        <div class="login-input">
          <label for="">登录密码:</label>
          <input
            type="text"
            placeholder="请输入登录密码"
            name="info[password]"
            id="password"
            class="password"
          />
        </div>
      </div>
      <div class="login-button" id="loginBtn">
        <a href="javascript:;" id="login-button-submit">登录会员</a>
      </div>
    </div>
    <!-- 遮盖层 -->
    <div id="bg" class="login-bg"></div>
    <!-- js代码 -->
    <script>
      // 1. 获取元素
      let loginHeader = document.querySelector(".login-header");
      let login = document.querySelector(".login");
      let bg = document.getElementById("bg");
      let close = document.querySelector(".close-login");
      // 2. 点击,弹出登录框和遮盖层
      loginHeader.onclick = function () {
        login.style.display = "block";
        bg.style.display = "block";
      };
      // 3. 点击关闭,隐藏登录框和遮盖层
      close.onclick = function () {
        login.style.display = "none";
        bg.style.display = "none";
      };
      // 4. 鼠标放到模态框最上面的登录会员一行,可以按住鼠标拖拽模态框在页面中移动
      // 由于鼠标拖拽模态框时,鼠标的位置是不变的,所以要先获取鼠标在登录会员框中的位置
      // (1) 当鼠标按下,就能获得鼠标在盒子内的坐标
      let title = document.querySelector(".login-title");
      title.onmousedown = function (e) {
        let x = e.pageX - login.offsetLeft;
        let y = e.pageY - login.offsetTop;
        // (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去鼠标在盒子内的坐标就是模态框的left和top值
        document.addEventListener("mousemove", move);
        // 由于后面会移除事件,为了方便使用所以这里给回调函数单独设置并命名为move
        function move(e) {
          login.style.left = e.pageX - x + "px";
          login.style.top = e.pageY - y + "px";
        }
        // (3) 鼠标弹起mouseup,就让鼠标移动事件移除
        document.addEventListener("mouseup", function () {
          document.removeEventListener("mousemove", move);
        });
      };
    </script>
  </body>
</html>

★ 案例:仿京东放大镜

案例需求

  1. 整个案例可以分为三个功能模块
  2. 鼠标经过小图片盒子, 黄色的遮挡层 和 大图片盒子显示,离开隐藏2个盒子功能
  3. 黄色的遮挡层跟随鼠标功能。
  4. 移动黄色遮挡层,大图片跟随移动功能。

案例分析:

  1. 黄色的遮挡层跟随鼠标功能。
  2. 把鼠标坐标给遮挡层不合适。因为遮挡层坐标以父盒子为准。
  3. 首先是获得鼠标在盒子的坐标。
  4. 之后把数值给遮挡层做为left 和top值。
  5. 此时用到鼠标移动事件,但是还是在小图片盒子内移动。
  6. 发现,遮挡层位置不对,需要再减去盒子自身高度和宽度的一半。
  7. 遮挡层不能超出小图片盒子范围。
  8. 如果小于零,就把坐标设置为0
  9. 如果大于遮挡层最大的移动距离,就把坐标设置为最大的移动距离
  10. 遮挡层的最大移动距离:小图片盒子宽度 减去 遮挡层盒子宽度

代码实现:

window.addEventListener('load', function() {
    var preview_img = document.querySelector('.preview_img');
    var mask = document.querySelector('.mask');
    var big = document.querySelector('.big');
    // 1. 鼠标经过 preview_img 显示和隐藏 mask 遮挡层 和 big 大盒子
    preview_img.addEventListener('mouseover', function() {
        mask.style.display = 'block';
        big.style.display = 'block';
    })
    preview_img.addEventListener('mouseout', function() {
            mask.style.display = 'none';
            big.style.display = 'none';
        })
        // 2. 鼠标移动的时候,让黄色的盒子跟着鼠标来走
    preview_img.addEventListener('mousemove', function(e) {
        // (1). 先计算出鼠标在盒子内的坐标
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        // console.log(x, y);
        // (2) (鼠标在盒子内的坐标)-(盒子高度的一半)=(mask的最终left和top值)
        var maskX = x - mask.offsetWidth / 2;
        var maskY = y - mask.offsetHeight / 2;
        // (3) 如果x坐标小于0 就让他停在0的位置
        // 遮挡层的最大移动距离
        var maskMax = preview_img.offsetWidth - mask.offsetWidth;
        if (maskX <= 0) {
            maskX = 0;
        } else if (maskX >= maskMax) {
            maskX = maskMax;
        }
        if (maskY <= 0) {
            maskY = 0;
        } else if (maskY >= maskMax) {
            maskY = maskMax;
        }
        mask.style.left = maskX + 'px';
        mask.style.top = maskY + 'px';
        // 3. 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
        // 大图
        var bigIMg = document.querySelector('.bigImg');
        // 大图片最大移动距离
        var bigMax = bigIMg.offsetWidth - big.offsetWidth;
        // 大图片的移动距离 X Y
        var bigX = maskX * bigMax / maskMax;
        var bigY = maskY * bigMax / maskMax;
        bigIMg.style.left = -bigX + 'px';
        bigIMg.style.top = -bigY + 'px';
    })
})

四、番外:元素视图方法

元素视图方法概述

元素视图有三个方法,分别是getBoundingClientRect、getClientRects(忽略)和elementFromPoint(忽略)。

参考:深入理解元素视图的3个方法 - 小火柴的蓝色理想 - 博客园

getBoundingClientRect

获取dom元素相对于浏览器视口的位置集合。不同浏览器,集合中的元素略有不同,ie8及以下版本的浏览器中没有width和height属性,若出现width和height属性,则它们是偏移宽高offset。位置集合中主要包含四个属性,分别是top、right、bottom和left,其中top和left是dom元素左上顶点相对于文档的偏移坐标,bottom和right是dom元素右下顶点相对于文档的偏移坐标。该方法的返回结果不是实时的,相当于一个快照。该方法是es5添加的方法,兼容性于所有主流浏览器。

<div id="div" style="width:100px;height:200px;padding:100px;"></div>
<script>
    console.log(div.offsetWidth);  //"300"
    console.log(div.offsetHeight); //"400"
    console.log(div.getBoundingClientRect());
</script>

<div id="div" style="width:100px;height:100px;"></div>
<script>
    var box = div.getBoundingClientRect();
    console.log(box.width); // "100"
    div.style.width = "200px";
    console.log(box.width); // "100", getBoundingClientRect返回结果是静态的
</script>

参考:JS | JS中的getBoundingClientRect()方法详解,秒懂!- 烤地瓜的CSDN博客


● 参考资料 ●

scrollTop及offsetTop的对比_scrolltop和offsettop-CSDN博客

深入理解元素视图的3个方法-getBoundingClientRect/getClientRects/elementFromPoint - 博客园

—— JS | JavaScrip之深入理解滚动scroll大小系列属性- 烤地瓜的CSDN博客 ——

—— JS | JavaScrip之深入理解客户区尺寸client系列属性 - 烤地瓜的CSDN博客 ——

—— JS | 详解图片懒加载的6种实现方案之利用JS监听scroll滚动事件 - 烤地瓜的CSDN博客 ——

一文看懂JS中元素偏移量(offsetLeft,offsetTop,offsetWidth,offsetHeight) - 博客园

Javascript进阶篇之DOM节点 - 获取浏览器窗口可视区域大小+获取网页尺寸) - 博客园



http://www.kler.cn/news/355834.html

相关文章:

  • RHCE笔记-时间服务器搭建
  • 【Flutter】基础入门:代码基本结构
  • 深度学习:元学习(Meta-Learning)详解
  • 自搭建VSCode的Make构建工具选择
  • 深度学习的关键概念和术语
  • MySQL-14.DQL-分组查询
  • 用python绘制钟表,并实现实际应用
  • HarmonyOS中ArkUi框架中常用的装饰器
  • 面对配分函数 - 噪扰对比估计(NCE)篇
  • windows的CMD命令提示符
  • 用 Python 构建高级配对交易策略
  • C++算法练习-day7——707.设计链表
  • 【Git】将其它分支的单个改动复制到当前分支
  • 基于ESP32的便携式游戏机
  • Elasticsearch Inference API 增加对阿里云 AI 的支持
  • 021_Thermal_Transient_in_Matlab统一偏微分框架之热传导问题
  • Openlayers高级交互(3/20):动态添加 layer 到 layerGroup,并动态删除
  • 【2D/3D-Lidar-SLAM】 Cartographer详细解读
  • PCDN 技术如何优化网络延迟(壹)
  • 如何快速学会盲打