Bom

窗口大小

偏移尺寸

第一组属性涉及偏移尺寸(offset dimensions),包含元素在屏幕上占用的所有视觉空间。元素在页 面上的视觉空间由其高度和宽度决定,包括所有内边距、滚动条和边框(但不包含外边距)。以下 4 个 属性用于取得元素的偏移尺寸。

  • offsetHeight,元素在垂直方向上占用的像素尺寸,包括它的高度、水平滚动条高度(如果可 见)和上、下边框的高度。

  • offsetLeft,元素左边框外侧距离包含元素左边框内侧的像素数。

  • offsetTop,元素上边框外侧距离包含元素上边框内侧的像素数。

  • offsetWidth,元素在水平方向上占用的像素尺寸,包括它的宽度、垂直滚动条宽度(如果可 见)和左、右边框的宽度。 其中,offsetLeft和offsetTop是相对于包含元素的,包含元素保存在offsetParent属性中。 offsetParent 不一定是 parentNode。比如,<td>元素的 offsetParent 是作为其祖先的<table> 元素,因为<table>是节点层级中第一个提供尺寸的元素。图 16-1 展示了这些属性代表的不同尺寸。

image-20230515012110926

客户端尺寸

元素的客户端尺寸(client dimensions)包含元素内容及其内边距所占用的空间。客户端尺寸只有两 个相关属性:clientWidth 和 clientHeight。其中,clientWidth 是内容区宽度加左、右内边距宽 度,clientHeight 是内容区高度加上、下内边距高度。图 16-2 形象地展示了这两个属性。

image-20230610000424738

客户端尺寸实际上就是元素内部的空间,因此不包含滚动条占用的空间。这两个属性最常用于确定 浏览器视口尺寸,即检测document.documentElement的clientWidth和clientHeight。这两个 属性表示视口(<html><body>元素)的尺寸。

注意 与偏移尺寸一样,客户端尺寸也是只读的,而且每次访问都会重新计算。

滚动尺寸

最后一组尺寸是滚动尺寸(scroll dimensions),提供了元素内容滚动距离的信息。有些元素,比如 <html>无须任何代码就可以自动滚动,而其他元素则需要使用 CSS 的 overflow 属性令其滚动。滚动 尺寸相关的属性有如下 4 个。

  • scrollHeight,没有滚动条出现时,元素内容的总高度。

  • scrollLeft,内容区左侧隐藏的像素数,设置这个属性可以改变元素的滚动位置。

  • scrollTop,内容区顶部隐藏的像素数,设置这个属性可以改变元素的滚动位置。

  • scrollWidth,没有滚动条出现时,元素内容的总宽度。 图 16-3 展示了这些属性的含义。

image-20230515012613815

下面这个函数检测元素是不是位于顶部,如果不是则把它滚动回顶部:

function scrollToTop(element) {
  if (element.scrollTop != 0) {
    element.scrollTop = 0;
  }
}

这个函数使用 scrollTop 获取并设置值。

确定元素尺寸

浏览器在每个元素上都暴露了 getBoundingClientRect()方法,返回一个 DOMRect 对象,包含 6 个属性:left、top、right、bottom、height 和 width。这些属性给出了元素在页面中相对于视 口的位置。图 16-41展示了这些属性的含义。

image-20230515012957679

客户端坐标

可以通过下面的方式获取鼠标事件的客户端坐标:

let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
  console.log(`Client coordinates: ${event.clientX}, ${event.clientY}`);
});
image-20230515081628048

页面坐标

let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
  console.log(`Page coordinates: ${event.pageX}, ${event.pageY}`);
});

在页面没有滚动时,pageX 和 pageY 与 clientX 和 clientY 的值相同。

IE8及更早版本没有在event对象上暴露页面坐标。不过,可以通过客户端坐标和滚动信息计算出 来。滚动信息可以从 document.body(混杂模式)或 document.documentElement(标准模式)的 scrollLeft 和 scrollTop 属性获取。计算过程如下所示:

let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
  let pageX = event.pageX,
    pageY = event.pageY;
  if (pageX === undefined) {
    pageX = event.clientX + (document.body.scrollLeft ||
      document.documentElement.scrollLeft);
  }
  if (pageY === undefined) {
    pageY = event.clientY + (document.body.scrollTop ||
      document.documentElement.scrollTop);
  }
  console.log(`Page coordinates: ${pageX}, ${pageY}`);
});

屏幕坐标

可以像下面这样获取鼠标事件的屏幕坐标:

let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
  console.log(`Screen coordinates: ${event.screenX}, ${event.screenY}`);
});
image-20230515082025158

在浏览器中,窗口大小有不同的含义,包括以下几个概念:

内容区大小(Viewport Size):指浏览器窗口内网页的可见区域大小,不包括浏览器的工具栏、菜单栏、边框等。

窗口大小(Window Size):指整个浏览器窗口的大小,(outerWidth、outerHeight)包括内容区、工具栏、菜单栏、边框等。

文档大小(Document Size):指网页文档的实际大小,包括了所有内容,无论是否在可视区域内。

工具栏(Toolbar):通常位于浏览器窗口的顶部,包含一些常用的工具按钮,如前进、后退、刷新、主页等等。工具栏可以通过浏览器的设置进行开启或关闭。

菜单栏(Menu Bar):通常位于工具栏下面,包含一些菜单,如文件、编辑、查看、收藏夹等等。菜单栏可以通过浏览器的设置进行开启或关闭。

边框(Border):是浏览器窗口的边框,用来分隔窗口和操作系统的其他部分。边框包括窗口的标题栏、边框线和窗口控制按钮等等。边框的大小和样式可以通过操作系统的设置进行调整。

获取内容区大小:

const viewportWidth = document.documentElement.clientWidth;
const viewportHeight = document.documentElement.clientHeight;

获取窗口大小:

const windowWidth = window.outerWidth;
const windowHeight = window.outerHeight;
// innerWidth、innerHeight不包含浏览器边框和工具栏
const pageWidth = window.innerWidth;
const pageHeight = window.innerHeight;

获取文档大小:


const documentWidth = document.documentElement.scrollWidth;
const documentHeight = document.documentElement.scrollHeight;

需要注意的是,由于浏览器的不同以及浏览器设置的不同,这些值可能会有所不同,使用时需要注意兼容性和浏览器差异。

视口位置

// 相对于当前视口向下滚动 100 像素
 window.scrollBy(0, 100);
// 相对于当前视口向右滚动 40 像素 
window.scrollBy(40, 0);
// 滚动到页面左上角 
window.scrollTo(0, 0);

// 滚动到距离屏幕左边及顶边各 100 像素的位置 window.scrollTo(100, 100);

这几个方法也都接收一个 ScrollToOptions 字典,除了提供偏移值,还可以通过 behavior 属性 告诉浏览器是否平滑滚动。

 // 正常滚动
window.scrollTo({
  left: 100,
  top: 100,
  behavior: 'auto'
});
// 平滑滚动
window.scrollTo({ 4
  left: 100,
  top: 100,
  behavior: 'smooth'
});

location对象

查询字符串

函数解析了查询字符串 location.search

let getQueryStringArgs = function () {
  // 取得没有开头问号的查询字符串
  let qs = (location.search.length > 0 ? location.search.substring(1) : ""),
  // 保存数据的对象 args = {};
  // 把每个参数添加到 args 对象
  for (let item of qs.split("&").map(kv => kv.split("="))) {
    let name = decodeURIComponent(item[0]),
      value = decodeURIComponent(item[1]);
    if (name.length) {
      args[name] = value;
    }
  }
  return args;
}

let args = getQueryStringArgs(); 
// 假设查询字符串为?q=javascript&num=10
alert(args["q"]);    // "javascript"
alert(args["num"]);  // "10"

URLSearchParams

URLSearchParams 提供了一组标准 API 方法,通过它们可以检查和修改查询字符串。给 URLSearchParams 构造函数传入一个查询字符串,就可以创建一个实例。这个实例上暴露了 get()、 set()和 delete ()等方法,可以对查询字符串执行相应操作。下面来看一个例子:

let qs = "?q=javascript&num=10";
let searchParams = new URLSearchParams(qs);
alert(searchParams.toString());  // " q=javascript&num=10"
searchParams.has("num"); // true
searchParams.get("num"); // 10
searchParams.set("page", "3");
alert(searchParams.toString());  // " q=javascript&num=10&page=3"

searchParams.delete("q");
alert(searchParams.toString());  // " num=10&page=3"

let searchParams = new URLSearchParams(qs);
for (let param of searchParams) {
  console.log(param);
}
// ["q", "javascript"]  
// ["num", "10"]

操作地址

可以通过修改 location 对象修改浏览器的地址。首先,最常见的是使用 assign()方法并传入一 个 URL,如下所示:

location.assign("http://www.wrox.com");

这行代码会立即启动导航到新 URL 的操作,同时在浏览器历史记录中增加一条记录。如果给 location.href 或 window.location 设置一个 URL,也会以同一个 URL 值调用 assign()方法。比 如,下面两行代码都会执行与显式调用 assign()一样的操作:

window.location = "http://www.wrox.com";
location.href = "http://www.wrox.com";

在这 3 种修改浏览器地址的方法中,设置 location.href 是最常见的。 修改 location 对象的属性也会修改当前加载的页面。其中,hash、search、hostname、pathname 和 port 属性被设置为新值之后都会修改当前 URL,如下面的例子所示:

// 假设当前URL为<http://www.wrox.com/WileyCDA/>
// 把URL修改为<http://www.wrox.com/WileyCDA/#section1>
location.hash = "#section1";
// 把URL修改为<http://www.wrox.com/WileyCDA/?q=javascript>
location.search = "?q=javascript";
// 把URL修改为<http://www.somewhere.com/WileyCDA/>
location.hostname = "www.somewhere.com";
// 把URL修改为<http://www.somewhere.com/mydir/>
location.pathname = "mydir";
// 把URL修改为<http://www.somewhere.com:8080/WileyCDA/>
location.port = 8080;

除了 hash 之外,只要修改 location 的一个属性,就会导致页面重新加载新 URL。 注意 修改hash的值会在浏览器历史中增加一条新记录。在早期的IE中,点击“后退” 和“前进”按钮不会更新 hash 属性,只有点击包含散列的 URL 才会更新 hash 的值。

在以前面提到的方式修改 URL 之后,浏览器历史记录中就会增加相应的记录。当用户单击“后退” 按钮时,就会导航到前一个页面。如果不希望增加历史记录,可以使用 replace()方法。这个方法接 收一个 URL 参数,但重新加载后不会增加历史记录。调用 replace()之后,用户不能回到前一页。比 如下面的例子:

<!DOCTYPE html>
<html>
<head>
  <title>You won't be able to get back here</title>
</head>
<body>
  <p>Enjoy this page for a second, because you won't be coming back here.</p>
  <script>
  setTimeout(() => location.replace("http://www.wrox.com/"), 1000); </script>
</body>
</html>

浏览器加载这个页面 1 秒之后会重定向到 www.wrox.com。此时,“后退”按钮是禁用状态,即不能 返回这个示例页面,除非手动输入完整的 URL。

最后一个修改地址的方法是 reload(),它能重新加载当前显示的页面。调用 reload()而不传参 数,页面会以最有效的方式重新加载。也就是说,如果页面自上次请求以来没有修改过,浏览器可能会 从缓存中加载页面。如果想强制从服务器重新加载,可以像下面这样给 reload()传个 true:

location.reload(); // 重新加载,可能是从缓存加载 location.reload(true); // 重新加载,从服务器加载

脚本中位于 reload()调用之后的代码可能执行也可能不执行,这取决于网络延迟和系统资源等因 素。为此,最好把 reload()作为最后一行代码。

history对象

导航

// 后退一页
history.go(-1);
// 前进一页
history.go(1);
// 前进两页
history.go(2);

// 导航到最近的 wrox.com 页面 
history.go("wrox.com");
// 导航到最近的 nczonline.net 页面 
history.go("nczonline.net");
// go()有两个简写方法:back()和 forward()。顾名思义,这两个方法模拟了浏览器的后退按钮和 前进按钮:
// 后退一页 history.back();
// 前进一页 history.forward();

history 对象还有一个 length 属性,表示历史记录中有多个条目。这个属性反映了历史记录的数 量,包括可以前进和后退的页面。对于窗口或标签页中加载的第一个页面,history.length 等于 1。 通过以下方法测试这个值,可以确定用户浏览器的起点是不是你的页面:

if (history.length == 1){ // 这是用户窗口中的第一个页面
}

history 对象通常被用于创建“后退”和“前进”按钮,以及确定页面是不是用户历史记录中的第 一条记录。

历史状态管理

hashchange 会在页面 URL 的散列变化时被触发,开发者可以在此时执行某些操作。而状态管理 API 则可以让开发者改变浏览器 URL 而不会加载新页面。为此,可以使用 history.pushState()方 法。这个方法接收 3 个参数:一个 state 对象、一个新状态的标题和一个(可选的)相对 URL。例如:

let stateObject = {foo:"bar"};
history.pushState(stateObject, "My title", "baz.html");

pushState()方法执行后,状态信息就会被推到历史记录中,浏览器地址栏也会改变以反映新的相 对 URL。除了这些变化之外,即使 location.href 返回的是地址栏中的内容,浏览器页不会向服务器发送请求。第二个参数并未被当前实现所使用,因此既可以传一个空字符串也可以传一个短标题。第一 个参数应该包含正确初始化页面状态所必需的信息。为防止滥用,这个状态的对象大小是有限制的,通 常在 500KB~1MB 以内。 因为 pushState()会创建新的历史记录,所以也会相应地启用“后退”按钮。此时单击“后退” 按钮,就会触发 window 对象上的 popstate 事件。popstate 事件的事件对象有一个 state 属性,其 中包含通过 pushState()第一个参数传入的 state 对象:

window.addEventListener("popstate", (event) => { let state = event.state;
if (state) { // 第一个页面加载时状态是null
    processState(state);
  }
});

基于这个状态,应该把页面重置为状态对象所表示的状态(因为浏览器不会自动为你做这些)。记 住,页面初次加载时没有状态。因此点击“后退”按钮直到返回最初页面时,event.state 会为 null。 可以通过 history.state 获取当前的状态对象,也可以使用 replaceState()并传入与 pushState()同样的前两个参数来更新状态。更新状态不会创建新历史记录,只会覆盖当前状态:

history.replaceState({newFoo: "newBar"}, "New title");

传给 pushState()和 replaceState()的 state 对象应该只包含可以被序列化的信息。因此, DOM 元素之类并不适合放到状态对象里保存。

注意 使用HTML5状态管理时,要确保通过pushState()创建的每个“假”URL背后 都对应着服务器上一个真实的物理 URL。否则,单击“刷新”按钮会导致 404 错误。所有 单页应用程序(SPA,Single Page Application)框架都必须通过服务器或客户端的某些配 置解决这个问题。

Last updated