21

If you have the following HTML:

<div contenteditable="true">
  <p>The first paragraph</p>
  <p>The second paragraph</p>
</div>

Is there a way to style the paragraph that is being edited differently from all other paragraphs in the div that is contenteditable?

div > p:focus { border: 1px solid red }

won't be applied to the paragraph being edited.

Here's a JSFiddle you can play with.

How can I style the paragraph being edited (the <p>-tag) differently?

I'd prefer a CSS-only solution, but perhaps I'll have to use JavaScript.

EDIT:

Since we could not find a pure CSS solution (and using :hover is not a real solution for me) I am now looking for some lines of JavaScript that set the attribute class="focus" on the node that is being edited.

pvorb
  • 7,157
  • 7
  • 47
  • 74

3 Answers3

11

I think I found a solution.

With the following code snippet you can get the parent element at the current caret position when the selection changes.

var selectedElement = null;
function setFocus(e) {
  if (selectedElement)
    selectedElement.style.outline = 'none';

  selectedElement = window.getSelection().focusNode.parentNode;
  // walk up the DOM tree until the parent node is contentEditable
  while (selectedElement.parentNode.contentEditable != 'true') {
    selectedElement = selectedElement.parentNode;
  }
  selectedElement.style.outline = '1px solid #f00';
};
document.onkeyup = setFocus;
document.onmouseup = setFocus;

Here I change the outline property manually but you could of course add a class attribute and set the style via CSS.

You still have to manually check if selectedElement is a child of our <div> with the contenteditable attribute. But I think you can get the basic idea.

Here's the updated JSFiddle.

EDIT: I updated the code as well as the JSFiddle to make it work in Firefox and Internet Explorer 9+, too. Unfortunately these Browsers do not have a onselectionchange event handler, so I had to use onkeyup and onmouseup.

pvorb
  • 7,157
  • 7
  • 47
  • 74
  • This does not work in Firefox or IE10 (probably all older IE's too). – dsgriffin May 30 '13 at 15:56
  • Thank you for the hint. I'll have a look on it. – pvorb May 30 '13 at 17:16
  • 1
    +1. That's pretty much what I would have suggested. One thing is that this will fail if the user makes some text bold and then places the caret within the bold text because `window.getSelection().focusNode.parentNode` will now be the bold element rather than the paragraph. I'd recommend iterating through `parentNode`s until you hit a `

    ` element instead; see http://stackoverflow.com/a/4642894/96100. One other small thing: the selection can change without a key or mouse event firing (via Select All options in browser menus, for example) so you may want a polling solution as a fallback.

    – Tim Down May 30 '13 at 22:53
  • @TimDown Yeah that's right. It will fail if the markup within the `
    ` is different. Walking up the DOM tree is something I'd expect one can figure on his/her own. A polling solution is always a bit hacky, but probably I'll have to add it for completeness. (Although I cannot think of a case where one can select a _single_ paragraph in the `
    ` without firing a mouse or a key event.)
    – pvorb May 30 '13 at 23:34
  • Nice work, guys. The one change I would make is to use `outline` rather than `border`, as then the paras don't jump around as the border is added and removed. – ralph.m May 31 '13 at 02:13
  • @ralph.m I changed it. But I think this is less important for the question. – pvorb May 31 '13 at 06:58
  • @TimDown I also added some lines that ensure the parent node is `contentEditable`. – pvorb May 31 '13 at 07:02
  • The fiddle doesn't account for line breaks (shift+enter), rather than paragraph breaks. Here's a fiddle that can handle those line breaks http://jsfiddle.net/zcsw5731/ – Michael Khalili Nov 22 '19 at 17:13
  • 1
    firefox 52 supports now [`onselectionchange`](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onselectionchange) – Friedrich -- Слава Україні Nov 16 '21 at 23:16
3

Interesting question. If it's viable, I'd suggest moving the contenteditable attribute to the ps themselves: http://codepen.io/pageaffairs/pen/FHKAC

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">

<style media="all">

div > p:focus {outline: 1px solid red; }

</style>

</head>
<body>

<div>
    <p contenteditable="true">The first paragraph</p>
    <p contenteditable="true">The second paragraph</p>
</div>

</body>
</html>

EDIT: Here is another version, that causes para returns to generate paras instead of divs: http://codepen.io/pageaffairs/pen/dbyIa

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">

<style media="all">

div:focus {outline: none;}
div > p:focus, div > p:hover {outline: 1px solid red; }

</style>

</head>
<body>

<div contenteditable="true">
    <p contenteditable="true">The first paragraph</p>
    <p contenteditable="true">The second paragraph</p>
</div>

</body>
</html>
ralph.m
  • 13,468
  • 3
  • 23
  • 30
  • 1
    This is generally what I want, but it comes with some side-effects: Say I hit return at the end of the last paragraph. In my version, a new paragraph is added. In your version it will insert a `
    ` in that `

    `. Strange behavior... :(

    – pvorb May 30 '13 at 10:02
  • Jeesh, that's odd. Well, I've added another example that seems to work. Only thing is, I had to add hover styles as well to get the focus to work on the paragraphs. Go figure. – ralph.m May 30 '13 at 10:24
  • 1
    I'm sorry, but hover state is not an option for me, since I want to indicate which paragraph you are editing even when you are not using a mouse. Maybe I'll have to work around that `
    ` insertion problem or I could find out the element around the current cursor position via JavaScript.
    – pvorb May 30 '13 at 14:04
  • I updated the question, so JavaScript solutions are generally welcome. – pvorb May 30 '13 at 14:44
3

jsFiddle here.

The following is imperfect in the sense that your mouse will need to be in the same area as the selected paragraph (to keep the :hover argument true), but as this will usually be the case anyway this should be fine - it's the best you're going to get if you want to keep your markup as it is anyway:

CSS:

div[contenteditable="true"]:focus > p:hover {
   border: 2px solid red;
}

HTML:

<div contenteditable="true">
   <p>The first paragraph</p>
   <p>The second paragraph</p>
</div>

jsFiddle here.

If you're able to change the markup, you can use the following simplified selector:

CSS:

p[contenteditable="true"]:focus {
   border: 2px solid red;
}

HTML:

<div>
   <p contenteditable="true">The first paragraph</p>
   <p contenteditable="true">The second paragraph</p>
</div>
dsgriffin
  • 66,495
  • 17
  • 137
  • 137
  • Hm, it would be nicer not to have inline spans around a paragraph, though—at least from a validation point of view. – ralph.m May 30 '13 at 10:26
  • That second one is what I posted in my first response, but it has the issue that when you press Enter/Return, it creates a new `
    ` instead of a new `

    `, hence my second try.

    – ralph.m May 30 '13 at 13:06
  • @Zenith Well, it does at least in Chrome. In Firefox it creates a `
    ` which is fine.
    – pvorb May 30 '13 at 14:00