Home > Mobile >  Detect which element flex-wrap affects
Detect which element flex-wrap affects

Time:05-29

Typing Game

I'm making a type test app where I have a caret line "|" moving from left to right.

This is what the rough build of my game looks like:

.words_wrapper {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  width: 400px;
}

.word {
  padding: 0.2rem;
}

.game{
  position: relative;
}
.caret {
  position: absolute;
  top: 0;
  height: 22px;
  width: 2px;
  background-color: red;
}
<div >
  <div  style="left: 10px;"></div>
  <div >
    <div >bob</div>
    <div >about</div>
    <div >us</div>
    <div >know</div>
    <div >computer</div>
    <div >again</div>
    <div >fails</div>
    <div >running</div>
    <div >bob</div>
    <div >about</div>
    <div >us</div>
    <div >know</div>
    <div >computer</div>
    <div >again</div>
    <div >fails</div>
    <div >running</div>
  </div>
</div>

I have a state variable to adjust the left attribute

const [caretPos,setCaretPos] = useState();

Then I modify this position based on other factors when typing and apply it in the div style.

<div className='caret' style={{left: `${caretPos}px`}>

Now, my question is, How can I detect if my caret is at the end of the line and make it go to the next line

Notice that the div has a flex-wrap property so it automatically makes the words go to the next line. Is there a way to detect which word is at the end of the line or at the beginning of the line?

CodePudding user response:

I have two ways of thinking about this problem:

  • The first answers your code question directly (how do I detect when the next flex element wraps).
  • The second addresses your cursor problem with an alternative approach.

Detecting a Flex Wrap

You might be able to detect a wrap by using the actual positions of the objects via getBoundingClientRect. This function gives you where something is relative to the viewport. Crucially, we know an element "wraps" if its vertical location is different from that of the previous element.

const isWrapping = (previous, current) =>
  previous.getBoundingClientRect().top !== current.getBoundingClientRect().top

const isWrapping = (previous, current) =>
  previous.getBoundingClientRect().top !== current.getBoundingClientRect().top

const highlightWrappingElements = (container) => {
  for (let i = 1; i < container.children.length;   i) {
    const previous = container.children[i - 1]
    const current = container.children[i]
    
    if (isWrapping(previous, current))
      current.classList.add('wrapping')
  }
}

highlightWrappingElements(document.querySelector('#container'))
.container {
  width: 30rem;
  border: 1px solid black;
  gap: 0.5rem;
  display: flex;
  flex-wrap: wrap;
}

.box {
  width: 5rem;
  height: 5rem;
  background: blue;
}

.wide { width: 7.5rem; }
.wrapping { background: orange; }
<div id="container" >
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
  <div ></div>
</div>

In the example, elements that start a new line are orange.

How you would integrate this technique into your problem depends on how your React code is set up, but the idea should be a good starting point.

Moving a Caret

Your actual goal is to move a caret through a paragraph of letters. To do so accurately, it must:

  • Know the length of each letter to position itself accurately
  • Know where the end of the line is so it can wrap

Those are non-trivial problems, especially if the box has to resize or the paragraph content is dynamic. That is, if you are trying to position the caret manually with position: absolute.

Instead, you might be able to actually embed the caret in the text itself.

<p>This is s<span ></span>ome text.</p>

This solves the above two issues immediately, as the caret will always be exactly where it needs to be as long as you rerender the node when the caret's position changes.

const paragraph = document.querySelector('#paragraph')
const content = paragraph.textContent

const caret = document.createElement('span')
caret.classList.add('caret')
let caretPosition = 0
const advanceCaret = () => {
  caretPosition  = 1
  if (caretPosition >= content.length)
    caretPosition = 0

  paragraph.innerHTML = content.substring(0, caretPosition)   caret.outerHTML   content.substring(caretPosition)
}

setInterval(advanceCaret, 100)
.container {
  width: 30rem;
  border: 1px solid black;
  padding: 1rem;
  font-size: 1.25rem;
}

p { margin: 0; }

.caret {
  position: relative;
  overflow: visible;
}

.caret::before {
  content: '';
  border-left: 2px solid red;
  position: absolute;
  height: 100%;
  top: 0;
  left: 0;
}
<div >
  <p id="paragraph">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

I prefer this as it's simple and avoids most of the complexity with manual positioning, but whether it's viable is up to how you are ultimately rendering your paragraph of text.

  • Related