5

I have a custom cursor image for my website, but only for the default status. In the rest of cases (specially for text) I want the predefined ones.

But if I define the custom cursor this way...

html {
  cursor: url('path/to/custom/cursor.svg') 0 0, default;
}

at least text status is lost (not pointer, but I suspect others have been lost as well) and my paragraphs, spans with text, headers, etc. show now my custom cursor instead of the predefined text selector one.

Of course, I could redefine styles for certain elements...

p, span, ol, ul, h1, h2, h3, h4 {
  cursor: text;
}

but text status is not really linked to certain html tags, it appears when there's a text node not affected by other modifiers. For example, how can I target a div with only text, but exclude a div that contains just another div of certain color, background, etc.?

As I see in this question there's not a way to target text nodes directly, so I want to know if there's a less invasive way to define a custom cursor only in the case where the predefined default one would appear, and still showing all the predefined cursors by every modified state (text, scroll, etc.)

Thank you in advance.

Example 1: if html cursor defined, all is overridden:

html {
  cursor: all-scroll;
}
Lorem ipsum dolor sit amet

Example 2: if we try to redefine some elements, we have now false positives...

html {
  cursor: all-scroll;
}

/* Dillema: Which elements should be redefined to target predefined text status?? */
div {
  cursor: text;
}
<div>Lorem ipsum dolor sit amet</div>
<div style="width: 100px; height: 100px; background-color: red;"></div>
dgnin
  • 1,565
  • 2
  • 20
  • 33
  • can you provide a [minimum working example](https://stackoverflow.com/help/minimal-reproducible-example)? – ACarter Jan 13 '23 at 10:56
  • @ACarter I added a couple of snippets using `all-scroll` as an example instead of a custom image, because this is not about custom images, after all. But I'm not sure if this clarifies the question. – dgnin Jan 13 '23 at 11:25
  • thanks, so you want the cursor only to change when it's above text, correct? – ACarter Jan 13 '23 at 11:28
  • html { height: 100%; } – Diego D Jan 13 '23 at 11:29
  • @ACarter what I really want is that the cursor *only* changes in the situations where the `default` cursor would appear. I don't know which approach would be better: trying to target the "default" cases to define only them, or after that trying to target texts (and scrolls, etc.) to redefine them to their predefined cursors. – dgnin Jan 13 '23 at 11:59

1 Answers1

0

TL;DR;

You can't strictly change the meaning of cursor: default; but you can style any element setting its cursor as desired.

So if you mean to address some particular elements or element types in general, you should select them pinpointing the elements with a fine grained selector (like: .classname{cursor: all-scroll;} or elementtype{cursor: all-scroll;}) but there's no selectors for element having a given property set at a given value.

If you want to get wild you may do in js:

//for every single element in the dom,
document.querySelectorAll('*')
//sets the cursor property to all-scroll if its current value is default
  .foreach(el=>{ if (el.style.cursor === 'default') el.style.cursor = 'pointer'})

to set the style property of every single html element in the document that it will take the max priority.

But to just take them all via css this will be enough *{cursor: pointer;}

If you do html{cursor: all-scroll;} you won't be granted that every single elements will be styled like that because some element types don't inherit the cursor property value from its parent/ancestor and when the mouse will be over it, its style will take the precedence over the underlying html "canvas".

Anyway at a bare minimum you should set the html height at 100% in that case to make sure that that rule at least will trigger when no other element on the top layer where overriding the behaviour.

In my demo I show the 2 strategies at play and I easily demo the fact that form elements don't inherit from the parent ancestor and that when the rule is applied with *, it takes it all.

The whole story:

Here's the mdn docs about the cursor css property:

https://developer.mozilla.org/en-US/docs/Web/CSS/cursor

The cursor CSS property sets the mouse cursor, if any, to show when the mouse pointer is over an element.

The cursor setting should inform users of the mouse operations that can be performed at the current location, including: text selection, activating help or context menus, copying content, resizing tables, and so on. You can specify either the type of cursor using a keyword, or load a specific icon to use (with optional fallback images and mandatory keyword as a final fallback).

It's clear that such property will affect the cursor when the mouse pointer is over the element styled like that. So to affect every elements under the root umbrella it's enough to style the whole <html> element but it's also important to make sure the visible height covers the actual viewport!

That's why it was a solution to use html { height: 100% } for example.

It should be noted that an html document has always a root element and that's <html>. Any text content is always a textNode and anywhere you find it inside the page is always the content of some node and at worst it's a child node in the body element. All I said so far it's maybe ignoring text coming from the content css property (but let's pretend it doesn't exist).

So of course you can't style the text itself but you can style the element containing it, since there's always such an element, therefor there's always a selector you can use to style a given text that for sure will be contained in some element that you can address. The only concern is that you CAN'T limit the styling to the text alone, but to the whole content of the element you are going to style.

Now the only added thing to know is that some elements will have some default values for some css properties that will override any value set on a parent element. That's because of how css specifity works and how some properties get inherited from the ancestors.

I made a demo that includes several html element types.

Plus there are two buttons:

  • Toggle custom cursor on html - That will toggle the css class custom .customcursor on the html element (the rule is predefined in the document as .customcursor{cursor: all-scroll !important;})
  • Toggle custom cursor on * - That will add/remove a css rule that will set the cursor css property to ALL elements in the DOM (*{cursor: all-scroll !important; .. the reason why I behaved this way is to leave to css the responsability to select ALL the elements instead of using js with querySelectorAll)

It's worth pointing out that I used !important to show off that it won't be enough to override the cursor property for those element types that don't inherit from parent.

  • When you'll set the customcursor on html you'll see that mostly all the elements take the customization except the form elements.
  • When you'll set the rule that targets ALL the elements, it will affect EVERYTHING and that cursor will be the only one you'll see while hovering on the whole page viewport.

//create the empty stylesheet on document loaded
var styleSheet;
document.addEventListener('DOMContentLoaded',()=>{
  const styleEl = document.createElement('style');  
  document.head.appendChild(styleEl);  
  styleSheet = styleEl.sheet;
});

function toggleCustomCursorOnBody(btn){  
  const cb = btn.querySelector('input');   
  document.querySelector('html').classList.toggle('customcursor');
  cb.checked = !cb.checked;  
}

function toggleCssRuleOnAllElements(btn){
  const cb = btn.querySelector('input');     
  if(!cb.checked)
    addCssRule();
  else
    removeCssRule();
  cb.checked = !cb.checked;  
}

function addCssRule(){    
  styleSheet.insertRule('*{cursor: all-scroll !important;}', 0);
}

function removeCssRule(){
   styleSheet.deleteRule(0);
}
html {    
  border: solid 18px purple; /*<--this is to show the size of the element we are setting cursor for! */
  background: lightgoldenrodyellow;      
  height: calc(100% - 36px);  /*<--this was what I pointed out in comments */
}

.customcursor{
  cursor: all-scroll !important;
}

/*the following just to give consistence to the page elements*/

[data-label]::before{
  content: attr(data-label);
  padding-right: 1em;
  font-weight: 600;
  padding: 0 .2em;
  font-size: 1rem;
  background-color: #FECE44;
  color: #333;
  width: 100%;
  max-height: 1.2rem;
}

.toggles{
  margin: 0 auto 1em auto;  
}

.toggle{
  cursor: pointer;
  padding: .5em 1em;
}

.toggle > input[type="checkbox"]{
  pointer-events: none;
}

body{
  font-size: 18px;
  text-align: center;
}

*{
  box-sizing: box-model;
  border: dotted 1px lightgray;
}

.container,
form
{  
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  gap: 2vmin;
}

body > .container{
  margin-top: 2vh;
}

.container > h1,
.container > h2,
.container > h3,
.container > h4,
.container > h5,
.container > h6
{
  margin: 0;
  max-height: 10vh;
}

.container > h1{
  background: rgba(204, 204, 204, 1);
}

.container > h2{
  background: rgba(204, 204, 204, 0.9);
}

.container > h3{
  background: rgba(204, 204, 204, 0.8);
}

.container > h4{
  background: rgba(204, 204, 204, 0.7);
}

.container > h5{
  background: rgba(204, 204, 204, 0.6);
}

.container > h6{
  background: rgba(204, 204, 204, 0.5);
}

.container > p{
  background-color: lime;
  font-size: 2rem;
  margin: 0;
}

.container > ol{
  background-color: cyan;
  font-size: 1rem;
  padding: .5em 1em .5em 1.5em;
  margin: 0;
  height: fit-content;
}

.container > a{
  background: antiquewhite;
  font-size: 2rem;
  height: fit-content;
  margin: 0;
}

.container > div:not(.container):not(.unstyled) {  
  width: 20vw;
  height: 5vh;
  
  background: dodgerblue;
  color: white;
  padding: 1em; 
  font-weight: 600;
  font-size: 1.5rem;
  
  display: flex;
  justify-content: center;
  align-items: center;
}

.container > span {
  width: 20vw;
  height: 5vh;
  
  background: cadetblue;
  color: white;
  padding: 1em; 
  font-weight: 600;
  font-size: 1.5rem;
  
  display: flex;
  justify-content: center;
  align-items: center;
}


.container > textarea{  
  width: 15ch;
  height: 10vh;
}

.container > label{
  outline: solid 1px gray;
  padding: .2em 1em;
  background: gray;
  color: white;
  max-height: 1em;
}

.container > select{
  max-height: 2em;
}

.container > input{
}

.container > input[type="text"]{
  width: 15ch;
  max-height: 1em;
  font-size: 1rem;
  padding: .5rem .5rem; 
}

.unstyled input[type="checkbox"]{  
  position: relative;
  width: 2em;
  height: 2em;    
}

.unstyled input[type="checkbox"] + label{  
}
<body>

<div class="toggles">
  <button id="toggle1" class="toggle" onclick="toggleCustomCursorOnBody(this);">
    Toggle custom cursor on &lt;html&gt;
    <input type="checkbox">
  </button>

  <button id="toggle2" class="toggle" onclick="toggleCssRuleOnAllElements(this);">
    Toggle custom cursor on *
    <input type="checkbox">
  </button>
</div>

[THIS IS A TEXT NODE HAVING FONT SIZE FROM BODY]

<div class="container">
    
  <div class="container" data-label="headings">
    <h1>&lt;h1&gt;</h1>
    <h2>&lt;h2&gt;</h2>
    <h3>&lt;h3&gt;</h3>
    <h4>&lt;h4&gt;</h4>
    <h5>&lt;h5&gt;</h5>
    <h6>&lt;h6&gt;</h6>
  </div>  
  
  <div class="container" data-label="contents">
    <p>&lt;p&gt;</p>
    <ol>
      <li>&lt;ol&gt; &lt;li&gt;</li>
      <li>&lt;ol&gt; &lt;li&gt;</li>
      <li>&lt;ol&gt; &lt;li&gt;</li>
    </ol>  
    <a href="#">&lt;a&gt;</a>
  </div>      

  <div class="container" data-label="layout">
    <div>&lt;div&gt;</div>  
    <span>&lt;span&gt;</span>
  </div>
      
  <form class="container" data-label="form">
    <label>&lt;label&gt;</label>
    
    <input type="text" value="<input type=text>">
    
    <textarea>&lt;textarea&gt;</textarea>  
    
    <div class="unstyled">
      <input type="checkbox">    
      <label>&lt;input cb&gt;</label>
    </div>

    <select>
      <option disabled selected>&lt;select&gt;...</option>
      <option value="1">Option1</option>
      <option value="2">Option2</option>
      <option value="3">Option3</option>
    </select>  
    
    <fieldset>
      <legend>&lt;legend&gt;</legend>
      <div>
        <input type="radio" checked>
        <label>&lt;radio&gt;</label>
      </div>
      <div>
        <input type="radio" checked>
        <label>&lt;radio&gt;</label>
      </div>
      <div>
        <input type="radio" checked>
        <label>&lt;radio&gt;</label>
      </div>
    </fieldset>
    
  </form>
  
</div>

</body>
Diego D
  • 6,156
  • 2
  • 17
  • 30