javascript 输入clientWidth和scrollWidth始终相等

kpbpu008  于 2023-06-20  发布在  Java
关注(0)|答案(3)|浏览(131)

bounty将在6天内到期。此问题的答案有资格获得+50声望奖励。baitendbidz正在寻找一个规范答案

我使用的是Vuetify text-fields,如果内容大于字段宽度(用户需要滚动),我希望显示包含内容的工具提示。工具提示应该只在悬停时出现(默认行为)。我从以下内容开始
(Playground)

<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>

我还尝试使用一个观察者而不是计算 prop (Playground)
问题是isTextFieldCuttingOffContent总是返回false,因为clientWidthscrollWidth总是相等的。你知道哪里出问题了吗?

slsn1g29

slsn1g291#

1.)watch()变量,使用nextTick()

scrollWidth由于DOM操作而发生更改。如果console.log输入字段的当前scrollWidth,它确实会在代码中正确更改。然而,这里的问题是这些DOM数据在VueReact性系统中不会自动更新。
要检索更新后的值,可以使用nextTick()。但是,等待nextTick()的值以确保DOM操作完成是很重要的。因此,您应该在async函数中调用它。使用computed()通常不适合此目的。相反,最好只在感兴趣的值发生变化时检查scrollWidth是否发生了变化。
要实现这一点,可以使用watch()函数。您可以指定要观察哪个变量的更改,以及在检测到更改时执行哪个函数。在本例中,我们将监视currentValue变量并执行一个异步函数。因此,当currentValue更改时,将执行async函数,等待使用nextTick()的更新,然后检查新的clientWidthscrollWidth之间的差异。然后,true/false值存储在一个单独的变量中,该变量可以在代码中引用。

<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/vue@3.3.4/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

如果您不仅需要在输入时检查scrollWidthclientWidth,而且还需要在任何宽度操作(例如调整大小或其他更改)期间检查,那么请随时实现@tao提到的Observer解决方案!然而,需要注意的是,我的解决方案在这种情况下仍然是必不可少的,因为它主要关注在键入过程中观察scrollWidth的变化,而Observer无法直接跟踪这些变化,因为它主要监视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/vue@3.3.4/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上尝试解决方案

qojgxg4l

qojgxg4l2#

@rozsazoltan的答案*1***(将.scrollWidth.clientWidth之间的差异 Package 到nextTick()中,等待DOM更新)是一个快速解决方案,将涵盖大多数情况。很有可能,这正是您在当前情况下所需要的。
但是,既然你要求一个
规范的答案(这意味着你想要在所有**可能的情况下都有效的东西),这个解决方案是不够的,因为它假设元素的.clientWidth.scrollWidth只在文本内容改变时改变,这是不正确的。
它们也可以在以下情况下更改:

  • 更改元素的任何祖先(包括window对象)或其后续兄弟的大小
  • 改变CSS属性值(自己的或继承的)导致文本的不同 Package (例如:font-sizefont-familywhite-spaceword-wrap等)

为了捕捉(并响应)所有这些变化,您需要:

  • 元素上的两个观察者(ResizeObserverMutationObserver
  • 读取元素当前.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的答案还没有包含resize和mutation观察器,这是我添加我的观察器的主要原因(也是为了展示稍微不同的语法)。现在佐尔坦的答案是完整的。

rjee0c15

rjee0c153#

检查clientWidth和scrollWidth属性可能是您的问题。时间可能是罪魁祸首-特别是当你试图访问它们的时候。元素的最终尺寸可能尚未建立,或者渲染过程可能尚未完成。
当currentValue发生变化时,通过不断检查尺寸,结合React式监视器可以确保精确的测量。为了实现这种技术,这里是代码的修订版本。

<script setup lang="ts">
  import { ref, computed, watch } from "vue";

  const currentValue = ref("");
  const textFieldComponent = ref<VTextField>();
  const isTextFieldCuttingOffContent = ref(false);

  const checkTextOverflow = () => {
    if (!textFieldComponent.value) {
      return;
    }

    if (!currentValue.value) {
      isTextFieldCuttingOffContent.value = false;
      return;
    }

    const el = textFieldComponent.value.$el;
    isTextFieldCuttingOffContent.value = el.clientWidth < el.scrollWidth;
  };

  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
            ref="textFieldComponent"
            label="label goes here"
            v-model="currentValue"
          />
        </div>
      </template>
    </v-tooltip>
  </v-container>
</template>

每当currentValue的值发生变化时,就会调用checkTextOverflow函数,以确保在DOM更新之后获取clientWidth和scrollWidth的测量值。
使用React式监视器修改isTextFieldCuttingOffContent变量可以精确地识别文本字段何时截断内容。:)希望这有帮助!

相关问题