在前面的章节中,我们围绕 CSSOM 的核心做了两件事:读取样式(通过 getComputedStyle)和 修改样式(通过操作样式表或 element.style)。

但 CSSOM 的能力远不止于此。

回想一下你日常开发中的这些场景:

  • 页面滚动到某个位置时,导航栏自动吸顶。

  • 点击“回到顶部”按钮,页面平滑滚动上去。

  • 实现一个图片懒加载 —— 当图片进入视口时才加载。

  • 获取某个元素在页面中的精确位置,用于拖拽或定位。

这些功能都涉及一个共同的领域:视图(View) —— 窗口有多大?页面滚动了多少?元素在屏幕上的什么位置?

这些需求,属于 CSSOM 的另一个重要组成部分:CSSOM View

CSSOM View 模块在 Window、Document、Element 等接口上扩展了大量属性和方法。它不直接操作样式本身,而是操作样式的 呈现结果——窗口的位置与大小、页面的滚动状态、元素的布局信息。本章将系统性地梳理这些 API,让你在面对“与视图相关”的需求时,知道该用什么工具。

一、窗口 API —— 控制浏览器窗口本身

窗口 API 提供了一组方法,用于控制浏览器窗口的位置和大小。这些方法直接作用于 window 对象。

1.1 移动窗口:moveTo() 与 moveBy()

moveTo(x, y) 将窗口移动到屏幕上的绝对位置:

// 将窗口移动到屏幕左上角
window.moveTo(0, 0);

// 将窗口移动到屏幕中央(假设屏幕分辨率为 1920×1080)
window.moveTo(460, 240);

moveBy(x, y) 将窗口相对于当前位置移动指定的偏移量:

// 将窗口向右移动 100 像素,向下移动 50 像素
window.moveBy(100, 50);

// 向左移动(负值)
window.moveBy(-50, 0);
1.2 调整窗口大小:resizeTo() 与 resizeBy()

resizeTo(width, height) 将窗口调整为指定的宽度和高度:

// 将窗口调整为 800×600
window.resizeTo(800, 600);

resizeBy(x, y) 将窗口的宽度和高度分别增加指定的像素值:

// 窗口宽度增加 100px,高度增加 50px
window.resizeBy(100, 50);

// 缩小窗口
window.resizeBy(-50, -30);
1.3 通过 window.open() 控制新窗口

window.open() 的 第三个参数 是一个逗号分隔的特性字符串,用于控制新窗口的外观和行为。

// 打开一个 400×300 的新窗口,位于屏幕 (100, 100) 位置
const newWindow = window.open(
    'https://example.com',
    'exampleWindow',
    'width=400,height=300,left=100,top=100'
);

常用的窗口特性包括:

特性

描述

width=number

窗口宽度(像素)

height=number

窗口高度(像素)

left=number

窗口距屏幕左侧的距离

top=number

窗口距屏幕顶部的距离

resizable=yes/no

窗口是否可调整大小

重要提醒

这些窗口控制方法在现代浏览器中受到严格限制

  1. 必须由用户操作触发:moveTo、moveBy、resizeTo、resizeBy 以及 window.open 都必须在用户手势(如点击事件)中调用,否则会被浏览器拦截。

  2. 不能跨域控制:只能控制当前浏览器标签页的窗口,不能控制其他标签页或跨域窗口。

  3. 弹窗拦截:window.open 如果不在用户手势中调用,会被浏览器视为弹窗而拦截。

因此,在实际开发中,这些 API 的使用场景比较有限——通常用于一些特殊的 Web 应用(如在线编辑器、控制台工具)或企业内部系统。

开发建议:除非你有明确的、必须控制浏览器窗口的需求,否则在日常 Web 开发中,这些 API 用得不多。相比之下,本章后续的滚动 API 和布局 API 才是你更常用的工具。

二、滚动 API —— 操控页面的滚动

滚动是 Web 开发中最常见的交互之一。CSSOM View 提供了从“全局页面滚动”到“单个元素滚动”的完整控制能力。

2.1 获取视口滚动位置

要获取页面当前滚动了多少,可以使用 window.scrollX 和 window.scrollY(或它们的别名 window.pageXOffset 和 window.pageYOffset)。

// 获取水平滚动距离
console.log(window.scrollX);      // 推荐
console.log(window.pageXOffset);  // 别名,兼容性更好

// 获取垂直滚动距离
console.log(window.scrollY);      // 推荐
console.log(window.pageYOffset);  // 别名,兼容性更好

小贴士:pageXOffset / pageYOffset 是更早的规范,兼容性更好。不过在现代浏览器中,scrollX / scrollY 已经得到广泛支持,两者完全等价。

2.2 控制页面滚动:scroll()、scrollTo()、scrollBy()

这三个方法都用于控制页面滚动,区别在于 滚动目标 的指定方式不同。

scrollTo(x, y) —— 滚动到指定的绝对位置:

// 滚动到页面顶部
window.scrollTo(0, 0);

// 滚动到水平 100px、垂直 500px 的位置
window.scrollTo(100, 500);

scrollBy(x, y) —— 从当前位置滚动指定的偏移量:

// 向下滚动 200 像素
window.scrollBy(0, 200);

// 向右滚动 100 像素,向下滚动 50 像素
window.scrollBy(100, 50);

scroll() —— 与 scrollTo() 完全等价,只是名称不同。

平滑滚动

以上三个方法都支持传入一个 配置对象 来实现平滑滚动:

// 使用配置对象实现平滑滚动到顶部
window.scrollTo({
    top: 0,
    left: 0,
    behavior: 'smooth'  // 'smooth' | 'instant' | 'auto'
});

// 平滑向下滚动 300 像素
window.scrollBy({
    top: 300,
    left: 0,
    behavior: 'smooth'
});

behavior 的可选值:

  • 'smooth':平滑动画滚动。

  • 'instant':瞬间跳转。

  • 'auto':由 CSS 的 scroll-behavior 属性决定。

体验提示:平滑滚动能显著提升用户体验,尤其是在“回到顶部”这类操作中。推荐在适当的场景中使用 behavior: 'smooth'。

2.3 元素滚动属性:scrollTop、scrollLeft、scrollWidth、scrollHeight

除了控制整个页面的滚动,我们还可以控制 单个可滚动元素 的滚动状态。

滚动位置属性(可读写)

  • element.scrollTop:元素内容垂直向上滚动的像素值(即顶部被隐藏的高度)。

  • element.scrollLeft:元素内容水平向左滚动的像素值(即左侧被隐藏的宽度)。

const container = document.querySelector('.scrollable-container');

// 读取当前滚动位置
console.log('垂直滚动:', container.scrollTop);
console.log('水平滚动:', container.scrollLeft);

// 设置滚动位置 —— 滚动到顶部
container.scrollTop = 0;

// 滚动到底部
container.scrollTop = container.scrollHeight - container.clientHeight;

内容尺寸属性(只读)

  • element.scrollWidth:元素内容的总宽度(包括溢出部分),包含内边距但不包含边框和外边距。

  • element.scrollHeight:元素内容的总高度(包括溢出部分),包含内边距但不包含边框和外边距。

// 判断是否可滚动(内容是否溢出)
const canScroll = container.scrollHeight > container.clientHeight;

// 计算滚动进度(0~1)
const progress = container.scrollTop / (container.scrollHeight - container.clientHeight);
console.log('滚动进度:', (progress * 100).toFixed(0) + '%');
2.4 scrollIntoView() —— 将元素滚动到可见区域

scrollIntoView() 是 Element 接口上的方法,它会滚动元素的祖先容器,使得调用该方法的元素对用户可见

这是前端开发中最常用的滚动 API 之一,比如:表单项校验失败时自动滚动到错误位置、点击导航菜单滚动到对应章节等。

基本用法

const element = document.getElementById('target');

// 默认行为:元素顶部对齐到视口顶部
element.scrollIntoView();

// 等价于
element.scrollIntoView(true);   // true = 顶部对齐
element.scrollIntoView(false);  // false = 底部对齐

使用配置对象(推荐)

element.scrollIntoView({
    behavior: 'smooth',   // 'smooth' | 'instant' | 'auto'
    block: 'center',      // 'start' | 'center' | 'end' | 'nearest'
    inline: 'nearest'     // 'start' | 'center' | 'end' | 'nearest'
});

配置项说明:

选项

可选值

描述

behavior

'smooth'、'instant'、'auto'

滚动动画方式

block

'start'、'center'、'end'、'nearest'

垂直方向的对齐方式

inline

'start'、'center'、'end'、'nearest'

水平方向的对齐方式

// 将元素居中显示在视口中(垂直和水平都居中)
element.scrollIntoView({
    behavior: 'smooth',
    block: 'center',
    inline: 'center'
});
2.5 监听滚动事件

当用户滚动页面或可滚动元素时,会触发 scroll 事件

// 监听整个页面的滚动
window.addEventListener('scroll', function() {
    console.log('页面滚动位置:', window.scrollY);
});

// 监听某个元素的滚动
const container = document.querySelector('.scrollable');
container.addEventListener('scroll', function() {
    console.log('元素滚动位置:', this.scrollTop);
});

性能优化提示:scroll 事件触发的频率非常高(每帧都可能触发),在事件处理函数中 不要做复杂的计算或 DOM 操作。如果需要做复杂操作,可以使用 requestAnimationFrame防抖(debounce) 来优化。

// 使用 requestAnimationFrame 优化滚动事件
let ticking = false;
window.addEventListener('scroll', function() {
    if (!ticking) {
        window.requestAnimationFrame(function() {
            // 在这里做滚动相关的处理
            console.log('滚动位置:', window.scrollY);
            ticking = false;
        });
        ticking = true;
    }
});

补充:如果需要检测滚动是否结束,可以使用 scrollend 事件,它会在滚动完成时触发。

三、布局 API —— 获取元素的位置与尺寸

在开发中,我们经常需要知道一个元素在页面中的精确位置和尺寸——比如实现拖拽、定位工具提示、判断元素是否在视口内等。CSSOM View 提供了多种方式来获取这些信息。

视口尺寸(浏览器窗口中可见区域的尺寸):

// 视口尺寸(包含滚动条)
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;

// 视口尺寸(不包含滚动条)—— 通过 document.documentElement 获取
const clientWidth = document.documentElement.clientWidth;
const clientHeight = document.documentElement.clientHeight;

浏览器窗口外部尺寸(包含工具栏、地址栏等):

// 浏览器窗口的完整宽度和高度
const outerWidth = window.outerWidth;
const outerHeight = window.outerHeight;

属性

包含内容

典型用途

window.innerWidth/Height

视口(含滚动条)

响应式布局、视口检测

document.documentElement.clientWidth/Height

视口(不含滚动条)

精确的可用宽度

window.outerWidth/Height

整个浏览器窗口

较少使用

3.2 获取元素的位置与尺寸

getBoundingClientRect() 是最常用的元素位置获取方法。它返回一个 DOMRect 对象,包含了元素的大小及其 相对于视口 的位置信息。

const rect = element.getBoundingClientRect();

console.log('位置(相对于视口):');
console.log('  top:', rect.top);      // 元素顶部距视口顶部的距离
console.log('  left:', rect.left);    // 元素左侧距视口左侧的距离
console.log('  bottom:', rect.bottom); // 元素底部距视口顶部的距离
console.log('  right:', rect.right);   // 元素右侧距视口左侧的距离

console.log('尺寸:');
console.log('  width:', rect.width);   // 元素宽度(含 padding 和 border)
console.log('  height:', rect.height); // 元素高度(含 padding 和 border)

关键理解

1. getBoundingClientRect()返回的 top、left 是 相对于视口 的,会随着页面滚动而变化。

2. 如果需要获取元素 相对于整个文档 的位置,需要加上滚动偏移量:

// 获取元素相对于文档顶部的位置
const rect = element.getBoundingClientRect();
const absoluteTop = rect.top + window.scrollY;
const absoluteLeft = rect.left + window.scrollX;

3. width 和 height 包含了 padding 和 border。

function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (
        rect.top < window.innerHeight &&
        rect.bottom > 0 &&
        rect.left < window.innerWidth &&
        rect.right > 0
    );
}

// 使用示例:图片懒加载
const images = document.querySelectorAll('.lazy-image');
images.forEach(img => {
    if (isInViewport(img)) {
        img.src = img.dataset.src;
        img.classList.remove('lazy-image');
    }
});
3.3 offset 系列属性

除了 getBoundingClientRect(),还有一组 offset 属性 可以获取元素的位置信息。

  • offsetTop:元素上边框到其 offsetParent 上边框的垂直距离。

  • offsetLeft:元素左边框到其 offsetParent 左边框的水平距离。

  • offsetParent:距离该元素最近的 已定位(position 不为 static)的祖先元素。

// 获取元素相对于其 offsetParent 的位置
const top = element.offsetTop;
const left = element.offsetLeft;
const parent = element.offsetParent;

console.log('相对于 offsetParent:', top, left);
console.log('offsetParent:', parent);

getBoundingClientRect() vs offset 属性

维度

getBoundingClientRect()

offsetTop / offsetLeft

参考系

视口(会随滚动变化)

offsetParent (不随滚动变化)

包含的内容

padding + border + 内容

从边框到边框

是否只读

使用场景

视口相关判断(懒加载、吸顶)

相对于父元素的定位计算

3.4 client 系列属性

还有一组 client 属性,主要用于获取元素的内部尺寸:

  • element.clientWidth:元素内部宽度(内容区 + padding,不含 border 和滚动条)。

  • element.clientHeight:元素内部高度(内容区 + padding,不含 border 和滚动条)。

  • element.clientTop:元素上边框的宽度。

  • element.clientLeft:元素左边框的宽度。

// 获取元素的可视内容区域尺寸(不含滚动条)
const visibleWidth = element.clientWidth;
const visibleHeight = element.clientHeight;

// 获取边框宽度
const borderTop = element.clientTop;
const borderLeft = element.clientLeft;

四、综合示例:实现一个“回到顶部”按钮

将本章的多个知识点整合到一个完整的示例中:

<style>
    body {
        height: 2000px;
        font-family: Arial, sans-serif;
    }
    .back-to-top {
        position: fixed;
        bottom: 40px;
        right: 40px;
        padding: 12px 20px;
        background: #007bff;
        color: white;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        opacity: 0;
        transform: translateY(20px);
        transition: all 0.3s ease;
        pointer-events: none;
    }
    .back-to-top.visible {
        opacity: 1;
        transform: translateY(0);
        pointer-events: auto;
    }
    .back-to-top:hover {
        background: #0056b3;
    }
    .progress-bar {
        position: fixed;
        top: 0;
        left: 0;
        height: 4px;
        background: linear-gradient(to right, #007bff, #00d4ff);
        width: 0%;
        transition: width 0.1s;
        z-index: 999;
    }
</style>

<div class="progress-bar" id="progressBar"></div>
<button class="back-to-top" id="backToTop">↑ 回到顶部</button>

<h1>滚动示例页面</h1>
<p style="margin-top: 800px;">向下滚动看看效果</p>

<script>
    const backBtn = document.getElementById('backToTop');
    const progressBar = document.getElementById('progressBar');
    
    // 监听页面滚动
    window.addEventListener('scroll', function() {
        const scrollY = window.scrollY;
        const docHeight = document.documentElement.scrollHeight - window.innerHeight;
        
        // 1. 更新进度条
        const progress = (scrollY / docHeight) * 100;
        progressBar.style.width = progress + '%';
        
        // 2. 控制“回到顶部”按钮的显示/隐藏
        if (scrollY > 300) {
            backBtn.classList.add('visible');
        } else {
            backBtn.classList.remove('visible');
        }
    });
    
    // 点击按钮平滑滚动到顶部
    backBtn.addEventListener('click', function() {
        window.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
    });
    
    // 示例:获取某个元素的位置
    const title = document.querySelector('h1');
    const rect = title.getBoundingClientRect();
    console.log('标题相对于视口的位置:', rect.top, rect.left);
    console.log('标题相对于文档的位置:', rect.top + window.scrollY, rect.left + window.scrollX);
</script>

这个示例展示了:

  • 使用 window.scrollY 监听滚动位置。

  • 使用 scrollTo() 实现平滑滚动回到顶部。

  • 使用 getBoundingClientRect() 获取元素位置。

  • 结合 CSS 类切换实现 UI 状态管理。

五、小结

本章我们完整学习了 CSSOM View 模块的核心功能:

1. 窗口 API

  • moveTo() / moveBy() —— 控制窗口位置。

  • resizeTo() / resizeBy() —— 控制窗口大小。

  • window.open() 的第三个参数控制新窗口尺寸。

  • 这些 API 受到严格的用户手势限制,使用场景有限。

2. 滚动 API

  • scrollX / scrollY(或 pageXOffset / pageYOffset)—— 获取视口滚动位置。

  • scrollTo() —— 滚动到指定位置。

  • scrollBy() —— 相对当前位置滚动。

  • 通过 { behavior: 'smooth' } 实现平滑滚动。

  • 元素级别的 scrollTop / scrollLeft(可读写)和 scrollWidth / scrollHeight(只读)。

  • scrollIntoView() —— 将元素滚动到可见区域,支持精细的对齐控制。

  • scroll 事件监听滚动,注意性能优化。

3. 布局 API

  • window.innerWidth / innerHeight —— 视口尺寸(含滚动条)。

  • document.documentElement.clientWidth / clientHeight —— 视口尺寸(不含滚动条)。

  • getBoundingClientRect() —— 获取元素相对于视口的位置和尺寸(最常用)。

  • offsetTop / offsetLeft / offsetParent —— 获取元素相对于定位父元素的位置。

  • clientWidth / clientHeight —— 获取元素内部可视尺寸。

CSSOM View 是你日常开发中使用频率最高的 API 集合之一。从“回到顶部”到“图片懒加载”,从“滚动吸顶”到“元素定位”,这些 API 几乎出现在每一个交互稍复杂的页面中。掌握它们,你就能自信地应对绝大多数与“视图”相关的需求。

在下一章中,我们将进入 CSSOM 系列的一个进阶话题——CSS 类型对象模型(CSS Typed OM),了解如何用类型化的方式更高效、更安全地操作 CSS 值。