首頁  >  文章  >  web前端  >  JavaScript位置與大小(1)之正確理解與運用與尺寸大小相關的DOM屬性_javascript技巧

JavaScript位置與大小(1)之正確理解與運用與尺寸大小相關的DOM屬性_javascript技巧

WBOY
WBOY原創
2016-05-16 15:23:141301瀏覽

在web開發中,不可避免遇到要計算元素大小以及位置的問題,解決這類問題的方法是利用DOM提供的一些API結合兼容性處理來,所有內容大概分3篇左右的文章的來說明。本文作為第一篇,介紹DOM提供的與尺寸大小相關的DOM屬性,提供一些相容性處理的方法,並結合常見的場景說明如何正確運用這些屬性。

1. 正確理解offsetWidth、clientWidth、scrollWidth及對應的height屬性

假設某一個元素的橫縱向捲軸都拖曳到最末端,則offsetWidth、clientWidth、scrollWidth等屬性對應的範圍如下圖所示:

 

1)offsetWidth ,offsetHeight對應的是盒子模型的寬度和高度,這兩個值跟我們使用chrome審查元素時看到的尺寸一致:

 

2)scrollWidth,與scrollHeight對應的是滾動區域的寬度和高度 , 但不包含滾動條的寬度!滾動區域由padding和content組成。

3)clientWidth,clientHeight對應的是盒子模型除去邊框後的那部分區域的寬度和高度,不包含滾動條的寬度。

4)任何一個DOM元素,都可以透過以下api快速得到offsetWidth,clientWidth,scrollWidh及相關的height屬性:

//domE為一個DOM Html Element物件
domE.scrollWidth
domE.scrollHeight
domE.clientWidth
domE.clientHeight
domE.offsetWidth
domE.offsetHeight
//domE為一個DOM Html Element物件
domE.scrollWidth
domE.scrollHeight
domE.clientWidth
domE.clientHeight
domE.offsetWidth
domE.offsetHeight

5) 這些屬性在現代瀏覽器包括pc和mobile上幾乎沒有相容性問題,可以放心使用 。如果你想了解詳細的相容性規則,可以參考下面的2篇文章:

W3C DOM Compatibility – CSS Object Model View

cssom視圖模式cssom-view-module相關整理與介紹

下面針對普通html元素,html根元素和body元素的以上相關屬性一一測試,以便驗證前面的結論,總結一些可在實際編碼過程中直接使用的經驗技巧。之所以要區分普通html元素,html根元素和body元素,是因為前面的理論,在html根元素和body元素會有一些怪異之處,需要小心處理。

註:

1、為了減少篇幅,測試貼出的程式碼不是完整的程式碼,但不影響學習參考,另外文中給出的測試結果都是在chrome(版本:45.0)下運行得出的,在測試結果在有差異的情況下,也會給出IE9,IE10,IE11,firefox(版本:42.0),opera(版本:34.0)的測試結果,沒有差異的會在測試結果中說明,不考慮IE8及以下。

2、safari因為設備限制暫不測試,另外它跟chrome內核相同,對標準支援的可靠性差不到哪裡去。

3、舊版的chrome,firefox,opera也因為裝置的限制無法測試,不過從瀏覽器對標準的支援程度考慮,這三個瀏覽器在很早的版本開始對W3C的標準都是比較規矩的,加上這些瀏覽器更新換代的速度較快,現在市面上這些瀏覽器主流的版本也都是較新的。

4、由於不考慮IE8以下,同時html現在都用html5,所以document.compatMode = ‘BackCompat' 的情況不考慮。不過儘管BackCompat模式是IE6類的瀏覽器引出的,但是對於chrome,firefox等也存在document.compatMode = 'BackCompat' 的情況,比如下面的這個網頁,你用chrome打開,並且在console中打印document.compatMode ,你會發現它的值也是BackCompat(原因跟該頁面用的是html4.0的dtd有關,如果換成html4.01的dtd就不會在chrome和firefox裡出現該情況了):

http://samples.msdn.microsoft.com/workshop/samples/author/dhtml/refs/compatModeCompat.htm
更多關於compatMode的知識,你可以透過下面的幾個資源學習:

https://developer.mozilla.org/zh-CN/docs/Web/API/Document/compatMode

https://msdn.microsoft.com/en-us/library/ms533687(VS.85).aspx

http://www.cnblogs.com/uedt/archive/2010/09/21/1832402.html

測試一、驗證普通html元素(非body及html根元素)的offsetWidth、clientWidth、scrollWidth及相關height屬性:

<style type="text/css">
  html,
  body {
    margin: 0;
  }
  body {
    padding: 100px;
  }
  .box {
    overflow: scroll;
    width: 400px;
    height: 300px;
    padding: 20px;
    border: 10px solid #000;
    margin: 0 auto;
    box-sizing: content-box;
  }
  .box-2 {
    border: 1px solid #000;
  }
</style>
<body>
  <div class="box">
    <div class="box-2">...</div>
  </div>
</body>
<script type="text/javascript">
var boxE = document.querySelectorAll('.box')[0];
console.log('scrollWidth:' + boxE.scrollWidth);
console.log('scrollHeight:' + boxE.scrollHeight);
console.log('clientWidth:' + boxE.clientWidth);
console.log('clientHeight:' + boxE.clientHeight);
console.log('offsetWidth :' + boxE.offsetWidth);
console.log('offsetHeight:' + boxE.offsetHeight);
</script>
<styletype="text/css">
  html,
  body{
    margin: 0;
  }
  body{
    padding: 100px;
  }
  .box{
    overflow: scroll;
    width: 400px;
    height: 300px;
    padding: 20px;
    border: 10px solid #000;
    margin: 0 auto;
    box-sizing: content-box;
  }
  .box-2{
    border: 1px solid #000;
  }
</style>
<body>
  <divclass="box">
    <divclass="box-2">...</div>
  </div>
</body>
<scripttype="text/javascript">
var boxE = document.querySelectorAll('.box')[0];
console.log('scrollWidth:' + boxE.scrollWidth);
console.log('scrollHeight:' + boxE.scrollHeight);
console.log('clientWidth:' + boxE.clientWidth);
console.log('clientHeight:' + boxE.clientHeight);
console.log('offsetWidth :' + boxE.offsetWidth);
console.log('offsetHeight:' + boxE.offsetHeight);
</script>

在這個例子中,box元素有400*300的寬高,20px的padding和10px的border,chrome下對應的盒子模型:

js執行結果:

 

從盒子模型與js執行結果可知:

1)offsetWidth與offsetHeight與chrome審查元素看到的尺寸完全一致;

2)cli​​entWidth與clientHeight分別等於offsetWidth與offsetHeight減掉對應邊框(上下共20px,左右共20px)及滾動條寬度後的值(chrome下方捲軸寬度為17px);

3)對於scrollWidth由於沒有發生橫向的溢出,同時由於overflow: scroll的原因,scrollWidth 跟clientWidth相同,但是沒有包含滾動條的寬度,這也驗證了前面提出的結論;

4)對於scrollHeight,在這個例子中,它其實等於上下padding(共40px) + div.box-2的offsetHeight(1370px),div.box-2:

 

5)以上測試還有一個css值得注意,就是box-sizing,以上程式碼中box-sizing設定為了content-box,如果把它改成border-box,結果也是類似的,因為offsetWidth,clientWidth還有scrollWidth對應的區域不會改變。

6)其它瀏覽器運作結果與1-5的結論一致。

測試二、驗證html根元素和body元素的相關offset client scroll寬高屬性:

<style type="text/css">
  html,
  body {
    margin: 0;
  }
  body {
    border: 10px solid #D4D2D2;
  }
  .box {
    overflow: scroll;
    width: 400px;
    height: 300px;
    padding: 20px;
    border: 10px solid #000;
    margin: 0 auto;
    box-sizing: content-box;
  }
  .box-2 {
    border: 1px solid #000;
  }
</style>
<body>
  <div class="box">
    <div class="box-2">...</div>
  </div>
  <div class="box">
    <div class="box-2">...</div>
  </div>
  <div class="box">
    <div class="box-2">...</div>
  </div>
  <div class="box">
    <div class="box-2">...</div>
  </div>
</body>
<script>
console.log('docE.scrollWidth:' + document.documentElement.scrollWidth);
console.log('scrollHeight:' + document.documentElement.scrollHeight);
console.log('docE.clientWidth:' + document.documentElement.clientWidth);
console.log('docE.clientHeight:' + document.documentElement.clientHeight);
console.log('docE.offsetWidth :' + document.documentElement.offsetWidth);
console.log('docE.offsetHeight:' + document.documentElement.offsetHeight);
console.log('');
console.log('body.scrollWidth:' + document.body.scrollWidth);
console.log('body.scrollHeight:' + document.body.scrollHeight);
console.log('body.clientWidth:' + document.body.clientWidth);
console.log('body.clientHeight:' + document.body.clientHeight);
console.log('body.offsetWidth :' + document.body.offsetWidth);
console.log('body.offsetHeight:' + document.body.offsetHeight);
</script>
<styletype="text/css">
  html,
  body{
    margin: 0;
  }
  body{
    border: 10px solid #D4D2D2;
  }
  .box{
    overflow: scroll;
    width: 400px;
    height: 300px;
    padding: 20px;
    border: 10px solid #000;
    margin: 0 auto;
    box-sizing: content-box;
  }
  .box-2{
    border: 1px solid #000;
  }
</style>
<body>
  <divclass="box">
    <divclass="box-2">...</div>
  </div>
  <divclass="box">
    <divclass="box-2">...</div>
  </div>
  <divclass="box">
    <divclass="box-2">...</div>
  </div>
  <divclass="box">
    <divclass="box-2">...</div>
  </div>
</body>
<script>
console.log('docE.scrollWidth:' + document.documentElement.scrollWidth);
console.log('scrollHeight:' + document.documentElement.scrollHeight);
console.log('docE.clientWidth:' + document.documentElement.clientWidth);
console.log('docE.clientHeight:' + document.documentElement.clientHeight);
console.log('docE.offsetWidth :' + document.documentElement.offsetWidth);
console.log('docE.offsetHeight:' + document.documentElement.offsetHeight);
console.log('');
console.log('body.scrollWidth:' + document.body.scrollWidth);
console.log('body.scrollHeight:' + document.body.scrollHeight);
console.log('body.clientWidth:' + document.body.clientWidth);
console.log('body.clientHeight:' + document.body.clientHeight);
console.log('body.offsetWidth :' + document.body.offsetWidth);
console.log('body.offsetHeight:' + document.body.offsetHeight);
</script>

在這個例子中,body下一共有4個box元素(總高度為360 * 4 = 1440px),body的寬是自適應的,body還有10px的border,運行結果如下:

從這個結果可以看到:

1)body元素由於10px邊框的原因,所以clientWidth比offsetWidth少了20px,這跟前面提到的理論是一致的,但是不可思議的是body的scrollWidth/scrollHeight竟然等於它的offsetWidth/offsetHeight,scrollWidthight,scrollWidth /scrollHeight是元素滾動區域的寬高度,按照前面給出的範圍圖來理解,body的scrollWidth/scrollHeight應該小於它的offsetWidth/offsetHeight才對;

2)docE的scrollWidth跟scrollHeight,應該等於body元素的offsetWidth跟offsetHeight,從運行結果來看,這一點是符合的,但是docE的clientWidth竟然等於它的offsetWidth,按照範圍圖,docE的clientWidth應該等於offsetWidth減去滾動條寬度才對。

其它的瀏覽器運作結果與chrome也有較大的差異:

IE11:

 

1)IE11下body元素沒有出現chrome下body元素的問題

2)IE11下html根元素也有chrome類似的問題

IE10,IE9:

 

1)IE10,9下body元素沒有出現chrome下body元素的問題

2)IE10,9下html根元素也沒有chrome類似的問題

firefox:與IE11運行結果一致。

opera: 與chrome運作結果一致,可能是因為我這個版本的opera用的跟chrome一樣的webkit核心的原因。

看起來IE9就跟IE10是最正常的,實在是有點難以理解,網上搜索很久,也沒有找到相關資料來說明這些差異, 最後也只能採取大膽假設的方式,猜測出幾個能解釋這些問題的原因:

1) 首先,網頁整體的滾動,跟普通html元素的滾動不一樣,普通html元素本身就是滾動對象, 但是對於網頁來說,滾動對像不一定是html根元素或body元素。因為當body內容為空時,body的高度是0,html根元素的高度也是0,如果這個時候給html或body加上overflow: scroll的css,會看到滾動條還是出現瀏覽器視窗的右邊跟底邊,所以對於網頁整體的滾動,理論上,滾動物件應該是window,而不是html元素或body元素!但實際情況並非如此,就測試的瀏覽器而言:

對於IE10,IE9,它的滾動物件是html根元素,所以它們的html根元素的offset會包含捲軸的寬度;

對於其它瀏覽器,滾動物件是window,所以它們的html根元素的offset不包含捲軸的寬度。

2)第二,普通元素發生捲動時,捲動內容=它的content區域+它的padding區域,當網頁整體捲動時,捲動內容應該是html根元素!但實際情況並非如此,就測試的瀏覽器而言:

對於IE9,IE10,IE11,firefox,它們的滾動區域是html根元素,所以它們的documentElement的scrollWidth和scrollHeight總是表示網頁整體的滾動區域大小!

對於chrome和opera,它們的捲動物件是body元素,所以它們的body的scrollWidth和scrollHeight總是表示網頁整體的捲動區域大小!

3)第三,瀏覽器總是把documentElement.clientWidth和documentElement.clientHeight描述為網頁視覺區域除去捲軸部分的大小,跟網頁內容沒有關係!

以上的這些推斷也並非是毫無道理,就拿滾動物件和滾動區域來說:chrome下如果要用js滾動頁面到某個位置,在不使用window.scrollTo的條件下,就必須用document.body.scrollTop = xxx 來處理,而設定document.documentElement.scrollTop無效,說明chrome的整體滾動區域是由body的滾動區域決定的;而IE11和火狐下如果要用js滾動頁面到某個位置,在不使用window.scrollTo的條件下,就必須用document.documentElement.scrollTop = xxx來處理,設定document.body.scrollTop無效,說明IE11和火狐的整體滾動區域是由html根元素的滾動區域決定的。

2. 利用JS精確取得DOM物件的大小

常見的場景有:

1)取得整個網頁的可視區域的大小,不包括捲軸

2)取得整個網頁的大小,包括看不見的捲動區域

3)取得一個普通html元素的大小

4)判斷元素或網頁有無出現捲軸

5)計算滾動條的寬度

下面针对这5个场景一一说明,以下代码均 不考虑IE8及以下,不考虑html4 ,另外请注意viewport的设置,要保证在移动设备上visual viewport与layout viewport重合。

1)如何获取整个网页的可视区域的大小,不包括滚动条

document.documentElement.clientWidth;
document.documentElement.clientHeight;
document.documentElement.clientWidth;
document.documentElement.clientHeight;

2)如何获取整个网页的大小,包括不可见的滚动区域

function pageWidth() {
  var doc = document.documentElement,
    body = document.body;
  if (doc.clientWidth == window.innerWidth) {
    return doc["clientWidth"];
  }
  return Math.max(
    body["scrollWidth"], doc["scrollWidth"],
    body["offsetWidth"], doc["clientWidth"]
  );
}
function pageHeight() {
  var doc = document.documentElement,
    body = document.body;
  if (doc.clientHeight == window.innerHeight) {
    return doc["clientHeight"];
  }
  return Math.max(
    body["scrollHeight"], doc["scrollHeight"],
    body["offsetHeight"], doc["clientHeight"]
  );
}
function pageWidth() {
  var doc = document.documentElement,
    body = document.body;
  if (doc.clientWidth == window.innerWidth) {
    return doc["clientWidth"];
  }
  return Math.max(
    body["scrollWidth"], doc["scrollWidth"],
    body["offsetWidth"], doc["clientWidth"]
  );
}
function pageHeight() {
  var doc = document.documentElement,
    body = document.body;
  if (doc.clientHeight == window.innerHeight) {
    return doc["clientHeight"];
  }
  return Math.max(
    body["scrollHeight"], doc["scrollHeight"],
    body["offsetHeight"], doc["clientHeight"]
  );
}

以上出现的window.innerWidth和window.innerHeight分别用来获取网页包括滚动条的可视区域的宽高,这也是一个兼容性不错的方法,不过从实际开发情况来看,我们需要不包括滚动条的可视区域更多一些,所以在前面没有单独介绍。另外在之前给出的PPK的博客中也有关于这两个属性的兼容性测试,可以去了解。

3)如何获取一个普通html元素的大小

简单方法:

docE.offsetWidth;
docE.offsetHeight;
docE.offsetWidth;
docE.offsetHeight;

利用getBoundingClientRect:

var obj = docE.getBoundingClientRect(),
  elemWidth,
  elemHeight;
if(obj) {
  if(obj.width) {
    elemWidth = obj.width;
    elemHeight = obj.height;
  } else {
    elemWidth = obj.right - obj.left;
    elemHeight = obj.bottom - obj.top;
  }
} else {
  elemWidth = docE.offsetWidth;
  elemHeight = docE.offsetHeight;
}
var obj = docE.getBoundingClientRect(),
  elemWidth,
  elemHeight;
if(obj) {
  if(obj.width) {
    elemWidth = obj.width;
    elemHeight = obj.height;
  } else {
    elemWidth = obj.right - obj.left;
    elemHeight = obj.bottom - obj.top;
  }
} else {
  elemWidth = docE.offsetWidth;
  elemHeight = docE.offsetHeight;
}

getBoundingClientRect将在下篇文章中跟其它与位置有关的DOM属性一起再详细介绍。

4 )判断元素或网页有无出现滚动条

function scrollbarState(elem) {
  var docE = document.documentElement,
    body = document.body;
  if (!elem || elem === document || elem === docE || elem === body) {
    return {
      scrollbarX: docE.clientHeight window.innerHeight,
      scrollbarY: docE.clientWidth window.innerWidth
    }
  }
  if (typeof(Element) == 'function' & !(elem instanceof(Element) || !body.contains(elem))) {
    return {
      scrollbarX: false,
      scrollbarY: false
    };
  }
  var elemStyle = elem.style,
    overflowStyle = {
      hidden: elemStyle.overflow == 'hidden',
      hiddenX: elemStyle.overflowX == 'hidden',
      hiddenY: elemStyle.overflowY == 'hidden',
      scroll: elemStyle.overflow == 'scroll',
      scrollX: elemStyle.overflowX == 'scroll',
      scrollY: elemStyle.overflowY == 'scroll'
    };
  return {
    scrollbarX: overflowStyle.scroll || overflowStyle.scrollX || (!overflowStyle.hidden & !overflowStyle.hiddenX && elem.clientWidth elem.scrollWidth),
    scrollbarY: overflowStyle.scroll || overflowStyle.scrollY || (!overflowStyle.hidden && !overflowStyle.hiddenY && elem.clientHeight elem.scrollHeight)
  };
}
function scrollbarState(elem) {
  var docE = document.documentElement,
    body = document.body;
  if (!elem || elem === document || elem === docE || elem === body) {
    return {
      scrollbarX: docE.clientHeight window.innerHeight,
      scrollbarY: docE.clientWidth window.innerWidth
    }
  }
  if (typeof(Element) == 'function' & !(eleminstanceof(Element) || !body.contains(elem))) {
    return {
      scrollbarX: false,
      scrollbarY: false
    };
  }
  var elemStyle = elem.style,
    overflowStyle = {
      hidden: elemStyle.overflow == 'hidden',
      hiddenX: elemStyle.overflowX == 'hidden',
      hiddenY: elemStyle.overflowY == 'hidden',
      scroll: elemStyle.overflow == 'scroll',
      scrollX: elemStyle.overflowX == 'scroll',
      scrollY: elemStyle.overflowY == 'scroll'
    };
  return {
    scrollbarX: overflowStyle.scroll || overflowStyle.scrollX || (!overflowStyle.hidden & !overflowStyle.hiddenX && elem.clientWidth elem.scrollWidth),
    scrollbarY: overflowStyle.scroll || overflowStyle.scrollY || (!overflowStyle.hidden && !overflowStyle.hiddenY && elem.clientHeight elem.scrollHeight)
  };
}

当x或y方向的overflow为scroll的时候,该方向的scrollbarX为true,表示出现滚动条。

5)计算滚动条的宽度

function scrollbarWidth() {
  var docE = document.documentElement,
    body = document.body,
    e = document.createElement('div');

  e.style.cssText = 'position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;';

  body.appendChild(e);
  var _scrollbarWidth = e.offsetWidth - e.clientWidth
  body.removeChild(e);
  return _scrollbarWidth;
}
function scrollbarWidth() {
  var docE = document.documentElement,
    body = document.body,
    e = document.createElement('div');
 
  e.style.cssText = 'position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;';
 
  body.appendChild(e);
  var _scrollbarWidth = e.offsetWidth - e.clientWidth
  body.removeChild(e);
  return _scrollbarWidth;
}

以上就是本文的全部内容,希望能对您有所帮助:)另外本文第二部分提供的代码,是根据个人思考和经验总结出的一些方法,在兼容性方面可能还有未考虑到的地方, 如果您有遇到其它不兼容的情况或者有更好的代码,还请不吝赐教 ,欢迎您的指导。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn