scroll - 如果父组件位于滚动容器内,则解决 Vuetify “v-menu”显示为固定
问题描述
Vuetify中的组件存在一个长期存在的问题:v-menu
- 默认情况下,弹出窗口与激活器物理“分离”并创建为 的子级
v-app
,从而避免在某些父 DOM 节点具有overflow: hidden
样式时被剪裁;但是,这会导致当激活器位于滚动容器内时弹出窗口表现为“位置:固定”的问题 - 也就是说,它不会随激活器滚动并且看起来在视觉上是断开的,只是“悬挂”在页面上。 - Vuetify 维护人员承认这一事实并建议使用“attach”道具 - 但是,在使用“attach”时,10 次中有 9 次计算错误的弹出窗口的位置。
经过 2 小时的调试,我终于放弃了使用“attach”属性,决定简单地跟踪激活器所在的父容器的滚动位置,并在计算弹出窗口的位置时将其考虑在内。我正在分享我对以下问题的解决方案,并希望它能够包含在主流的 Vuetify 中。
解决方案
这是解决上述问题的补丁文件以及其他一些问题。在您的项目中创建一个名为的文件夹patches
并将补丁文件保存为patches/vuetify#2.5.5.patch
. scripts
然后在您的组中添加一个新脚本package.json
"scripts":
{
....
"prepare": "custompatch"
}
然后运行npm -i -D custompatch
(为您的 CI/CD 安装修补程序)和npx custompatch
(在您的开发环境中实际修补 Vuetify)。
Index: \vuetify\lib\components\VMenu\VMenu.js
===================================================================
--- \vuetify\lib\components\VMenu\VMenu.js
+++ \vuetify\lib\components\VMenu\VMenu.js
@@ -124,10 +124,10 @@
return {
maxHeight: this.calculatedMaxHeight,
minWidth: this.calculatedMinWidth,
maxWidth: this.calculatedMaxWidth,
- top: this.calculatedTop,
- left: this.calculatedLeft,
+ top: `calc(${this.calculatedTop} - ${this.scrollY}px + ${this.originalScrollY}px)`, // we deduct the difference to account
+ left: `calc(${this.calculatedLeft} - ${this.scrollX}px + ${this.originalScrollX}px)`, // for the change in scroll
transformOrigin: this.origin,
zIndex: this.zIndex || this.activeZIndex
};
}
Index: \vuetify\lib\components\VTextField\VTextField.js
===================================================================
--- \vuetify\lib\components\VTextField\VTextField.js
+++ \vuetify\lib\components\VTextField\VTextField.js
@@ -433,8 +433,9 @@
this.$refs.input.focus();
},
onFocus(e) {
+ this.onResize(); // this fixes the wrong position of the label when the input is focused - label is off by 8-10 pixels to the right, overlapping the field border
if (!this.$refs.input) return;
const root = attachedRoot(this.$el);
if (!root) return;
Index: \vuetify\lib\directives\click-outside\index.js
===================================================================
--- \vuetify\lib\directives\click-outside\index.js
+++ \vuetify\lib\directives\click-outside\index.js
@@ -35,10 +35,11 @@
}
function directive(e, el, binding) {
const handler = typeof binding.value === 'function' ? binding.value : binding.value.handler;
+ const target = e.target;
el._clickOutside.lastMousedownWasOutside && checkEvent(e, el, binding) && setTimeout(() => {
- checkIsActive(e, binding) && handler && handler(e);
+ checkIsActive({...e, target}, binding) && handler && handler({...e, target}); // this fixes a strange behavior - e.target on this line differs from e.target outside of the closure when Vuetify is inside a Shadow DOM
}, 0);
}
function handleShadow(el, callback) {
Index: \vuetify\lib\mixins\detachable\index.js
===================================================================
--- \vuetify\lib\mixins\detachable\index.js
+++ \vuetify\lib\mixins\detachable\index.js
@@ -22,13 +22,23 @@
},
contentClass: {
type: String,
default: ''
+ },
+ scroller:
+ {
+ default: null, // it works the same way as "attach" - but must refer to the scrolling parent of the activator
+ validator: validateAttachTarget
}
},
data: () => ({
activatorNode: null,
- hasDetached: false
+ hasDetached: false,
+ scrollingNode: null,
+ scrollX: 0,
+ scrollY: 0,
+ originalScrollX: 0,
+ originalScrollY: 0
}),
watch: {
attach() {
this.hasDetached = false;
@@ -36,10 +46,38 @@
},
hasContent() {
this.$nextTick(this.initDetach);
+ },
+ isActive(val)
+ {
+ if (val)
+ {
+ if (typeof this.scroller === 'string') {
+ // CSS selector
+ this.scrollingNode = document.querySelector(this.scroller);
+ } else if (this.scroller && typeof this.scroller === 'object') {
+ // DOM Element
+ this.scrollingNode = this.scroller;
+ }
+ if (this.scrollingNode)
+ {
+ this.originalScrollX = this.scrollingNode.scrollLeft; // we only need the difference between scrolling position
+ this.originalScrollY = this.scrollingNode.scrollTop; // before opening the menu and scrolling position while the menu is open
+ this.scrollX = this.originalScrollX; // current scrolling position will be updated by the event handler
+ this.scrollY = this.originalScrollY;
+ this.scrollingNode.addEventListener('scroll', this.setScrollOffset, {passive: true});
+ }
+ }
+ else
+ {
+ if (this.scrollingNode)
+ {
+ this.scrollingNode.removeEventListener('scroll', this.setScrollOffset, {passive: true});
+ }
+ this.scrollingNode = null;
+ }
}
-
},
beforeMount() {
this.$nextTick(() => {
@@ -79,9 +117,12 @@
} catch (e) {
console.log(e);
}
/* eslint-disable-line no-console */
-
+ if (this.scrollingNode)
+ {
+ this.scrollingNode.removeEventListener('scroll', this.setScrollOffset, {passive: true});
+ }
},
methods: {
getScopeIdAttrs() {
@@ -117,9 +158,13 @@
}
target.appendChild(this.$refs.content);
this.hasDetached = true;
+ },
+ setScrollOffset(event)
+ {
+ this.scrollX = event.target.scrollLeft;
+ this.scrollY = event.target.scrollTop;
}
-
}
});
//# sourceMappingURL=index.js.map
\ No newline at end of file
推荐阅读
- reactjs - 为材料表的标题属性添加图标
- node.js - windows build tool 安装卡住,更多信息向下
- node.js - 从 Phoenix / Elixir GET 函数中调用 Typescript 函数
- ruby - Ruby 中的价值案例
- javascript - JavaScript 在其他地方运行,但不在 MediaWiki:common.js 中
- android-jetpack-compose - Jetpack Compose:在 if 语句中“记住”?
- python - Pandas DataFrame 中的数据透视表 - 将行变为列
- flutter - 将多个图像的异步图像上传到firebase存储
- sql - Postgis 触发器:表和物化视图
- python - 用 loc pandas 替换行