如何解决如果父组件位于滚动容器内,则解决 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,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',{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',{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
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。