12

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.

ccprog
  • 20,308
  • 4
  • 27
  • 44
  • 1
    *in Chrome, the caret will be positioned at the nearest character only if you click inside the blue area* --> it's not the case for me (Chrome 75 windows 8) – Temani Afif Jul 01 '19 at 15:39
  • Thanks for noticing that. Behavior seems to be restricted to Mac and Linux. – ccprog Jul 01 '19 at 16:05
  • AFAIK, this problem is in iOS too – yqlim Jul 04 '19 at 01:15
  • 1
    There likely isn't a way of fixing this without Chrome's dev team doing so or working around the issue. To work around the issue we will likely need to know more about your actual setup. – Zach Saucier Jul 05 '19 at 21:54
  • 1
    @ZachSaucier The contenteditable is controlled by [tag:quill], so the setup of elements and style properties needs to follow a pattern that can be implemented with its extension mechanisms. Also, we do not only need to provide for one size, but for font sizes and line heights changeable by the user. The main problem we have is that users get a visual feedback of where the text line is by marking the parent element of the `

    ` elements, so for them it is a justified expectation that every click inside that area will position the caret consistently.

    – ccprog Jul 06 '19 at 22:13
  • I think it is possible to fix this using `Selection` API(though a bit hackish). But it is time consuming! – mahan Jul 08 '19 at 05:04
  • @mahan how would that work? The problem is not manipulating text selection, (quill has its own abstraction for that), but pointer events behaving unreasonable. We have already thought about overwriting the complete event behavior, but that would be the absolutely last resort. – ccprog Jul 08 '19 at 11:40
  • You need to find the closest text node to the pointer and position cursor just there. – mahan Jul 09 '19 at 07:39
  • latest chrome doesn't have this issue – Dean Van Greunen Jul 09 '19 at 18:30
  • @DeanVanGreunen which Chrome version are you using? Thanks! Also are you using Windows or Mac? – Crashalot Jul 09 '19 at 20:52
  • @mahan This amounts to a complete re-implementation of mouse/pointer events, as we need marking text, doubleclicks tec also to work. – ccprog Jul 09 '19 at 20:58

1 Answers1

5

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.

Community
  • 1
  • 1
Alekos Dordas
  • 802
  • 7
  • 20
  • 2
    Wow. This is a bit like magic. I have no idea why this works, but it does. – ccprog Jul 09 '19 at 01:22
  • I am glad that it works. Basically it's just a work-around to give each browser the rules that do the job. – Alekos Dordas Jul 09 '19 at 06:24
  • Further study shows it is not usable for us, although the reason lies outside the testcase provided so far. It is possible that one line contains multiple consecutive spans with differing font-size or font-family. With vertical-align not set to baseline, the expected line behavior breaks. – ccprog Jul 09 '19 at 12:21
  • THe test case has been amended to give you an example what needs to work. – ccprog Jul 09 '19 at 21:00
  • I updated the answer (I was signed out, that is why it marked as "community") – Alekos Dordas Jul 10 '19 at 05:54
  • I found a further improvement that fixes the behavior after the last line: add an additional linebreak after the last line, and set `vertical-align: bottom` for both those linebreaks. Even more, since our use case asssures that font-size and line-height is set for all spans, I can do `p { font-size: 0px; line-height: 0 }` instead of targeting first/last line. See this [Codepen](https://codepen.io/ccprog/pen/dBaRWN) for the complete version. – ccprog Jul 10 '19 at 15:08
  • One last question: where did you find the need for the Firefox rule? I could not verify it is needed. – ccprog Jul 10 '19 at 15:16
  • At Firefox (Linux at least - not sure if on Windows as well, and cannot check now) font-size: 0px isn't respected - it keeps a minimum height, that adds some extra little space before the first line (as a result of the new-line that we added with ::before) – Alekos Dordas Jul 10 '19 at 15:27
  • It seems setting `font-size: 0px` for the p element catches that as well. – ccprog Jul 10 '19 at 15:41
  • Sorry for not awarding the bounty in time; it seems you only received 50 points instead of 100. Let me try to award the other 50 directly. Thanks again for your help! – Crashalot Jul 12 '19 at 03:07
  • No worries buddy ;-) – Alekos Dordas Jul 12 '19 at 05:04
  • @AlekosDordas it's important you get the full bounty. you were very helpful and diligent in updating the answer. i'll create a new bounty and award you the points. that seems like the best and fairest option. – Crashalot Jul 12 '19 at 21:06