웹 개발에서는 요소의 크기와 위치를 계산하는 문제가 불가피하게 발생합니다. 이러한 문제를 해결하는 방법은 호환성 처리와 결합된 DOM에서 제공하는 일부 API를 사용하는 것입니다. . 첫 번째 기사로서 이 기사에서는 DOM에서 제공하는 크기와 관련된 DOM 속성을 소개하고 몇 가지 호환성 처리 방법을 제공하며 일반적인 시나리오를 기반으로 이러한 속성을 올바르게 사용하는 방법을 설명합니다.
1. offsetWidth, clientWidth, scrollWidth 및 해당 높이 속성을 올바르게 이해합니다
특정 요소의 가로, 세로 스크롤바를 끝까지 드래그했다고 가정하면, offsetWidth, clientWidth, scrollWidth 및 기타 속성의 해당 범위는 아래 그림과 같습니다.
1) offsetWidth 및 offsetHeight는 상자 모델의 너비와 높이에 해당합니다. 이 두 값은 요소를 검사하기 위해 크롬을 사용할 때 표시되는 크기와 일치합니다.
2) scrollHeight에 해당하는 scrollWidth는 스크롤 영역의 너비와 높이이지만 스크롤 막대의 너비는 포함되지 않습니다! 스크롤 영역은 패딩과 콘텐츠로 구성됩니다.
3) clientWidth 및 clientHeight는 스크롤 막대의 너비를 제외하고 테두리를 제거한 후 상자 모델 영역의 너비와 높이에 해당합니다.
4) 모든 DOM 요소는 다음 API를 통해 offsetWidth, clientWidth, scrollWidh 및 관련 높이 속성을 빠르게 가져올 수 있습니다.
//domE는 DOM HTML 요소 객체입니다
domE.scrollWidth
domE.scrollHeight
domE.clientWidth
domE.clientHeight
domE.offsetWidth
domE.offsetHeight
//domE는 DOM HTML 요소 객체입니다
domE.scrollWidth
domE.scrollHeight
domE.clientWidth
domE.clientHeight
domE.offsetWidth
domE.offsetHeight
5) 이러한 속성은 PC, 모바일 등 최신 브라우저에서 호환성 문제가 거의 없어 안심하고 사용할 수 있습니다. 자세한 호환성 규칙을 알고 싶다면 다음 2개의 기사를 참조하세요.
W3C DOM 호환성 – CSS 개체 모델 보기
cssom 뷰 모드 cssom-view-module 컴파일 및 소개
위의 일반 html 요소, html 루트 요소 및 body 요소의 관련 속성을 하나씩 테스트하여 이전 결론을 확인하고 실제 코딩 과정에서 직접 사용할 수 있는 몇 가지 경험 기술을 요약합니다. 일반 html 요소, html 루트 요소 및 body 요소를 구별해야 하는 이유는 이전 이론 때문입니다. html 루트 요소와 body 요소에는 주의해서 처리해야 합니다.
참고:
1. 테스트용으로 게시된 코드는 길이를 줄이기 위해 완성된 코드는 아니지만, 학습 참고자료에 영향을 미치지 않습니다. 또한, 글에 제시된 테스트 결과는 모두 크롬에서 실행하여 얻은 것입니다. (버전: 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 브라우저에서 도입되었지만 document.compatMode = 'BackCompat'은 크롬, 파이어폭스 등에 대해서도 존재합니다. 예를 들어 다음 웹 페이지를 크롬으로 열고 콘솔에서 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
테스트 1. 일반 html 요소(본문이 아닌 요소 및 html 루트 요소)의 offsetWidth, clientWidth, scrollWidth 및 관련 높이 속성을 확인합니다.
<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>
이 예에서 상자 요소의 너비와 높이는 400*300이고 패딩은 20픽셀이며 테두리는 10픽셀입니다.
js 실행 결과:
박스 모델과 js 실행 결과를 통해 다음을 알 수 있습니다.
1) offsetWidth 및 offsetHeight는 크롬에서 요소를 검사할 때 표시되는 크기와 정확히 동일합니다.
2) clientWidth 및 clientHeight는 각각 offsetWidth 및 offsetHeight에서 해당 테두리(상하 총 20px, 좌우 총 20px) 및 스크롤 막대 너비(크롬 아래의 스크롤 막대 너비는 17px)를 뺀 것과 같습니다. );
3) scrollWidth의 경우 가로 오버플로가 발생하지 않으며, 오버플로로 인해 scrollWidth는 clientWidth와 동일하지만 스크롤 막대의 너비를 포함하지 않습니다. 이는 앞서 제안한 결론도 확인합니다.
4) scrollHeight의 경우 이 예에서는 실제로 div.box-2, div.box-2의 위쪽 및 아래쪽 패딩(총 40px) offsetHeight(1370px)와 같습니다.
5) 위 테스트에서 주목할 만한 또 다른 CSS는 box-sizing입니다. 위 코드에서 box-sizing은 content-box로 변경하면 결과는 다음과 같습니다. 비슷합니다. 왜냐하면 offsetWidth와 clientWidth는 여전히 scrollWidth에 해당하는 영역은 변경되지 않기 때문입니다.
6) 다른 브라우저의 결과는 결론 1-5와 일치합니다.
테스트 2. html 루트 요소와 본문 요소의 관련 오프셋 클라이언트 스크롤 너비 및 높이 속성을 확인합니다.
<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,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; }
以上就是本文的全部内容,希望能对您有所帮助:)另外本文第二部分提供的代码,是根据个人思考和经验总结出的一些方法,在兼容性方面可能还有未考虑到的地方, 如果您有遇到其它不兼容的情况或者有更好的代码,还请不吝赐教 ,欢迎您的指导。