diff --git a/packages/components/popover/README.en-US.md b/packages/components/popover/README.en-US.md
index 6fbbc2ca3..6dd7c6592 100644
--- a/packages/components/popover/README.en-US.md
+++ b/packages/components/popover/README.en-US.md
@@ -2,7 +2,6 @@
## API
-
### Popover Props
name | type | default | description | required
@@ -11,6 +10,7 @@ style | Object | - | CSS(Cascading Style Sheets) | N
custom-style | Object | - | CSS(Cascading Style Sheets),used to set style on virtual component | N
close-on-click-outside | Boolean | true | \- | N
content | String | - | \- | N
+fixed | Boolean | false | \- | N
placement | String | top | options: top/left/right/bottom/top-left/top-right/bottom-left/bottom-right/left-top/left-bottom/right-top/right-bottom | N
show-arrow | Boolean | true | \- | N
theme | String | dark | options: dark/light/brand/success/warning/error | N
@@ -41,4 +41,4 @@ t-class-content | \-
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
---td-popover-padding | 24rpx | -
+--td-popover-padding | 24rpx | -
diff --git a/packages/components/popover/README.md b/packages/components/popover/README.md
index 37aaae18e..5e554b4b8 100644
--- a/packages/components/popover/README.md
+++ b/packages/components/popover/README.md
@@ -25,12 +25,29 @@ isComponent: true
### 组件类型
-带箭头的弹出气泡
+#### 带箭头的弹出气泡
{{ base }}
-## API
+### 组件样式
+
+#### 气泡主题
+{{ theme }}
+
+#### 气泡位置
+{{ placement }}
+
+## FAQ
+如果使用场景为 `fixed`,除了需要显示指定 `fixed` 属性为 `true`,还需在触发元素的顶层添加`t-popover-wrapper--fixed` 类,用于定位触发元素。
+
+```html
+
+
+
+```
+
+## API
### Popover Props
@@ -40,6 +57,7 @@ style | Object | - | 样式 | N
custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N
close-on-click-outside | Boolean | true | 是否在点击外部元素后关闭菜单 | N
content | String | - | 确认框内容 | N
+fixed | Boolean | false | 如果 popover 是在一个 `position:fixed` 的区域,需要显式指定属性 fixed 为 true | N
placement | String | top | 浮层出现位置。可选项:top/left/right/bottom/top-left/top-right/bottom-left/bottom-right/left-top/left-bottom/right-top/right-bottom | N
show-arrow | Boolean | true | 是否显示浮层箭头 | N
theme | String | dark | 弹出气泡主题。可选项:dark/light/brand/success/warning/error | N
@@ -55,8 +73,8 @@ visible-change | `(visible: boolean)` | 确认框显示或隐藏时触发
名称 | 描述
-- | --
-\- | 自定义 `` 显示内容
-content \| 自定义 `content` 显示内容
+\- | 默认插槽,作用同 `content` 插槽
+content | 自定义 `content` 显示内容
### Popover External Classes
@@ -70,4 +88,4 @@ t-class-content | 内容样式类
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
---td-popover-padding | 24rpx | -
\ No newline at end of file
+--td-popover-padding | 24rpx | -
diff --git a/packages/components/popover/popover.less b/packages/components/popover/popover.less
index 3a8fb3370..40e474764 100644
--- a/packages/components/popover/popover.less
+++ b/packages/components/popover/popover.less
@@ -32,6 +32,10 @@
overflow: visible;
transition: 0.2s ease-in-out all;
+ &--fixed {
+ position: fixed;
+ }
+
&__content {
position: relative;
padding: @popover-padding;
diff --git a/packages/components/popover/popover.ts b/packages/components/popover/popover.ts
index 3d5f38f0b..951ae2cf5 100644
--- a/packages/components/popover/popover.ts
+++ b/packages/components/popover/popover.ts
@@ -161,50 +161,59 @@ export default class Popover extends SuperComponent {
return start + triggerSize / 2 - contentSize / 2;
},
- calcPlacement(placement: string, triggerRect: any, contentRect: any) {
- const { isHorizontal, isVertical } = this.getToward(placement);
- // 获取内容大小
- const { width: contentWidth, height: contentHeight } = contentRect;
- // 获取所在位置
- const { left: triggerLeft, top: triggerTop, right: triggerRight, bottom: triggerBottom } = triggerRect;
- // 是否能正常放置
- let canPlace = true;
- const { windowWidth, windowHeight } = getWindowInfo();
- let finalPlacement = placement;
-
- if (isHorizontal) {
- if (placement.startsWith('top')) {
- canPlace = triggerTop - contentHeight >= 0;
- } else if (placement.startsWith('bottom')) {
- canPlace = triggerBottom + contentHeight <= windowHeight;
- }
- } else if (isVertical) {
- if (placement.startsWith('left')) {
- canPlace = triggerLeft - contentWidth >= 0;
- } else if (placement.startsWith('right')) {
- canPlace = triggerRight + contentWidth <= windowWidth;
- }
- }
-
- if (!canPlace) {
- // 反向
- if (isHorizontal) {
- finalPlacement = placement.startsWith('top')
- ? placement.replace('top', 'bottom')
- : placement.replace('bottom', 'top');
- } else if (isVertical) {
- finalPlacement = placement.startsWith('left')
- ? placement.replace('left', 'right')
- : placement.replace('right', 'left');
- }
- }
-
- const basePos = this.calcContentPosition(finalPlacement, triggerRect, contentRect);
-
- return {
- placement: finalPlacement,
- ...basePos,
- };
+ calcPlacement(isFixed: boolean, placement: string, triggerRect: any, contentRect: any) {
+ return new Promise<{ placement: string; top: number; left: number }>((resolve) => {
+ // 选取当前组件节点所在的组件实例,以支持 fixed 定位的元素计算位置
+ const owner = this.selectOwnerComponent().createSelectorQuery();
+ owner.select(`.${name}-wrapper--fixed`).boundingClientRect();
+ owner.exec((b) => {
+ const [triggerChildRect] = b;
+ if (triggerChildRect && isFixed) {
+ triggerRect = triggerChildRect;
+ }
+
+ const { isHorizontal, isVertical } = this.getToward(placement);
+ // 获取内容大小
+ const { width: contentWidth, height: contentHeight } = contentRect;
+ // 获取所在位置
+ const { left: triggerLeft, top: triggerTop, right: triggerRight, bottom: triggerBottom } = triggerRect;
+ // 是否能正常放置
+ let canPlace = true;
+ const { windowWidth, windowHeight } = getWindowInfo();
+ let finalPlacement = placement;
+
+ if (isHorizontal) {
+ if (placement.startsWith('top')) {
+ canPlace = triggerTop - contentHeight >= 0;
+ } else if (placement.startsWith('bottom')) {
+ canPlace = triggerBottom + contentHeight <= windowHeight;
+ }
+ } else if (isVertical) {
+ if (placement.startsWith('left')) {
+ canPlace = triggerLeft - contentWidth >= 0;
+ } else if (placement.startsWith('right')) {
+ canPlace = triggerRight + contentWidth <= windowWidth;
+ }
+ }
+
+ if (!canPlace) {
+ // 反向
+ if (isHorizontal) {
+ finalPlacement = placement.startsWith('top')
+ ? placement.replace('top', 'bottom')
+ : placement.replace('bottom', 'top');
+ } else if (isVertical) {
+ finalPlacement = placement.startsWith('left')
+ ? placement.replace('left', 'right')
+ : placement.replace('right', 'left');
+ }
+ }
+
+ const basePos = this.calcContentPosition(finalPlacement, triggerRect, contentRect);
+
+ resolve({ placement: finalPlacement, ...basePos });
+ });
+ });
},
async computePosition() {
@@ -217,18 +226,27 @@ export default class Popover extends SuperComponent {
query.select(`#${name}-content`).boundingClientRect();
query.selectViewport().scrollOffset();
- query.exec((res) => {
+ query.exec(async (res) => {
const [triggerRect, contentRect, viewportOffset] = res;
if (!triggerRect || !contentRect) return;
+ // 如果 fixed 定位,不需要加上滚动偏移量
+ const isFixed = this.properties.fixed;
// 最终放置位置
- const { placement: finalPlacement, ...basePos } = this.calcPlacement(_placement, triggerRect, contentRect);
- // TODO 优化:滚动时可能导致箭头闪烁
+ const { placement: finalPlacement, ...basePos } = await this.calcPlacement(
+ isFixed,
+ _placement,
+ triggerRect,
+ contentRect,
+ );
+
+ // TODO 优化:滚动时切换placement可能导致箭头闪烁
this.setData({ _placement: finalPlacement });
- const { scrollTop = 0, scrollLeft = 0 } = viewportOffset;
- const top = basePos.top + scrollTop;
- const left = basePos.left + scrollLeft;
+ const { scrollTop = 0, scrollLeft = 0 } = viewportOffset || {};
+
+ const top = isFixed ? basePos.top : basePos.top + scrollTop;
+ const left = isFixed ? basePos.left : basePos.left + scrollLeft;
const style = `top:${Math.max(top, 0)}px;left:${Math.max(left, 0)}px;`;
const arrowStyle = this.calcArrowStyle(_placement, triggerRect, contentRect);
diff --git a/packages/components/popover/popover.wxml b/packages/components/popover/popover.wxml
index 9651c2c13..cac5a49c7 100644
--- a/packages/components/popover/popover.wxml
+++ b/packages/components/popover/popover.wxml
@@ -16,7 +16,7 @@
wx:if="{{realVisible}}"
id="{{classPrefix}}-content"
style="{{style}} {{contentStyle}} {{customStyle}}"
- class="{{class}} {{classPrefix}} {{transitionClass}} {{prefix}}-class"
+ class="{{class}} {{classPrefix}} {{transitionClass}} {{prefix}}-class {{fixed ? classPrefix + '--fixed' : ''}}"
data-placement="{{_placement}}"
>