MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript操作DOM样式的细节

2024-05-014.2k 阅读

一、DOM 样式基础概述

在网页开发中,文档对象模型(DOM)是表示和操作 HTML 与 XML 文档的标准方式。而 JavaScript 作为前端开发的核心语言之一,对 DOM 样式的操作至关重要。通过操作 DOM 样式,我们能够动态地改变网页元素的外观,为用户提供更加丰富和交互性强的体验。

DOM 样式主要通过元素的 style 属性来进行直接操作,同时也可以借助 class 类名,利用 CSS 规则来间接改变样式。style 属性是一个 CSSStyleDeclaration 对象,它包含了一系列与 CSS 属性对应的 JavaScript 属性。例如,CSS 中的 font-size 属性,在 JavaScript 中对应的是 style.fontSize

二、直接操作 style 属性

2.1 获取元素的 style 属性

在 JavaScript 中,首先要获取到目标元素,才能对其 style 属性进行操作。获取元素的方式有多种,比如使用 document.getElementByIddocument.querySelector 等方法。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv">这是一个 div 元素</div>
    <script>
        const myDiv = document.getElementById('myDiv');
        console.log(myDiv.style);
    </script>
</body>
</html>

在上述代码中,通过 document.getElementById 获取到 idmyDiv 的元素,并打印出它的 style 属性。此时,如果在 HTML 中没有给该元素的 style 属性赋值,那么打印出来的 style 对象中包含的属性值大多为空字符串。

2.2 设置元素的 style 属性

设置元素的 style 属性可以直接改变元素的外观。例如,要改变一个元素的背景颜色和字体大小:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv">这是一个 div 元素</div>
    <script>
        const myDiv = document.getElementById('myDiv');
        myDiv.style.backgroundColor = 'lightblue';
        myDiv.style.fontSize = '20px';
    </script>
</body>
</html>

在这个例子中,分别使用 style.backgroundColorstyle.fontSize 设置了 div 元素的背景颜色和字体大小。需要注意的是,JavaScript 中的 CSS 属性名采用驼峰命名法,例如 background-color 在 JavaScript 中为 backgroundColor

2.3 读取元素的计算样式

有时候,我们需要获取元素实际显示的样式,也就是计算样式。window.getComputedStyle 方法可以用来获取元素的计算样式。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
        #myDiv {
            width: 200px;
            height: 100px;
            background-color: lightblue;
            font-size: 16px;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv">这是一个 div 元素</div>
    <script>
        const myDiv = document.getElementById('myDiv');
        const computedStyle = window.getComputedStyle(myDiv);
        console.log(computedStyle.width);
        console.log(computedStyle.backgroundColor);
    </script>
</body>
</html>

在上述代码中,通过 window.getComputedStyle 获取到 myDiv 元素的计算样式,并打印出其宽度和背景颜色。getComputedStyle 返回的是一个 CSSStyleDeclaration 对象,包含了所有计算后的 CSS 属性值。

三、通过 class 操作样式

3.1 添加和移除 class

通过操作元素的 class 可以更方便地复用 CSS 样式。在 JavaScript 中,可以使用 classList 属性来操作 classclassList 是一个 DOMTokenList 对象,提供了 addremovetoggle 等方法。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
       .highlight {
            background-color: yellow;
            color: red;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv">这是一个 div 元素</div>
    <button onclick="addHighlight()">添加高亮</button>
    <button onclick="removeHighlight()">移除高亮</button>
    <script>
        function addHighlight() {
            const myDiv = document.getElementById('myDiv');
            myDiv.classList.add('highlight');
        }
        function removeHighlight() {
            const myDiv = document.getElementById('myDiv');
            myDiv.classList.remove('highlight');
        }
    </script>
</body>
</html>

在上述代码中,通过 classList.add 方法为 div 元素添加 highlight 类,通过 classList.remove 方法移除该类。这样就可以通过 CSS 中定义的 highlight 类的样式来改变元素的外观。

3.2 切换 class

classList.toggle 方法可以在元素上切换一个 class。如果 class 存在则移除,如果不存在则添加。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
       .highlight {
            background-color: yellow;
            color: red;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv">这是一个 div 元素</div>
    <button onclick="toggleHighlight()">切换高亮</button>
    <script>
        function toggleHighlight() {
            const myDiv = document.getElementById('myDiv');
            myDiv.classList.toggle('highlight');
        }
    </script>
</body>
</html>

点击按钮时,toggleHighlight 函数会根据 myDiv 元素是否已经有 highlight 类,来决定是添加还是移除该类,从而实现样式的切换。

3.3 检查 class 是否存在

有时候需要检查元素是否已经有某个 class,可以使用 classList.contains 方法。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
       .highlight {
            background-color: yellow;
            color: red;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv" class="highlight">这是一个 div 元素</div>
    <button onclick="checkHighlight()">检查高亮</button>
    <script>
        function checkHighlight() {
            const myDiv = document.getElementById('myDiv');
            if (myDiv.classList.contains('highlight')) {
                console.log('元素有 highlight 类');
            } else {
                console.log('元素没有 highlight 类');
            }
        }
    </script>
</body>
</html>

上述代码中,通过 classList.contains 方法检查 myDiv 元素是否包含 highlight 类,并在控制台打印相应的信息。

四、操作伪类样式

4.1 伪类概述

CSS 伪类用于向某些选择器添加特殊的效果。常见的伪类有 :hover:active:visited 等。在 JavaScript 中,虽然不能直接像操作普通样式那样操作伪类样式,但可以通过模拟用户行为来间接实现。

4.2 模拟 :hover 效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
        #myButton:hover {
            background-color: lightgreen;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <button id="myButton">悬停我</button>
    <script>
        const myButton = document.getElementById('myButton');
        myButton.addEventListener('mouseenter', function () {
            this.style.backgroundColor = 'lightgreen';
        });
        myButton.addEventListener('mouseleave', function () {
            this.style.backgroundColor = '';
        });
    </script>
</body>
</html>

在上述代码中,通过 addEventListener 方法监听 mouseentermouseleave 事件,当鼠标进入按钮时,设置按钮的背景颜色为 lightgreen,模拟 :hover 伪类的效果;当鼠标离开按钮时,清除背景颜色。

4.3 模拟 :active 效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
        #myButton:active {
            color: white;
            background-color: blue;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <button id="myButton">点击我</button>
    <script>
        const myButton = document.getElementById('myButton');
        myButton.addEventListener('mousedown', function () {
            this.style.color = 'white';
            this.style.backgroundColor = 'blue';
        });
        myButton.addEventListener('mouseup', function () {
            this.style.color = '';
            this.style.backgroundColor = '';
        });
    </script>
</body>
</html>

这里通过监听 mousedownmouseup 事件,当鼠标按下按钮时,设置按钮的颜色和背景颜色,模拟 :active 伪类的效果;当鼠标松开时,恢复原来的样式。

五、动画与过渡效果的操作

5.1 使用 CSS 过渡实现动画效果

CSS 过渡可以在元素的属性值发生变化时创建平滑的过渡效果。在 JavaScript 中,可以通过改变元素的 style 属性来触发过渡。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
        #myDiv {
            width: 100px;
            height: 100px;
            background-color: lightblue;
            transition: width 2s ease;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <button onclick="enlargeDiv()">放大 div</button>
    <script>
        function enlargeDiv() {
            const myDiv = document.getElementById('myDiv');
            myDiv.style.width = '200px';
        }
    </script>
</body>
</html>

在上述代码中,myDiv 元素定义了一个过渡效果,当 width 属性发生变化时,会在 2 秒内以 ease 的方式过渡。点击按钮时,通过 JavaScript 改变 width 属性,从而触发过渡效果。

5.2 使用 CSS 动画实现复杂动画

CSS 动画比过渡更加灵活,可以创建复杂的动画效果。通过 JavaScript 可以控制动画的播放、暂停、停止等。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
        @keyframes move {
            from {
                left: 0px;
            }
            to {
                left: 200px;
            }
        }
        #myDiv {
            position: relative;
            width: 50px;
            height: 50px;
            background-color: lightblue;
            animation: move 5s infinite;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <button onclick="pauseAnimation()">暂停动画</button>
    <script>
        function pauseAnimation() {
            const myDiv = document.getElementById('myDiv');
            myDiv.style.animationPlayState = 'paused';
        }
    </script>
</body>
</html>

在这个例子中,定义了一个名为 move 的动画,myDiv 元素会在 5 秒内从左边 0 像素的位置移动到左边 200 像素的位置,并且无限循环。通过点击按钮,使用 JavaScript 将 animationPlayState 属性设置为 paused,从而暂停动画。

5.3 监听动画和过渡事件

在 JavaScript 中,可以监听动画和过渡的事件,比如 animationstartanimationendtransitionend 等。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
        #myDiv {
            width: 100px;
            height: 100px;
            background-color: lightblue;
            transition: width 2s ease;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <button onclick="enlargeDiv()">放大 div</button>
    <script>
        const myDiv = document.getElementById('myDiv');
        myDiv.addEventListener('transitionend', function () {
            console.log('过渡结束');
        });
        function enlargeDiv() {
            myDiv.style.width = '200px';
        }
    </script>
</body>
</html>

上述代码中,为 myDiv 元素添加了 transitionend 事件监听器,当过渡效果结束时,会在控制台打印出 “过渡结束” 的信息。

六、样式操作中的性能问题

6.1 重排与重绘

在操作 DOM 样式时,会涉及到重排(reflow)和重绘(repaint)。重排是指当 DOM 的变化影响了元素的几何属性(如宽度、高度、位置等)时,浏览器需要重新计算元素的几何属性,重新布局页面。重绘是指当元素的外观发生变化(如颜色、背景等)但不影响其几何属性时,浏览器只需要重新绘制该元素。

重排比重绘的代价更高,因为重排会导致浏览器重新计算布局,这涉及到更多的计算量。例如,以下代码会频繁触发重排:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv">这是一个 div 元素</div>
    <script>
        const myDiv = document.getElementById('myDiv');
        for (let i = 0; i < 100; i++) {
            myDiv.style.left = i + 'px';
        }
    </script>
</body>
</html>

在上述代码的循环中,每次改变 left 属性都会触发重排,这会严重影响性能。

6.2 性能优化策略

为了减少重排和重绘的次数,可以采用以下策略:

  1. 批量操作:将多次样式改变合并为一次。例如,可以先将要改变的样式都设置到一个 class 中,然后一次性添加或移除该 class
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
       .newStyle {
            color: red;
            background-color: yellow;
            font-size: 20px;
        }
    </style>
    <title>Document</title>
</head>
<body>
    <div id="myDiv">这是一个 div 元素</div>
    <button onclick="applyNewStyle()">应用新样式</button>
    <script>
        function applyNewStyle() {
            const myDiv = document.getElementById('myDiv');
            myDiv.classList.add('newStyle');
        }
    </script>
</body>
</html>

在这个例子中,通过添加 newStyle 类,一次性应用多个样式改变,只触发一次重排和重绘。

  1. 先离线操作:可以使用 documentFragment 创建一个临时的 DOM 片段,在片段上进行操作,完成后再将片段添加到文档中。这样可以避免在操作过程中频繁触发重排和重绘。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <ul id="myList"></ul>
    <script>
        const myList = document.getElementById('myList');
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < 10; i++) {
            const li = document.createElement('li');
            li.textContent = `列表项 ${i}`;
            fragment.appendChild(li);
        }
        myList.appendChild(fragment);
    </script>
</body>
</html>

在上述代码中,先在 documentFragment 中创建并添加多个 li 元素,最后将片段添加到 ul 元素中,这样只触发一次重排和重绘。

  1. 读写分离:避免在读取元素的布局相关属性(如 offsetWidthclientHeight 等)后立即改变其样式。因为读取这些属性会强制浏览器进行重排,以获取最新的布局信息。如果在读取后紧接着改变样式,会导致额外的重排。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv" style="width: 100px; height: 100px; background-color: lightblue;"></div>
    <script>
        const myDiv = document.getElementById('myDiv');
        // 先读取
        const width = myDiv.offsetWidth;
        // 然后再改变样式
        myDiv.style.width = (width + 50) + 'px';
    </script>
</body>
</html>

在这个例子中,先读取 offsetWidth,然后再改变 width 样式,避免了不必要的重排。

七、兼容性问题与解决方案

7.1 不同浏览器的样式差异

不同浏览器对 CSS 样式的支持和解析可能存在差异。例如,一些 CSS3 属性在某些旧版本浏览器中可能不被支持。在 JavaScript 操作 DOM 样式时,也会受到这些差异的影响。比如,在获取元素的计算样式时,IE 浏览器使用 element.currentStyle 而不是 window.getComputedStyle

7.2 兼容性解决方案

  1. 使用 CSS 前缀:对于一些 CSS3 属性,不同浏览器可能需要添加特定的前缀才能正确支持。例如,-webkit- 用于 WebKit 内核浏览器(如 Chrome、Safari),-moz- 用于 Firefox,-ms- 用于 IE 等。在 JavaScript 中设置样式时也需要考虑这些前缀。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <script>
        const myDiv = document.getElementById('myDiv');
        function setTransform() {
            if ('transform' in document.documentElement.style) {
                myDiv.style.transform = 'rotate(45deg)';
            } else if ('webkitTransform' in document.documentElement.style) {
                myDiv.style.webkitTransform = 'rotate(45deg)';
            } else if ('mozTransform' in document.documentElement.style) {
                myDiv.style.mozTransform = 'rotate(45deg)';
            }
        }
        setTransform();
    </script>
</body>
</html>

在上述代码中,通过检查浏览器是否支持无前缀的 transform 属性,若不支持则检查带有 -webkit--moz- 前缀的属性,以确保在不同浏览器中都能正确设置 transform 样式。

  1. 使用 Polyfill:对于一些浏览器不支持的新特性,可以使用 Polyfill 来提供兼容性。例如,对于 classList 属性,在一些旧版本浏览器中不支持,可以使用以下 Polyfill:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv" class="oldClass"></div>
    <script>
        if (!('classList' in document.documentElement)) {
            Object.defineProperty(Element.prototype, 'classList', {
                get: function () {
                    const self = this;
                    function update(fn) {
                        return function (value) {
                            const classes = self.className.split(/\s+/);
                            const index = classes.indexOf(value);
                            fn(classes, index, value);
                            self.className = classes.join(' ');
                        };
                    }
                    return {
                        add: update(function (classes, index, value) {
                            if (index === -1) classes.push(value);
                        }),
                        remove: update(function (classes, index) {
                            if (index > -1) classes.splice(index, 1);
                        }),
                        toggle: update(function (classes, index, value) {
                            if (index === -1) classes.push(value);
                            else classes.splice(index, 1);
                        }),
                        contains: function (value) {
                            return this.className.split(/\s+/).indexOf(value) > -1;
                        }
                    };
                }
            });
        }
        const myDiv = document.getElementById('myDiv');
        myDiv.classList.add('newClass');
    </script>
</body>
</html>

这段代码为不支持 classList 的浏览器添加了一个模拟的 classList 实现,使得在这些浏览器中也能使用 classList 的相关方法来操作 class

  1. 特性检测:在使用新的 DOM 样式操作方法或属性之前,先进行特性检测,判断浏览器是否支持该特性。例如,在使用 requestAnimationFrame 来实现动画时:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <script>
        const myDiv = document.getElementById('myDiv');
        function animate() {
            if (window.requestAnimationFrame) {
                requestAnimationFrame(animate);
                myDiv.style.left = parseInt(myDiv.style.left || 0) + 1 + 'px';
            }
        }
        animate();
    </script>
</body>
</html>

在上述代码中,先检查 window.requestAnimationFrame 是否存在,若存在则使用它来实现动画,这样可以确保在不支持 requestAnimationFrame 的浏览器中不会出现错误。

通过以上对 JavaScript 操作 DOM 样式细节的探讨,包括直接操作 style 属性、通过 class 操作样式、操作伪类样式、实现动画与过渡效果、处理性能问题以及解决兼容性问题等方面,开发者可以更加熟练和高效地利用 JavaScript 来动态改变网页元素的外观,为用户打造出更加出色的 Web 体验。在实际开发中,需要根据项目的需求和目标浏览器的情况,灵活运用这些技术和方法。