从 scrollTop 兼容性问题认识 DTD 声明

1,718 阅读5分钟

本文写于2017-10-17,有点历史,就是想发布出来(`・ω・´)​

前言

本文起源于一次同事向我反馈,在做页面回到顶部功能时获取 scrollTop 数值有问题。当时他实测在 360 浏览器可以获取 scrollTop,在 chrome 和 Firefox 不能正常使用。那时我的第一反应是浏览器兼容性问题,猜测同事有可能使用的是 document.body.scrollTop 来获取 scrollTop 并且使用了 360 兼容模式浏览,因为 360 兼容模式是 IE 模式,因此 chrome 和 Firefox 不能正常获取。在帮助同事解决了问题后,总结了本文。

先上结论:

  • jQuery 版本回到顶部$('html,body')
$('html,body').animate({ scrollTop: 0 }, 100);
  • 原生版本 兼容版回到顶部
/**
 * 设置页面滚动条距顶部高度
 * @param {Number} top
 */
export function setPageScrollTop(top) {
  document.documentElement.scrollTop /* 标准 */ = top;
  window.pageYOffset /* Safari */ = top;
  document.body.scrollTop /* IE6/7/8 */ = top;
}

/**
 *  获取滚动条距顶部高度
 */
export function getPageScrollTop() {
  let doc = document;
  return (
    doc.documentElement.scrollTop /* 标准 */ || window.pageYOffset /* Safari */ || doc.body.scrollTop /* IE6/7/8 */ || 0
  );
}
var scrollTop =
  document.documentElement.scrollTop /* 标准 */ ||
  window.pageYOffset /* Safari */ ||
  document.body.scrollTop; /* IE6/7/8 */ || 0

下面根据上述问题引出兼容性源头。在谈论 scrollTop 的浏览器差异前,先科普一下 DTD。

DTD (Document Type Definition)

文档类型定义(DTD)可定义合法的 XML 文档构建模块。它使用一系列合法的元素来定义文档的结构。

DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。

DTD 用途

  • 通过 DTD, XML 文件均可携带一个有关其自身格式的描述。

  • 通过 DTD,独立的团体可一致地使用某个标准的 DTD 来交换数据。

  • 可以使用某个标准的 DTD 来验证从外部接收到的数据。

  • 可以使用 DTD 来验证文件自身的数据。

DTD 的使用方式

内部的 DOCTYPE 声明

内部的 DOCTYPE 声明 指 在 xml 文档内部使用 DTD 声明。

  • 语法结构:<!DOCTYPE 根元素 [元素声明]>

案例:

  • 熟悉的 html 声明
<!-- html5 声明 -->
<!DOCTYPE html>
<html></html>

<!-- html4 声明 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"></html>

  • 自定义 xml 解析

    • 定义一个 note类型的文档: <!DOCTYPE note []
    • 定义 note类型 文档的元素: <!ELEMENT note (to,from,heading,body)>
    • 定义 note类型 文档的元素的类型为 "#PCDATA" 类型:如:<!ELEMENT body (#PCDATA)>
<!-- 内部声明DTD的自定义xml源文件 -->
<?xml version="1.0"?>
<!DOCTYPE note [
  <!ELEMENT note (to,from,heading,body)>
  <!ELEMENT to      (#PCDATA)>
  <!ELEMENT from    (#PCDATA)>
  <!ELEMENT heading (#PCDATA)>
  <!ELEMENT body    (#PCDATA)>
]>
<note>
  <to>George</to>
  <from>John</from>
  <heading>Reminder</heading>
  <body>Don't forget the meeting!</body>
</note>

外部文档声明

外部文档声明 指 在 xml 文档内部不直接声明 DTD 类型,转而通过引用外部 DTD 声明文件来实现。

  • 语法结构:<!DOCTYPE 根元素 SYSTEM "文件名">

案例:

对上述 内部的 DOCTYPE 声明 案例作改写

<!-- note.dtd源文件 -->
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
<!-- 外部声明DTD的自定义xml源文件 -->
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

特别说明

对于 xml 文档元素的类型,CDATA 与 PCDATA 的区别

  • CDATA
    • Character Data: 指字符数据。数据源是不会被解析器解析的文本,文本中的标签不被看作标记来对待,其中的实体也不会被展开。
    • 用于属性声明。
    • 属性值是字符数据。
    • CDATA 是属性声明中的类型,就是字符串,&、<、“”和‘’ 等都具有特殊含义被解析,例如:"解析为双引号。
  • PCDATA
    • parsed character data:指被解析的字符数据。数据源可以是字符串、子元素、字符串和子元素,是会被解析器解析的文本(如, > 要写成 &gt; 才不会出错。 ),文本将被解析器检查实体以及标记,其中的标签会被当作标记来处理,实体会被展开。
    • 用于元素声明。
    • 该内容模型说明元素中可以同时出现文本和元素。
    • PCDATA 是元素声明中的类型,指的是混合类型,即可以包含子元素也可包含字符串, &和<也是具有特殊含义被解析。被解析的字符数据不应当包含任何 &、< 或者 > 字符;需要使用 &、< 以及 > 实体来分别替换它们,否则会报错。

HTML 的 DTD

对页面具有 DTD,或者说指定了 DOCTYPE 时,使用 document.documentElement(指整个节点树的根节点 root,即 标签) 获取文档对象

对页面不具有 DTD,或者说没有指定了 DOCTYPE 时,使用 document.body(即 标签) 获取文档对象

在 IE 和 Firefox 中均是如此。

因此不管 html 源文件有是否 DTD,兼容写法如下:

var scrollTop =
  document.documentElement.scrollTop /* 标准 */ ||
  window.pageYOffset /* Safari */ ||
  document.body.scrollTop; /* IE6/7/8 */ ||0

各浏览器的差异

各浏览器文本都以 DOM 层次结构来描述。

DOM 把层次中的每一个对象都称之为节点,就是一个层次结构,你可以理解为一个树形结构,就像我们的目录一样,一个根目录,根目录下有子目录,子目录下还有子目录。

以 HTML 超文本标记语言为例:整个文档的就是一个根 root(标签),在 DOM 中可以使用 document.documentElement 来访问它,它就是整个节点树的根节点。而 body 是子节点,要访问到 body 标签,在脚本中应该写:document.body。

  • body 是 DOM 对象里的 body 子节点,即 标签;

  • documentElement 是整个节点树的根节点 root,即 标签;

IE6/7/8 的 scrollTop

对于没有 doctype 声明的页面里可以使用 document.body.scrollTop 获取

对于有 doctype 声明的页面则可以使用 document.documentElement.scrollTop 获取

当页面滚动条刚好在最顶端,window.pageYOffset 返回为 undefined

Safari 的 scrollTop

Safari 比较特别,有自己获取 scrollTop 的函数 window.pageYOffset 来获取 scrollTop 的值

Firefox 的 scrollTop

Firefox 等相对标准些的浏览器,可直接使用 W3C 标准方法获取 document.documentElement.scrollTop

兼容获取 scrollTop

对于各浏览器的情况,可以使用逻辑与来获取兼容值

var scrollTop =
  document.documentElement.scrollTop /* 标准 */ ||
  window.pageYOffset /* Safari */ ||
  document.body.scrollTop; /* IE6/7/8 */ || 0

总结

由上述总结可知:

  • IE6/7/8 通过 document.body 获取 scrollTop
  • Safari 通过 window.pageYOffset 获取 scrollTop
  • 标准浏览器 通过 document.documentElement 获取 scrollTop

jQuery 版本回到顶部$('html,body')

$('html,body').animate({ scrollTop: 0 }, 100);

原生版本 兼容版回到顶部

/**
 * 设置页面滚动条距顶部高度
 * @param {Number} top
 */
export function setPageScrollTop(top) {
  document.documentElement.scrollTop /* 标准 */ = top;
  window.pageYOffset /* Safari */ = top;
  document.body.scrollTop /* IE6/7/8 */ = top;
}

/**
 *  获取滚动条距顶部高度
 */
export function getPageScrollTop() {
  let doc = document;
  return (
    doc.documentElement.scrollTop /* 标准 */ || window.pageYOffset /* Safari */ || doc.body.scrollTop /* IE6/7/8 */ || 0
  );
}
var scrollTop =
  document.documentElement.scrollTop /* 标准 */ ||
  window.pageYOffset /* Safari */ ||
  document.body.scrollTop; /* IE6/7/8 */ || 0

直接使用$('html,body')来获取文档

相关文档