我正在使用 Vuetify 文本字段,并且希望在内容大于字段宽度(用户需要滚动)时显示包含内容的工具提示。工具提示应仅出现在悬停时(默认行为)。我从以下开始
(游乐场)
<script setup lang="ts">
import { ref, computed } from "vue";
const currentValue = ref("");
const textFieldComponent = ref<VTextField>();
const isTextFieldCuttingOffContent = computed(() => {
if (!textFieldComponent.value) {
return false;
}
if (!currentValue.value) {
return false;
}
return (
textFieldComponent.value.$el.clientWidth <
textFieldComponent.value.$el.scrollWidth
);
});
</script>
<template>
<v-container style="width: 300px">
<v-tooltip :text="currentValue" :disabled="!isTextFieldCuttingOffContent">
<template v-slot:activator="{ props }">
<div v-bind="props">
<v-text-field
ref="textFieldComponent"
label="label goes here"
v-model="currentValue"
/>
</div>
</template>
</v-tooltip>
</v-container>
</template>
我还尝试使用观察者而不是计算道具(Playground)
问题是
isTextFieldCuttingOffContent
总是返回 false,因为 clientWidth
和 scrollWidth
总是相等。您有什么想法是错误或遗漏的吗?
watch()
变量,使用 nextTick()
scrollWidth
会因 DOM 操作而发生变化。如果您 console.log 输入字段的当前 scrollWidth
,它确实会在您的代码中正确更改。然而,这里的问题是这些 DOM 数据在 Vue 反应系统中不会自动更新。
要检索更新的值,您可以使用
nextTick()
。但是,等待 nextTick()
的值以确保 DOM 操作完成非常重要。因此,您应该在异步函数中调用它。使用 computed()
通常不适合此目的。相反,最好仅在利息值发生变化时检查 scrollWidth
是否发生变化。
要实现此目的,您可以使用
watch()
函数。您指定要观察哪个变量的更改以及检测到更改时要执行哪个函数。在我们的例子中,我们将监视 currentValue
变量并执行异步函数。因此,当 currentValue
更改时,将执行异步函数,使用 nextTick()
等待更新,然后检查新的 clientWidth
和 scrollWidth
之间的差异。然后,真/假值存储在可以在代码中引用的单独变量中。
<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
const currentValue = ref("");
const textFieldComponent = ref<VTextField>();
// store boolean for disabled checking
const isTextFieldCuttingOffContent = ref(false);
// checks if the current scrollWidth of the input field is wider than the clientWidth
const checkTextOverflow = async () => {
await nextTick();
const inputWidth = textFieldComponent.value.clientWidth;
const textWidth = textFieldComponent.value.scrollWidth;
isTextFieldCuttingOffContent.value = textWidth > inputWidth;
};
// call checkTextOverflow() function when currentValue changed
watch(currentValue, checkTextOverflow);
</script>
<template>
<v-container style="width: 300px">
<v-tooltip :text="currentValue" :disabled="!isTextFieldCuttingOffContent">
<template v-slot:activator="{ props }">
<div v-bind="props">
<v-text-field
id="here"
ref="textFieldComponent"
label="label goes here"
v-model="currentValue"
/>
</div>
</template>
</v-tooltip>
</v-container>
</template>
const { createApp, ref, watch, nextTick } = Vue
const app = createApp({
setup() {
const currentValue = ref('')
const textFieldComponent = ref(null)
// store boolean for disabled checking
const isTextFieldCuttingOffContent = ref(false)
// checks if the current scrollWidth of the input field is wider than the clientWidth
const checkTextOverflow = async () => {
await nextTick() // check DOM updates
const inputWidth = textFieldComponent.value.clientWidth
const textWidth = textFieldComponent.value.scrollWidth
isTextFieldCuttingOffContent.value = textWidth > inputWidth
}
// call checkTextOverflow() function when currentValue changed
watch(currentValue, checkTextOverflow)
return { currentValue, textFieldComponent, isTextFieldCuttingOffContent }
},
}).mount('#app')
.container {
width: 100px;
resize: both;
overflow: hidden;
}
input {
width: 100%;
height: 100%;
box-sizing: border-box;
}
<!-- WithNextTick.vue -->
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
<div id="app">
<div class="container">
<input ref="textFieldComponent" v-model="currentValue">
</div>
<p v-if="isTextFieldCuttingOffContent">Warning: value overflow detected</p>
</div>
watch()
变量,使用 Observer
如果您不仅需要在打字过程中检查
scrollWidth
与 clientWidth
的对比,还需要在任何宽度操作过程中(例如调整大小或其他更改),那么请随意实施 @tao 提到的 Observer 解决方案!然而,值得注意的是,我的解决方案在这种情况下仍然是必不可少的,因为它主要关注于观察打字过程中的scrollWidth
变化,观察者无法直接跟踪这些变化,因为它主要监视 DOM 操作和/或元素调整大小,这些操作是在输入字段中输入内容或使用 JavaScript 修改变量时不会触发。
要了解观察者是如何工作的,请阅读@tao的内容丰富的答案。
import { ref, onMounted } from 'vue'
let resizeObserver = null
let mutationObserver = null
onMounted(() => {
// declare ResizeObserver to textFieldComponent
resizeObserver = new ResizeObserver(checkTextOverflow)
resizeObserver.observe(textFieldComponent.value)
// declare MutationObserver to textFieldComponent
mutationObserver = new MutationObserver(checkTextOverflow)
mutationObserver.observe(textFieldComponent.value, {
childList: true,
subtree: true,
characterData: true,
attributes: true
})
})
const { createApp, ref, watch, onMounted } = Vue
const app = createApp({
setup() {
let resizeObserver = null
let mutationObserver = null
const currentValue = ref('')
const textFieldComponent = ref(null)
// store boolean for disabled checking
const isTextFieldCuttingOffContent = ref(false)
// checks if the current scrollWidth of the input field is wider than the clientWidth
const checkTextOverflow = () => {
const inputWidth = textFieldComponent.value?.clientWidth || 0
const textWidth = textFieldComponent.value?.scrollWidth || 0
isTextFieldCuttingOffContent.value = textWidth > inputWidth
}
// call checkTextOverflow() function when currentValue changed
watch(currentValue, checkTextOverflow)
// run function after dom loaded
onMounted(() => {
// declare ResizeObserver to textFieldComponent
resizeObserver = new ResizeObserver(checkTextOverflow)
resizeObserver.observe(textFieldComponent.value)
// declare MutationObserver to textFieldComponent
mutationObserver = new MutationObserver(checkTextOverflow)
mutationObserver.observe(textFieldComponent.value, {
childList: true,
subtree: true,
characterData: true,
attributes: true
})
})
return { currentValue, textFieldComponent, isTextFieldCuttingOffContent }
},
}).mount('#app')
.container {
width: 100px;
resize: both;
overflow: hidden;
}
input {
width: 100%;
height: 100%;
box-sizing: border-box;
}
<!-- WithObserver.vue -->
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
<div id="app">
<div class="container">
<input ref="textFieldComponent" v-model="currentValue">
</div>
<p v-if="isTextFieldCuttingOffContent">Warning: value overflow detected</p>
</div>
监控变量是必要的,并且在任何情况下都不可避免。此外,通过@tao提到的功能,您还可以考虑意外事件,例如调整浏览器窗口的大小,以确保工具提示显示按预期工作。除了监控变量之外是否还需要其他功能,这取决于具体情况和要求。
我准备了一个示例代码片段用于演示目的,以便您可以比较代码和结果。
@rozsazoltan 的回答 1(将
.scrollWidth
和 .clientWidth
之间的差异包装到 nextTick()
中,等待 DOM 更新)是一个快速修复,它将涵盖大多数情况。最有可能的是,这正是您当前情况下所需要的。
但是,由于您要求一个 canonical 答案(这意味着您想要在 all 可能的情况下工作的东西),该解决方案是不够的,因为它假设元素的
.clientWidth
和 .scrollWidth
仅在以下情况下发生变化文本内容发生变化,这是不正确的。
他们还可以更改:
window
对象)或其后续兄弟元素的大小font-size
、font-family
、white-space
、word-wrap
等)为了捕捉(并因此做出反应)所有这些变化,您需要:
.scrollWidth
和 .clientWidth
值的函数,将它们放置在组件状态的某个位置。通过此设置,两者之间的差异将是反应性的,而不需要 nextTick()
。并且它将涵盖所有情况。概念证明(伪代码):
<!-- ... -->
<v-tooltip :disabled="!state.hasTooltip">
<!-- ... -->
<v-text-field
ref="textFieldComponent"
v-model="currentValue"
/>
import { useResizeObserver, useMutationObserver } from '@vueuse/core'
const currentValue = ref('')
const state = reactive({
scrollWidth: 0,
clientWidth: 0,
hasTooltip: computed(
() => state.clientWidth > state.scrollWidth
)
})
const updateState = () => {
state.scrollWidth = +textFieldComponent.value.scrollWidth || 0
state.clientWidth = +textFieldComponent.value.clientWidth || 0
}
watch(currentValue, updateState, { immediate: true })
onMounted(() => {
useResizeObserver(textFieldComponent.value, updateState)
useMutationObserver(textFieldComponent.value, updateState)
})
如果您需要实际的实施示例,请告诉我,我将从您的沙箱开始创建一个示例。
1 - 当时我回答 rozsazolnan 的答案尚未包含调整大小和突变观察者,这是我添加我的主要原因(也是为了展示稍微不同的语法)。现在佐尔坦的答案已经完成了。