javascript 尝试在我的Nuxt应用程序中计算文本的宽度

lb3vh1jj  于 2023-08-02  发布在  Java
关注(0)|答案(1)|浏览(95)

该网站的功能与其他打字测试网站类似,用户收到文本并需要键入单词。目前,文本总是分成三行。当用户完成键入第二行时,它向上移动,并且用户继续键入第三行。
为了计算行的划分,我测试在不超过容器宽度的情况下,一个句子可以容纳多少个单词,在容器中将显示行。为了实现这一点,我使用函数“getTextWidth”,它创建一个与网站样式相同的span,并检索该span的宽度。
起初,一切都很完美。但是,在我开始使用外部字体后,事情开始出错。在某些情况下,某些行占用两行而不是一行。
我尝试了几件事,比如改变字体,调整大小,检查span标签以查看它们是否具有样式,渲染它们等等。我相信,不知何故,当跨度被创建时,它没有接收样式,导致行更小,我的代码认为它们适合,但我可能是错的。
所有的代码都在上面,是的,它很大,但我可能会遗漏一些东西,所以我把整个组件

<template>
    <div class="typing-container">
        <div id="text-container">
            <div v-for="(line, lineId) in displayedLines" :key="lineId">
                <span v-for="(letter, letterId) in line.line" :key="letterId">
                    <span v-if="data.letterIndex === data.absoluteLetterIndexex[lineId+data.lineIndex][letterId]"  class="text-line"></span>
                    <span :style="{color: data.colors[data.absoluteLetterIndexex[lineId+data.lineIndex][letterId]]}">{{letter}}</span>
                </span>
            </div>
        </div>
        <font-awesome-icon :icon="['fas', 'rotate']" />
    </div>

</template>

<script setup lang="ts">

    import { computed, reactive, onMounted } from 'vue';
    import { useTypingStore } from '../../store/typing/typingStore'
    import { Word, Line } from './types';

    const store = useTypingStore();
    const { $axios } = useNuxtApp();

    const data = reactive({
        text: "",
        letterIndex: 0,
        wordIndex:0,
        lineIndex: 0,
        colors: [] as string[],
        words: [] as Word[],
        lines: [] as Line[],
        absoluteLetterIndexex: [] as number[][] 
    })

    function keyPressed(event: KeyboardEvent){
        const { key } = event;

        const regex = /^[a-zA-Z'.,:?!;()-]$/;

        const currentWord = data.words[data.wordIndex];
        const lastWord = data.words[data.wordIndex-1] ?? null;
        // just for reducing boiler plate from the data word

        if(regex.test(key)){ // if its a normal text letter
            if(key === data.text[data.letterIndex]){  // if its the right letter
                data.colors[data.letterIndex] = 'white'
                data.words[data.wordIndex].lettersLeft--;
                if(data.letterIndex === data.absoluteLetterIndexex[data.lineIndex+1][data.absoluteLetterIndexex[data.lineIndex+1].length-2]){ // if the letter is the last from the second line (-2 because there is always a empty space in the end)
                    data.lineIndex++;
                }
            }else{
                data.colors[data.letterIndex] = 'red';
            }
            data.words[data.wordIndex].lastWritedIndex = data.letterIndex +1;
            data.letterIndex+=1;
        }else{
            if(key === "Backspace"){

                if(data.letterIndex <= 0 ){ //start of the text cannot go back
                    return;   
                }

                if(data.wordIndex-1 >=0 && data.letterIndex === currentWord.start && lastWord.lettersLeft === 0){ // If we are at the beginning of the word and are trying to do a backspace, which would make us go back to the last word, so we gotta check if it wasn't already complete.
                    return;
                }
                
                if (currentWord.start > data.letterIndex - 1){ // if we are in the start of a word and do a backspace we need to go to the last writed letter in the previous word
                    data.letterIndex = lastWord.lastWritedIndex;
                    data.words[data.wordIndex].lastWritedIndex =  data.words[data.wordIndex].start;
                    data.wordIndex--;
                }else{
                    data.letterIndex-=1;
                    if(data.colors[data.letterIndex] === "white"){ // if i'm deleting a already correct letter there will be one more letter left
                        data.words[data.wordIndex].lettersLeft++;
                        data.words[data.wordIndex].lastWritedIndex--;
                    }
                    data.colors[data.letterIndex] = "gray";
                }
            }
            if(key === ' ' && currentWord.start !== data.letterIndex){
                data.letterIndex = data.words[++data.wordIndex].start
                
            }
        }
    }

    function setData( text: string ){
        let newWords: Word[] = [];
        let length = 0;
        let word = "";
        let start=0,lastWritedIndex;

        for (var i = 0; i < text.length; i++) {
          if(text[i] !== ' '){
            length++;
            word+=text[i];
          }else{
            newWords.push({
              start,
              word,
              length,
              lastWritedIndex: start,
              index: newWords.length,
              lettersLeft: length
            });
            start = i+1;
            word = "";
            length= 0
          }
        }
        data.words = newWords;
        data.text = text
        data.lines = divideTextInLines();
        data.colors = (new Array(data.text.length).fill('gray'))
    }

    async function getRandomText(){
        const text = ((await $axios.get('http://metaphorpsum.com/paragraphs/6')).data.replace(/\n/g, ' '));
        setData(text)
    }

    function divideTextInLines() {
        
        let lines: Line[] = [];
        let currentLine = "";
        let cumulativeCharacters= 0;
        const containerWidth = document.getElementById("text-container")?.offsetWidth;

        for (let i = 0; i < data.words.length; i++) {
            const word = data.words[i].word;
            const lineWithWord = currentLine ? currentLine + " " + word : word;
            const lineWidth = getTextWidth(lineWithWord);

            if (containerWidth && lineWidth <= containerWidth) {
                currentLine = lineWithWord;
            } else {
                console.log(currentLine, lineWidth);
                currentLine+= " ";
                lines.push({
                    line: currentLine,
                    cumulativeCharacters
                });
                
                let lineAbsoluteIndexes = [];
                for (let j = 0; j < currentLine.length; j++) {
                    lineAbsoluteIndexes.push(cumulativeCharacters + j);
                }
                data.absoluteLetterIndexex.push(lineAbsoluteIndexes);
                cumulativeCharacters += currentLine.length;
                currentLine = word;
            }
        }

        if (currentLine) {
            currentLine+= " ";
            lines.push({
                line: currentLine,
                cumulativeCharacters
            });
            let lineAbsoluteIndexes = [];
            for (let j = 0; j < currentLine.length; j++) {
                lineAbsoluteIndexes.push(cumulativeCharacters + j);
            }
            data.absoluteLetterIndexex.push(lineAbsoluteIndexes);
        }

        return lines;
    }

    function getTextWidth(text: string) {
        const span = document.createElement('span');
        const spanStyles = {
            fontSize: '1.7rem',
            fontFamily: 'RobotMono',
            whiteSpace: 'nowrap',
            visibility: 'hidden',
        };

        Object.assign(span.style, spanStyles);

        span.innerText = text;
        console.log(span)
        document.body.appendChild(span);
        const width = span.offsetWidth;
        document.body.removeChild(span);
        return width;
    }

    const colors = computed(()=>{
        return data.colors;
    })

    const displayedLines = computed(()=>{
        return data.lines.slice(data.lineIndex, data.lineIndex+3);
    })

    onMounted(()=>{
        window.addEventListener("keydown", keyPressed )
        getRandomText();
    })

</script>

<style lang="css" scoped>
    #text-container{
        width: 80rem;
        max-width: 90%;
        font-size: 1.7rem;
        font-family: RobotMono;
    }
    .text-line{
        padding: 2px 1px;
        background-color: red;
        font-size: 1.7rem;
        font-weight: bold;
    }

    .typing-container{
        display: flex;
        flex-direction: column;
        gap: 3rem;
        justify-content: center;
        align-items: center;
    }
</style>

字符串

8ehkhllq

8ehkhllq1#

我认为问题可能在于,在执行异步布局任务之前,您创建的跨距的测量宽度并不准确。
我已经编写了自己的实用函数,通过创建画布来测量文本宽度,以避免这个问题。它可以在@tubular/util中找到。下面是函数的代码:

export function getTextWidth(items: string | string[], font: string | HTMLElement, fallbackFont?: string): number {
  const canvas = ((getTextWidth as any).canvas as HTMLCanvasElement ||
                  ((getTextWidth as any).canvas = document.createElement('canvas') as HTMLCanvasElement));
  const context = canvas.getContext('2d')!;
  let maxWidth = 0;
  let elementFont;

  if (typeof font === 'string')
    elementFont = font;
  else if (typeof font === 'object')
    elementFont = getFont(font);

  if (elementFont)
    context.font = elementFont;
  else if (fallbackFont)
    context.font = fallbackFont;
  else
    context.font = 'normal 12px sans-serif';

  if (!Array.isArray(items))
    items = [items];

  for (const item of items) {
    const width = context.measureText(item).width;
    maxWidth = max(maxWidth, width);
  }

  return maxWidth;
}

字符串

相关问题