最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

css - two :not() selectors on one element are completely ignored on Safari - Stack Overflow

programmeradmin5浏览0评论

Please note that I'm using iOS 17.4, cannot update and have no other device with a somewhat recent Safari available.

Preamble

In a small hobby project I'm working on I have a section containing some text and the title of what you're currently viewing. The title can be edited using a separate, hidden-until-needed, text input field. For the sake of simplicity, and because it was the easiest to implement, the normal title is shown until you hover over it, or you tab into the input field, at which point it will hide the title and show the input field, essentially replacing it.

It might seem easy to swap two elements out on hover using something like display: none. However, that doesn't work at all when I want to focus the element using the tab key when it is not displayed at all. So, here we are...

Theoretical implementation

Only if the parent .switchOnHover isn't hovered AND the children aren't focused or similar, then keep the default, otherwise swap it.

This means that what is true for .onhover needs to be the complete opposite for .nonehover, hence the :not selectors.

There's also the constraint that I won't use a second CSS rule to "undo" the hiding process, as I don't know the previous styles applied. (it's a universal rule that can be used for more than just this one part).

The code

The smallest demonstration of the issue at hand. The supposed-to-be visible element is marked in red.

.switchOnHover:not(:hover):not(:has(:active, :focus, :focus-within)) > .nonehover,
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .onhover {
    background-color: red;
}
<div class="switchOnHover">
    <div class="nonehover">Hello</div>
    <div class="onhover" tabindex="0">World</div>
</div>

Please note that I'm using iOS 17.4, cannot update and have no other device with a somewhat recent Safari available.

Preamble

In a small hobby project I'm working on I have a section containing some text and the title of what you're currently viewing. The title can be edited using a separate, hidden-until-needed, text input field. For the sake of simplicity, and because it was the easiest to implement, the normal title is shown until you hover over it, or you tab into the input field, at which point it will hide the title and show the input field, essentially replacing it.

It might seem easy to swap two elements out on hover using something like display: none. However, that doesn't work at all when I want to focus the element using the tab key when it is not displayed at all. So, here we are...

Theoretical implementation

Only if the parent .switchOnHover isn't hovered AND the children aren't focused or similar, then keep the default, otherwise swap it.

This means that what is true for .onhover needs to be the complete opposite for .nonehover, hence the :not selectors.

There's also the constraint that I won't use a second CSS rule to "undo" the hiding process, as I don't know the previous styles applied. (it's a universal rule that can be used for more than just this one part).

The code

The smallest demonstration of the issue at hand. The supposed-to-be visible element is marked in red.

.switchOnHover:not(:hover):not(:has(:active, :focus, :focus-within)) > .nonehover,
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .onhover {
    background-color: red;
}
<div class="switchOnHover">
    <div class="nonehover">Hello</div>
    <div class="onhover" tabindex="0">World</div>
</div>

There's also a second variation with the same issue. Here the code actually "hides" the element (which is why .onhover and .nonehover are swapped).

.switchOnHover:not(:is(:hover, :has(:active, :focus, :focus-within))) > .onhover,
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .nonehover {
    height: 0;
    width: 0;
    bottom: 100%;
    right: 100%;
    opacity: 0;
    position: absolute;
    user-select: none;
    pointer-events: none;
    overflow: hidden;
}
<div class="switchOnHover">
    <div class="nonehover">Hello</div>
    <div class="onhover" tabindex="0">World</div>
</div>

On Safari, the first code snippet doesn't unmark the first element and has both highlighted; and the second one doesn't swap on hover at all (except using external mouse, and then it flickers), but does swap when using the tab key to focus the other element.

And the most frustrating part is that when the two :not()s are seperated, it does work! Well, you need to both make it active and hovered, but even that doesn't work with the code snippets above this one.

.switchOnHover:not(:hover) > .nonehover {
    outline: solid 6px cyan;
}
.switchOnHover:not(:has(:active, :focus, :focus-within)) > .nonehover {
    background-color: lime;
}
.switchOnHover:is(:hover, :has(:active, :focus, :focus-within)) > .onhover {
    background-color: red;
}
<div class="switchOnHover">
    <div class="nonehover">Hello</div>
    <div class="onhover" tabindex="0">World</div>
</div>

I want to know how to keep it in the shortest form possible while working as expected and (optionally) why WebKit handles this differently. Could anyone help out?

Edit 1: Added second shortest demonstration.

Edit 2: Expanded Preamble, on request, to further explain the situation.

Share Improve this question edited Mar 25 at 0:11 Lopolin asked Mar 23 at 1:33 LopolinLopolin 155 bronze badges 7
  • I’m interested, but not from the point of view of how Safari handles multiple :not selectors. There’s very likely a way to implement the effect you want without using :not at all, but in order to be sure I need to be clearer on what exactly you are trying to implement. Please edit your post to clarify this. – Brett Donald Commented Mar 23 at 7:22
  • I think a very clear, step by step description of what you want to happen on IOS would be helpful as, obviously, test 3 for instance, gives different results on IOS to Windows11/Edge because of the different ways hover comes about.. – A Haworth Commented Mar 23 at 9:33
  • I can see the same problem even with your 3rd snippet (using ‘hover’ on IOS, aka click). – A Haworth Commented Mar 23 at 21:24
  • @BrettDonald I updated the post to include more information. – Lopolin Commented Mar 25 at 0:09
  • @AHaworth the goal of the third one is to visualize what happens when both :not selectors aren't in the same line. You can see in the first snippet that it uses two :not selectors in the same line. – Lopolin Commented Mar 25 at 0:17
 |  Show 2 more comments

1 Answer 1

Reset to default 0

The easiest solution to implement is not to swap elements at all, just swap styles. Use a regular text input which mimics the appearance of a heading, but when it’s hovered or has the focus then it’s styled to look like an input box.

body {
  margin: 0 2em;
}

h1 input {
  --border-width: 2px;
  --padding-left: 0.3em;

  /* make the input inherit font style from its parent */
  font-family: inherit;
  font-weight: inherit;
  font-size: inherit;

  /* set it up to look like a text box, but use a transparent border color so it appears just like a regular heading */
  width: 100%;
  padding-left: var(--padding-left);
  border: var(--border-width) solid transparent;
  border-radius: 0.25em;
  transition: 0.2s;
  outline: none;

  /* shift it to the left so it lines up with a regular heading */
  position: relative;
  left: calc((var(--border-width) + var(--padding-left)) * -1);
}

/* on hover */
h1 input:hover, h1 input:active, h1 input:focus {
  border-color: #888;
  background: #fafafa;
  box-shadow: inset 0 0 5px rgb(0 0 0 / 0.2);
}
<h1>A regular title</h1>
<h1><input type="text" value="An editable title"></h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus. Nam lectus eros, maximus ac magna vel, congue consequat eros. Fusce id pretium diam. Cras sit amet pharetra ante. Sed quis commodo quam, vel facilisis ipsum. Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed. Donec interdum sit amet est non accumsan. Donec non augue feugiat, fermentum nunc non, convallis est. Cras vel ligula nec odio faucibus ultricies. Sed vulputate tortor eget pretium convallis. Cras interdum elit eget mi porta suscipit. Morbi ut velit diam. Etiam finibus eros et efficitur rutrum. Quisque viverra metus ac eleifend imperdiet. Quisque pretium ut purus vitae tempus. Duis varius risus congue velit faucibus, sed interdum purus consectetur.</p>

Or a slightly simpler solution is to use outline instead of border.

body {
  margin: 0 2em;
}

h1 input {
  --padding-left: 0.3em;

  /* make the input inherit font style from its parent */
  font-family: inherit;
  font-weight: inherit;
  font-size: inherit;

  /* set it up to look like a text box, but use a transparent border color so it appears just like a regular heading */
  width: 100%;
  padding-left: var(--padding-left);
  border: 0;
  border-radius: 0.25em;
  transition: 0.2s;

  /* shift it to the left so it lines up with a regular heading */
  position: relative;
  left: calc(var(--padding-left) * -1);
}

/* on hover */
h1 input:hover, h1 input:active, h1 input:focus {
  outline: 2px solid #888;
  background: #fafafa;
  box-shadow: inset 0 0 5px rgb(0 0 0 / 0.2);
}
<h1>A regular title</h1>
<h1><input type="text" value="An editable title"></h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus. Nam lectus eros, maximus ac magna vel, congue consequat eros. Fusce id pretium diam. Cras sit amet pharetra ante. Sed quis commodo quam, vel facilisis ipsum. Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed. Donec interdum sit amet est non accumsan. Donec non augue feugiat, fermentum nunc non, convallis est. Cras vel ligula nec odio faucibus ultricies. Sed vulputate tortor eget pretium convallis. Cras interdum elit eget mi porta suscipit. Morbi ut velit diam. Etiam finibus eros et efficitur rutrum. Quisque viverra metus ac eleifend imperdiet. Quisque pretium ut purus vitae tempus. Duis varius risus congue velit faucibus, sed interdum purus consectetur.</p>

发布评论

评论列表(0)

  1. 暂无评论