CSS Anchor Positioning 锚点定位:用纯 CSS 搞定浮层定位,告别 JS 计算

更新日期: 2026-05-03 阅读: 12 标签: 定位

如果你写过 tooltip、下拉菜单或者弹窗,应该遇到过这种情况:用 JS 去拿元素位置,监听滚动和窗口大小变化,还要处理边界溢出,代码写起来又长又难维护。

现在 CSS 原生提供了一个叫 Anchor Positioning(锚点定位)的特性,Chrome 125 以上版本已经支持。你只需要写 CSS,就能把任意浮层固定到指定的元素上,还能自动翻转避开边界。


一、基础用法:把浮层钉到锚点上

核心思路分三步:

  1. 给目标元素(锚点)起个名字:anchor-name

  2. 给浮层绑定这个名字:position-anchor

  3. 用 anchor() 函数指定位置

/* 锚点元素 */
.btn {
  anchor-name: --my-btn;
}

/* 浮层 */
.tooltip {
  position: absolute;
  position-anchor: --my-btn;
  bottom: anchor(top);    /* 浮层底部贴着锚点顶部,出现在上方 */
  left: anchor(left);     /* 左对齐 */
}
<button class="btn">悬停我</button>
<div class="tooltip">这是提示文字</div>

只要这几行 CSS,浮层就会自动跟着按钮走,完全不需要 JS。


二、anchor() 函数的八个方向

anchor() 函数支持多种位置关键字,常用的有这些:

.popup {
  position: absolute;
  position-anchor: --target;

  /* 出现在锚点下方 */
  top: anchor(bottom);

  /* 出现在锚点上方 */
  bottom: anchor(top);

  /* 出现在锚点右侧 */
  left: anchor(right);

  /* 出现在锚点左侧 */
  right: anchor(left);

  /* 左对齐 */
  left: anchor(left);

  /* 右对齐 */
  right: anchor(right);

  /* 居中(配合 transform) */
  left: anchor(center);
  transform: translateX(-50%);
}


三、自动翻转:position-try-fallbacks

这是锚点定位最实用的功能。当浮层靠近浏览器边界时,可以自动改变方向,防止被裁切。

下方空间不够时自动翻转到上方:

.dropdown {
  position: absolute;
  position-anchor: --menu-btn;

  /* 默认出现在下方 */
  top: anchor(bottom);
  left: anchor(left);

  /* 空间不足时,尝试翻转到上方 */
  position-try-fallbacks: --flip-up;
}

/* 定义翻转规则 */
@position-try --flip-up {
  top: auto;
  bottom: anchor(top);
}

有了这个,不用再写 JS 去检测边界然后手动切换方向。


四、anchor-size():让浮层宽度跟着锚点走

想让下拉菜单和按钮一样宽,以前要拿 JS 读宽度再设置。现在一行 CSS 就够:

.dropdown-menu {
  position: absolute;
  position-anchor: --trigger;

  top: anchor(bottom);
  left: anchor(left);

  /* 宽度等于锚点宽度 */
  width: anchor-size(width);

  /* 最小宽度等于锚点宽度,内容多了可以更宽 */
  min-width: anchor-size(width);
}

anchor-size() 支持 width、height、inline、block 等值。


五、配合 Popover API:纯 CSS 弹出层

HTML 自带的 Popover API 可以跟锚点定位配合,完全不用 JS:

<button popovertarget="my-popup" class="trigger">点我</button>

<div id="my-popup" popover class="popup">
  这是一个弹出框,纯 CSS 实现
</div>
.trigger {
  anchor-name: --trigger;
}

.popup {
  position: absolute;
  position-anchor: --trigger;

  top: anchor(bottom);
  left: anchor(center);
  transform: translateX(-50%);
  margin-top: 8px;

  /* 去掉 popover 默认样式 */
  border: none;
  border-radius: 8px;
  padding: 12px 16px;
  background: #333;
  color: #fff;
}

点击按钮,弹出框会自动出现在按钮下方,一行 JS 都不用写。


六、实战:右键菜单

<div class="item" contextmenu="ctx-menu">右键点我</div>
<menu id="ctx-menu" popover type="context" class="ctx-menu">
  <li>复制</li>
  <li>粘贴</li>
  <li>删除</li>
</menu>
.item {
  anchor-name: --item;
}

.ctx-menu {
  position: absolute;
  position-anchor: --item;

  top: anchor(top);
  left: anchor(right);

  /* 使用内置翻转关键词,不需要额外定义 */
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}

flip-block 负责垂直翻转,flip-inline 负责水平翻转,这些是内置的,不用自己写 @position-try。


七、兼容性

特性ChromeFirefoxSafari
anchor-name / position-anchor125+ ✅实验阶段实验阶段
position-try-fallbacks125+ ✅实验阶段
anchor-size()125+ ✅实验阶段

目前 Chrome 125 以上版本已全面支持,Firefox 和 Safari 正在实验阶段。生产环境中可以用 @supports 做降级处理。


八、完整示例代码

下面是一个完整的 HTML 文件,包含了上面讲的 6 个例子,可以直接复制运行:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS Anchor Positioning 锚点定位示例</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
        }
        .section {
            margin: 40px 0;
            padding: 20px;
            border: 1px solid #eee;
            border-radius: 8px;
        }
        h2 {
            color: #2c3e50;
            margin-bottom: 20px;
        }

        /* 1. 基础用法 */
        .btn {
            anchor-name: --my-btn;
            padding: 10px 20px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .tooltip {
            position: absolute;
            position-anchor: --my-btn;
            bottom: anchor(top);
            left: anchor(left);
            background: #2c3e50;
            color: white;
            padding: 8px 12px;
            border-radius: 4px;
            opacity: 0;
            transition: opacity 0.3s;
        }
        .btn:hover + .tooltip {
            opacity: 1;
        }

        /* 2. anchor() 函数示例 */
        .target {
            anchor-name: --target;
            width: 100px;
            height: 100px;
            background: #e74c3c;
            margin: 40px 0;
        }
        .popup-bottom {
            position: absolute;
            position-anchor: --target;
            top: anchor(bottom);
            left: anchor(left);
            background: #27ae60;
            color: white;
            padding: 10px;
            border-radius: 4px;
        }
        .popup-right {
            position: absolute;
            position-anchor: --target;
            left: anchor(right);
            top: anchor(top);
            background: #f39c12;
            color: white;
            padding: 10px;
            border-radius: 4px;
        }
        .popup-center {
            position: absolute;
            position-anchor: --target;
            left: anchor(center);
            top: anchor(center);
            transform: translate(-50%, -50%);
            background: #9b59b6;
            color: white;
            padding: 10px;
            border-radius: 4px;
        }

        /* 3. 自动翻转 */
        .menu-btn {
            anchor-name: --menu-btn;
            padding: 10px 20px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .dropdown {
            position: absolute;
            position-anchor: --menu-btn;
            top: anchor(bottom);
            left: anchor(left);
            position-try-fallbacks: --flip-up;
            background: white;
            border: 1px solid #eee;
            border-radius: 4px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            padding: 10px 0;
            min-width: 150px;
            opacity: 0;
            transition: opacity 0.3s;
        }
        .menu-btn:hover + .dropdown {
            opacity: 1;
        }
        .dropdown li {
            list-style: none;
            padding: 8px 16px;
            cursor: pointer;
        }
        .dropdown li:hover {
            background: #f5f5f5;
        }
        @position-try --flip-up {
            top: auto;
            bottom: anchor(top);
        }

        /* 4. anchor-size() */
        .trigger {
            anchor-name: --trigger;
            padding: 12px 30px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .dropdown-menu {
            position: absolute;
            position-anchor: --trigger;
            top: anchor(bottom);
            left: anchor(left);
            width: anchor-size(width);
            background: white;
            border: 1px solid #eee;
            border-radius: 4px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            padding: 10px 0;
            opacity: 0;
            transition: opacity 0.3s;
        }
        .trigger:hover + .dropdown-menu {
            opacity: 1;
        }

        /* 5. 结合 Popover API */
        .popover-trigger {
            anchor-name: --popover-trigger;
            padding: 10px 20px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        #my-popup {
            position: absolute;
            position-anchor: --popover-trigger;
            top: anchor(bottom);
            left: anchor(center);
            transform: translateX(-50%);
            margin-top: 8px;
            border: none;
            border-radius: 8px;
            padding: 12px 16px;
            background: #333;
            color: #fff;
        }

        /* 6. 右键菜单 */
        .item {
            anchor-name: --item;
            width: 200px;
            height: 100px;
            background: #e74c3c;
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 40px 0;
            cursor: pointer;
        }
        #ctx-menu {
            position: absolute;
            position-anchor: --item;
            top: anchor(top);
            left: anchor(right);
            position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
            background: white;
            border: 1px solid #eee;
            border-radius: 4px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            padding: 10px 0;
            min-width: 150px;
        }
        #ctx-menu li {
            list-style: none;
            padding: 8px 16px;
            cursor: pointer;
        }
        #ctx-menu li:hover {
            background: #f5f5f5;
        }
    </style>
</head>
<body>
    <h1>CSS Anchor Positioning 锚点定位示例</h1>

    <div class="section">
        <h2>1. 基础用法</h2>
        <button class="btn">悬停我</button>
        <div class="tooltip">这是提示文字</div>
    </div>

    <div class="section">
        <h2>2. anchor() 八个方向</h2>
        <div class="target"></div>
        <div class="popup-bottom">出现在下方</div>
        <div class="popup-right">出现在右侧</div>
        <div class="popup-center">居中</div>
    </div>

    <div class="section">
        <h2>3. 自动翻转</h2>
        <button class="menu-btn">悬停我(靠近边界自动翻转)</button>
        <ul class="dropdown">
            <li>选项 1</li>
            <li>选项 2</li>
            <li>选项 3</li>
        </ul>
    </div>

    <div class="section">
        <h2>4. 宽度跟随锚点</h2>
        <button class="trigger">悬停我(菜单宽度和按钮一样)</button>
        <ul class="dropdown-menu">
            <li>选项 A</li>
            <li>选项 B</li>
            <li>选项 C</li>
        </ul>
    </div>

    <div class="section">
        <h2>5. 纯 CSS 弹出层</h2>
        <button popovertarget="my-popup" class="popover-trigger">点我</button>
        <div id="my-popup" popover>
            这是一个弹出框,纯 CSS 实现
        </div>
    </div>

    <div class="section">
        <h2>6. 右键菜单</h2>
        <div class="item" contextmenu="ctx-menu">右键点我</div>
        <menu id="ctx-menu" popover type="context">
            <li>复制</li>
            <li>粘贴</li>
            <li>删除</li>
        </menu>
    </div>
</body>
</html>

CSS 锚点定位是近几年很值得关注的新特性。它把之前需要大量 JS 才能搞定的定位逻辑,还给了 CSS。虽然 Firefox 和 Safari 还在实验阶段,但 Chrome 已经稳定支持了,提前学起来,后面全面普及了就能直接用。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13744

相关推荐

html5获取地理位置和定位

H5地理位置定位功能:首先判断用户浏览器是否支持该功能,目前大多数现代浏览器均支持,获取位置信息需用户授权同意;showPosition()获取用户经度纬度;执行函数getLocation(),如果调用成功即可显示经度纬度,简单吧

浏览器获取手机经纬度位置

网址必须为域名,不能用ip直接访问,否则手机浏览器直接拒绝改请求。iphone的浏览器(包括微信扫码进入网站) ,亲测调用 http 的网站是不能获取到经纬度的,是苹果手机安全的考虑,如果使用https网站的话

原生JS实现页面内定位

需求:点击跳转到页面指定位置.利用a标签的锚点跳转。1、利用a标签的锚点跳转,2、用js的scrollIntoView方法,3、获取id为test的元素距离父元素顶部的位置,即offsetTop, 改变父元素的scrollTop

css3中position:sticky

最近在写一个小程序,项目中遇到一个需求:页面滚动到tab切换菜单时,菜单fixed到页面顶部;使用小程序的onPageScroll事件,滚动到指定位置添加fixed样式;

css和xpath定位补充

XPath是一种在XML文档中定位元素的语言。因为HTML可以看作XML的一种实现, 所以Selenium用户可以使用这种强大的语言在Web应用中定位元素。

前端粘性定位事件

浏览器的世界里本没有粘性定位事件。然而,网页开发中,元素若使用了粘性定位 position:sticky ,常常还需要一个粘性定位事件,比如,外卖菜单。

Ref实现导航滚动定位

在开发项目中时常有点击跳转滚动到锚点的需求,最简单的锚点定位就是给一个a标签,a标签的href = ‘#锚点’,然后给需要跳转的锚点一个id = ‘锚点’。参考最简单的锚点跳转实现方式,在React中使用useRef来实现跳转锚点的功能。

CSS定位

改变元素在页面中的位置:页面中元素的默认定位方式,默认文档流,从上往下,从左往右;当元素设置浮动后,该元素脱离默认文档流,后面的元素会上前补位。当前元素会在当前行,向左或者向右排列

苹果手机对网页上样式为position:fixed的弹窗支持不好的解决办法

在Web页面上,如果想模拟对话框效果,一般会给div元素添加position:fixed的样式来实现,然后给背景添加一个半透明的遮罩。你可以使用position:absolute样式来定义对话框,但是position:relative的元素出现的位置决定了对话框在页面中的绝对位置。

css绝对定位和相对定位、固定定位

绝对定位指的是通过规定HTML元素在水平和垂直方向上的位置来固定元素,基于绝对定位的元素不会占据空间。相对定位与绝对定位的区别在于它的参照点不是左上角的原点,而是该元素本身原先的起点位置。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!