输入的clientWidth和scrollWidth始终相等

问题描述 投票:0回答:2

我正在使用 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
总是相等。您有什么想法是错误或遗漏的吗?

javascript html vue.js vuejs3
2个回答
4
投票

1.)
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>


升级(2023-06-19 #1)(受@tao评论启发)

2.)
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提到的功能,您还可以考虑意外事件,例如调整浏览器窗口的大小,以确保工具提示显示按预期工作。除了监控变量之外是否还需要其他功能,这取决于具体情况和要求。

我准备了一个示例代码片段用于演示目的,以便您可以比较代码和结果。

在 Vue SFC Playground 上尝试解决方案


2
投票

@rozsazoltan 的回答 1(将

.scrollWidth
.clientWidth
之间的差异包装到
nextTick()
中,等待 DOM 更新)是一个快速修复,它将涵盖大多数情况。最有可能的是,这正是您当前情况下所需要的。

但是,由于您要求一个 canonical 答案(这意味着您想要在 all 可能的情况下工作的东西),该解决方案是不够的,因为它假设元素的

.clientWidth
.scrollWidth
仅在以下情况下发生变化文本内容发生变化,这是不正确的。

他们还可以更改:

  • 更改元素的任何祖先(包括
    window
    对象)或其后续兄弟元素的大小
  • CSS 属性值的更改(自有或继承)导致文本的不同换行(例如:
    font-size
    font-family
    white-space
    word-wrap
    等)

为了捕捉(并因此做出反应)所有这些变化,您需要:

  • 元素上的两个观察者(一个 ResizeObserver 和一个 MutationObserver
  • 一个读取元素当前
    .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 的答案尚未包含调整大小和突变观察者,这是我添加我的主要原因(也是为了展示稍微不同的语法)。现在佐尔坦的答案已经完成了。

© www.soinside.com 2019 - 2024. All rights reserved.