【前端面试知识点】:root 和 html 选择器在CSS中有什么区别?CSS变量的作用域是怎样的?

0 阅读5分钟

1. :root 和 html 选择器有什么区别?

简单说:  在HTML网页里,它们俩指的都是同一个东西——<html>标签。但就像“总统”和“国家主席”的区别一样,称呼不同,身份和权重略有差异。

  • :root:这是CSS里的一个“伪类”,专门用来选中整个文档的根元素。在HTML里根就是<html>,所以它俩选中的是同一个标签。
  • html:这就是直接通过标签名来选择<html>元素。

区别主要在两点:

  • 权重不一样(特异性)
    :root的权重比html高。你可以把CSS规则想象成衣服::root是“高级定制”,html是“普通成衣”。如果同时给<html>设置样式,两条规则都写了同一个属性,:root的样式会“覆盖”html的样式(除非html后面加了!important)。比如:

    css

    :root { color: red; }
    html { color: blue; }
    

    最终文字颜色是红色,因为:root更“强势”。

  • 适用范围不同
    :root是CSS标准里的“根元素”代名词,不光可以用在HTML,还可以用在SVG、XML等文档里。而html只针对HTML文档。所以写全局样式时,用:root更“通用”,也更符合“根”的语义。

通常用法:  我们一般用:root来定义全局CSS变量,因为它权重高,且一看就知道是根级别。


2. CSS变量的作用域是怎样的?

一句话:CSS变量的作用域跟普通CSS属性一样——在哪个选择器里定义,就作用在那个选择器选中的元素及其后代元素上。

你可以把它想象成 “家族遗传”

  • 全局变量(定义在:root上) :就像写在族谱最顶上的家规,所有子孙后代(所有元素)都能继承和使用。
  • 局部变量(定义在某个具体元素上) :就像某个小家庭自己定的家规,只在这个家庭(该元素)及其后代里有效,不影响其他分支。
  • 覆盖(重写) :如果后代自己也定义了同名变量,就会“覆盖”祖先的变量,在自己和更后代里用新值,就像“不听祖宗的话,自己另搞一套”。

举个例子:

image.png

html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS变量作用域 · 经典示例</title>
    <style>
        /* 基础样式,仅为了让例子更清晰 */
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: system-ui, 'Segoe UI', Roboto, sans-serif;
            background: #f8fafc;
            padding: 2rem;
            line-height: 1.5;
            color: #0f172a;
        }

        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            border-radius: 24px;
            box-shadow: 0 20px 35px -8px rgba(0,0,0,0.1);
            padding: 2rem;
        }

        h1 {
            font-size: 1.9rem;
            font-weight: 600;
            margin-bottom: 0.25rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }
        h1 small {
            font-size: 0.9rem;
            font-weight: 400;
            background: #e2e8f0;
            color: #334155;
            padding: 0.2rem 0.8rem;
            border-radius: 30px;
        }

        .desc {
            background: #f1f5f9;
            padding: 1.2rem;
            border-radius: 18px;
            margin: 1.5rem 0;
            border-left: 6px solid #3b82f6;
            font-size: 1rem;
        }

        code {
            background: #e2e8f0;
            padding: 0.2rem 0.5rem;
            border-radius: 8px;
            font-family: 'JetBrains Mono', 'Fira Code', monospace;
            font-size: 0.9rem;
            color: #0f172a;
        }

        pre {
            background: #0f172a;
            color: #e2e8f0;
            padding: 1rem 1.5rem;
            border-radius: 16px;
            overflow-x: auto;
            font-size: 0.95rem;
            margin: 1.5rem 0;
            border: 1px solid #334155;
        }

        /* 核心变量定义 */
        :root {
            --main-color: red;      /* 全局红色 */
        }

        .sidebar {
            --main-color: blue;     /* 只在 sidebar 内部有效 */
            background: #f0f9ff;
            border-radius: 20px;
            padding: 1.2rem;
            border: 2px solid #b7e0ff;
            margin: 1rem 0;
        }

        /* 盒子样式 */
        .box {
            color: var(--main-color);
            border: 2px solid var(--main-color);
            background: white;
            padding: 1rem 1.5rem;
            border-radius: 40px;
            font-weight: 600;
            margin: 0.8rem 0;
            transition: all 0.1s;
            box-shadow: 0 4px 6px -2px rgba(0,0,0,0.05);
        }

        .box span {
            display: inline-block;
            background: #f1f5f9;
            padding: 0.2rem 1rem;
            border-radius: 30px;
            font-size: 0.85rem;
            margin-left: 0.8rem;
            color: #1e293b;
        }

        .highlight {
            background: #fffbeb;
            border: 2px dashed #f59e0b;
            border-radius: 16px;
            padding: 1rem;
        }

        hr {
            margin: 2rem 0;
            border: none;
            border-top: 3px dotted #cbd5e1;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>
            🎨 CSS变量作用域 · 经典例
            <small>全局 vs 局部</small>
        </h1>

        <div class="desc"><code>:root { --main-color: red; }</code>  全局红色<br><code>.sidebar { --main-color: blue; }</code>  只在侧边栏内部覆盖为蓝色<br><code>.box { color: var(--main-color); }</code>  根据所在位置不同呈现不同颜色
        </div>

        <pre><code>:root {
  --main-color: red;   /* 全局变量 */
}

.sidebar {
  --main-color: blue;  /* 只在 sidebar 及其内部有效 */
}

.box {
  color: var(--main-color); /* 如果 .box 在 sidebar 内就是蓝色,否则红色 */
}</code></pre>

        <!-- 全局区域的两个盒子 -->
        <div class="box">
            📦 我在全局作用域(没有 .sidebar 包裹)<span>颜色: 红色 (全局变量)</span>
        </div>
        <div class="box">
            📦 我也是全局盒子 <span>红色</span>
        </div>

        <!-- 侧边栏区域,内部变量被覆盖为蓝色 -->
        <div class="sidebar">
            <p style="margin-top: 0; margin-bottom: 0.5rem; font-weight: 500; color: #0369a1;">🔷 sidebar 区域 (--main-color: blue)</p>
            <div class="box">
                📦 我在 .sidebar 内部 <span>颜色: 蓝色 (局部变量覆盖)</span>
            </div>
            <div class="box">
                📦 我也是 sidebar 内的盒子 <span>蓝色</span>
            </div>
        </div>

        <!-- 再放一个全局盒子,确认红色未受影响 -->
        <div class="box">
            📦 我又回到了全局 <span>红色 (不受 sidebar 影响)</span>
        </div>

        <hr>

        <!-- 额外的说明:多层嵌套 -->
        <div style="background: #f9fafb; border-radius: 20px; padding: 1.2rem;">
            <p style="font-size: 1.1rem; font-weight: 600; margin-bottom: 1rem;">🧩 作用域规则验证:</p>
            <div class="box" style="border-color: gray; color: var(--main-color);">
                当前这个盒子在根级别 → <span id="demoColor"></span>
            </div>
            <div class="sidebar" style="margin-top: 1rem;">
                <div class="box" style="border-color: gray;">
                    而在 sidebar 内部 → <span id="demoColor2"></span>
                </div>
            </div>
        </div>

        <p style="margin-top: 2rem; font-size: 0.95rem; color: #475569; background: #ecfdf5; padding: 0.8rem; border-radius: 40px; text-align: center;">
            ✅ 结论:CSS变量作用域由定义的选择器决定,后代继承最近祖先的定义。
        </p>
    </div>

    <!-- 简单脚本来显示计算后的颜色值(辅助说明) -->
    <script>
        (function() {
            const boxes = document.querySelectorAll('.box');
            const colorSpans = document.querySelectorAll('#demoColor, #demoColor2');
            // 动态提取颜色文本
            function rgbToHex(rgb) {
                if (!rgb) return '';
                const match = rgb.match(/^rgb((\d+),\s*(\d+),\s*(\d+))$/);
                if (!match) return rgb;
                const r = parseInt(match[1]).toString(16).padStart(2,'0');
                const g = parseInt(match[2]).toString(16).padStart(2,'0');
                const b = parseInt(match[3]).toString(16).padStart(2,'0');
                return `#${r}${g}${b}`.toUpperCase();
            }

            const rootBox = document.querySelector('.box:first-child');
            const sidebarBox = document.querySelector('.sidebar .box');
            if (rootBox && document.getElementById('demoColor')) {
                const color = window.getComputedStyle(rootBox).color;
                document.getElementById('demoColor').innerText = rgbToHex(color) + ' (红)';
            }
            if (sidebarBox && document.getElementById('demoColor2')) {
                const color = window.getComputedStyle(sidebarBox).color;
                document.getElementById('demoColor2').innerText = rgbToHex(color) + ' (蓝)';
            }
        })();
    </script>
</body>
</html>

作用域规则总结:

  • 变量从当前元素往上找,找到哪个定义就用哪个(最近祖先优先)。
  • 变量不跨兄弟元素,也不跨没有继承关系的元素。
  • 定义在媒体查询或伪类里的变量,只在对应条件下生效。

所以,CSS变量天然支持“作用域”,让我们可以像写JavaScript一样管理样式主题,非常方便。