Search code examples
javascripthtmljquerycss

Put ellipsis in the middle of input field


How can I put ellipsis in the middle of texts and have the results like this

sdghkjsd ... ndfgjknk

I want to truncate words on mobile and tablet view only supported on all browsers

and I want to be able to enter new values in input and make submit without the changes getting truncate (like I found sdghkjsd ... ndfgjknk and enter sdghkjsdghhjdfgjhdjghjdfhghjhgkdfgjnfkdgnjkndfgjknk and submit it without being truncated (submit the original, longer value)

// making the input editable
$(".long-value-input").on("click", function() {
  $(this).prop("readonly", "");
  $(this).focus();
});
/* For smart phones */
@media screen and (max-width: 600px) {
  .field {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
}

/* For tablets */
@media screen and (min-width: 600px) and (max-width: 900px) {
  .field {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
}
<input type="text" class="field" value="sdghkjsdghhjdfgjhdjghjdfhghjhgkdfgjnfkdgnjkndfgjknk" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


Solution

  • CSS cannot place ellipsis in the middle of a string.
    So, by using JavaScript, to give a general idea:

    • On "blur" Event, write the current value to a ::before pseudo element
    • If the scrollWidth is larger than the clientWidth:
      • Remove one character from the array middle and insert ellipsis character
      • Repeat until scrollWidth matches the clientWidth

    const ellipsify = (elParent) => {
      const elInput = elParent.querySelector("input");
      const init = () => {
        const chars = elInput.value;
        const tot = chars.length;
        const half = Math.floor(tot / 2);
        let i = 0;
        elParent.dataset.val = elInput.value;
        while (i < half && elParent.scrollWidth > elParent.offsetWidth) {
          const ir = Math.max(0, Math.floor((tot - i) / 2)); // index of removal
          elParent.dataset.val = [...chars].toSpliced(ir, i, " … ").join("");
          i += 2;
        }
      };
      
      elInput.addEventListener("blur", init);
      init();
    };
    
    document.querySelectorAll(".input-ellipsis").forEach(ellipsify);
    .input-ellipsis {
      position: relative;
      display: inline-flex;
      align-items: center;
    
      >* {
        font: inherit;
        line-height: 1.3em;
        box-sizing: border-box;
      }
    
      &::before {
        content: attr(data-val);
        position: absolute;
        line-height: 1.5em;
        pointer-events: none;
        width: 100%;
        color: #000;
        white-space: nowrap;
        text-indent: 4px;
      }
    
      &:has(input:focus)::before {
        opacity: 0;
      }
    
      input {
        appearance: none;
        color: transparent;
    
        &:focus {
          color: #000;
        }
      }
    }
    <span class="input-ellipsis">
      <input type="text" placeholder="Enter your text" value="The quick brown fox jumps over the lazy dog." />
    </span>
    
    <span class="input-ellipsis">
      <input type="text" placeholder="Enter your text" value="No ellipsis needed!" />
    </span>
    
    <span class="input-ellipsis">
      <input type="text" placeholder="Enter your text" value="" />
    </span>

    Depending on the expected length of the input value you might want to inverse the logic for efficiency.

    With the above, the INPUT's value never changes and you can submit it to the server without any issues. The trick in the example above is the opacity and text color we use to switch in order to show the ::before contents or the input value, when focused.

    PS: what I would additionally improve (out of scope for this answer) is: if a user focuses the input in the second portion of the text (right of the ellipsis), when the input value "enlarges" - move the caret to match the focused position-in-text.