该网站的功能与其他打字测试网站类似,用户收到文本并需要键入单词。目前,文本总是分成三行。当用户完成键入第二行时,它向上移动,并且用户继续键入第三行。
为了计算行的划分,我测试在不超过容器宽度的情况下,一个句子可以容纳多少个单词,在容器中将显示行。为了实现这一点,我使用函数“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>
字符串
1条答案
按热度按时间8ehkhllq1#
我认为问题可能在于,在执行异步布局任务之前,您创建的跨距的测量宽度并不准确。
我已经编写了自己的实用函数,通过创建画布来测量文本宽度,以避免这个问题。它可以在@tubular/util中找到。下面是函数的代码:
字符串