Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Positioning the caret in a contenteditable with large line-height in Chrome

Chrome on macOS and Chromium on Linux don’t sensibly position the caret when clicking inside an editable area for larger line heights.

In this example, we set a value for line-height for <span> elements. Leaving it off and inheriting from the parent element is not possible because of other app requirements, mainly the use of Quill.js rich text editor. There may be multiple <span> per line with differing font sizes, but no nested elements.

p {
  display: inline-block;
  margin: 0;
  background: lightgrey;
}
span {
  line-height: 2.5;
  font-size: 50px;
  background: lightblue;
}
span.small {
  font-size: 25px;
}
<p contenteditable><span>some </span><span class="small">text</span><br/><span>some text</span></p>

In Firefox, if you click into the gray area (marking the <p> element), the caret will always be positioned at the nearest character. If you click between lines, the caret also positions sensibly.

In Chrome, the caret positions at the nearest character only if you click inside the blue area (marking the element). In the grey area, the caret ends up at the start of the next line, or at the end of the last line if you click below the last span.

How can you replicate the Firefox behavior with Chrome?

Note: giving the spans a display: inline-block as recommended here does not solve the problem.

like image 827
ccprog Avatar asked Jul 01 '19 14:07

ccprog


1 Answers

As you already know, it has to do with Chrome and how it deals with line height.

Although, I have written a workaround that seems to work well on Linux (Chrome, Firefox) as well as Windows (Chrome, Firefox, Edge).

With vertical-align: text-bottom, all lines seem to work as intended except for the first one. So the idea is to add a line break (and negate it afterwards with font-size: 0)

p::before {
  content: "\A";
  white-space: pre;
  display: inline;
}

p::first-line {
  font-size: 0px;
}

This works pretty well on Chrome (both Linux and Windows), but on Firefox I didn't manage to negate the extra line break. So, since it was initially working nice in the first place I used a firefox-only rule to hide the extra line break.

So, we have our workaround working on Chrome and Firefox (both Windows and Linux) but Edge had some difficulties with vertical-align so (once again) I used an ms only rule to unset the vertical-align.

Result (working on Chrome Windows/Linux, Firefox Windows/Linux, Edge Windows)

p {
  display: inline-block;
  margin: 0;
  background: lightgrey;
  
}
span {
  line-height: 2.5;
  font-size: 30px;
  background: lightblue;
  vertical-align: text-bottom;
}

p::before {
  content: "\A";
  white-space: pre;
  display: inline;
}

p::first-line {
  font-size: 0px;
}

/*  Firefox only */
@-moz-document url-prefix() {
  p::before {
    display: none;
  }
}

/* Edge only */
@supports (-ms-ime-align:auto) {
  span {
    vertical-align: unset;
  }
}
<p contenteditable><span>some text</span><br/><span>some text</span></p>

UPDATE

At the updated testcase, that you have multiple font sizes per line, you will need to skip vertical-align: bottom|text-bottom and compromise with having the extra space "allocated" to the line below (only in Chrome - Linux).

Note that you will still need the aforementioned "hack" for the first line in order to have a consistent behavior between all lines.

Have a look at this codepen for the updated testcase.

like image 113
Alekos Dordas Avatar answered Nov 04 '22 14:11

Alekos Dordas