Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invert text color on mouse hover

I'd like to invert a black text while hovering it with a custom -black- cursor. This GIF demonstrates the effect:

Couldn't wrap my head around to make this work with CSS & JS. Some combination with mix-blend-modes, clipping masks, pseudo elements and filters I guess.

The following code makes the cursor white but doesn't alow the black text to be turned white. Sounds abstract? Here's a demo.

// All creds go to https://murmure.me 👀

const cursor = $(".cursor"),
      body = $("body"),
      slider = $(".slider"),
      nav = $(".nav"),
      fail = $(".fail"),
      invert = $(".invert"),
      nav_open_menu = $(".navopen"),
      wwidth = $(window).width(),
      wheight = $(window).height(),
             
cursorMove = function() {
  var e, n;
  return (
    body.addClass("cursor-on"),
    cursor.css({
      transform: "matrix(1, 0, 0, 1, " + wwidth / 2 + ", " + wheight / 2 + ")"
    }),
    (e = wheight / 2),
    (n = 0.65 * wwidth / 2),
    n > e ? e : n,
    $(window).on("mousemove", function(e) {
      var n, t;
      if (
        ((window.x = e.clientX),
        (window.y = e.clientY),
        cursor.css({
          transform: "matrix(1, 0, 0, 1, " + x + ", " + y + ")"
        }),
        !nav.hasClass("overlay-visible"))
      )
        return (
          (n = Math.floor((x - 60) / 5)),
          (t = Math.floor((y - 60) / 5)),
          n < 20 && t < 20
            ? nav_open_menu.addClass("magnetize").css({
                transform: "scale(1.3) translate3d(" + n + "px, " + t + "px, 0)"
              })
            : nav_open_menu.removeClass("magnetize").attr("style", "")
        );
    })
  );
};
cursorBind = function() {
    var e, n, t;
    if (
      ((n = cursor.find("span")).removeClass("link external new"),
      (e = $(".focus")),
      (t = $(".slack")),
      $(window).on({
        mouseenter: function() {
          return n.removeClass("off");
        },
        mouseleave: function() {
          return n.addClass("off");
        }
      }),
      $("a, button, .cursor-link, .sort-listing th").on({
        mouseenter: function() {
          var e;
          return (
            (e = $(this).hasClass("external") ? "link external" : "link"),
            n.addClass(e)
          );
        },
        mouseleave: function() {
          return n.removeClass("link external");
        }
      }),
      $("h1").on({
        mouseenter: function() {
          var e;
          return (
            (e = $(this).hasClass("external") ? "invert external" : "invert"),
            n.addClass(e)
          );
        },
        mouseleave: function() {
          return n.removeClass("invert external");
        }
      }),
      e.length &&
        e.find("a").on({
          mouseenter: function() {
            return n.addClass("new");
          },
          mouseleave: function() {
            return n.removeClass("new");
          }
        }),
      slider.length &&
        slider.on({
          mouseenter: function() {
            var e;
            return (
              (e = $(this).hasClass("full") ? "click" : "drag"), n.addClass(e)
            );
          },
          mouseleave: function() {
            return n.removeClass("drag click");
          }
        }),
      t.length &&
        t.on({
          mouseenter: function() {
            return n.addClass("light");
          },
          mouseleave: function() {
            return n.removeClass("light");
          }
        }),
      fail.length)
    )
      return fail.on({
        mouseover: function() {
          return n.addClass("relol");
        },
        mouseleave: function() {
          return n.removeClass("relol");
        }
      });
  };

cursorMove();
cursorBind();
body {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

h1 {
  font-weight: 500;
  font-size: 9vw;
}
 

*, *:before, *:after {
	 box-sizing: border-box;
}
 body {
	 min-height: 100vh;
	 background-color: #fff;
	 color: #000;
	 text-rendering: optimizelegibility;
	 line-height: 1.5;
	/* System stack font-family mentioned by @wooorm */
	 font-family: Roboto, -apple-system, BlinkMacSystemFont, Helvetica Neue, Segoe UI, Oxygen, Ubuntu, Cantarell, Open Sans, sans-serif;
}
 .cursor-on {
	 cursor: none;
}
 .cursor-on * {
	 cursor: none;
}
 .cursor-on .cursor {
	 position: fixed;
	 z-index: 10;
	 pointer-events: none;
	 top: -1rem;
	 left: -1rem;
}
 .cursor-on .cursor > span {
	 display: block;
	 width: 2rem;
	 height: 2rem;
	 border-radius: 2rem;
	 background: #f8e71c;
	 transition: transform 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);
	 transform: scale(1);
}
 .cursor-on .cursor > span.off {
	 transform: scale(0);
}
 .cursor-on .cursor > span.link {
	 transform: scale(1.5);
	 background-color: #0000ed;
}
 .cursor-on .cursor > span.link.external:after {
	 content: "↗";
	 display: block;
	 white-space: pre;
	 color: #fff;
	 font-weight: 100;
	 font-size: 1rem;
	 text-align: center;
	 width: 100%;
	 line-height: 1;
	 padding-top: 0.6em;
}
 .cursor-on .cursor > span.light {
	 background: #fff;
}
 .cursor-on .cursor > span.drag, .cursor-on .cursor > span.click, .cursor-on .cursor > span.relol, .cursor-on .cursor > span.new {
	 transform: scale(2.5);
}
 .cursor-on .cursor > span.drag:after, .cursor-on .cursor > span.click:after, .cursor-on .cursor > span.relol:after, .cursor-on .cursor > span.new:after {
	 display: block;
	 content: "";
	 white-space: pre;
	 color: #fff;
	 font-size: 5px;
	 text-align: center;
	 width: 100%;
	 line-height: 1;
	 padding-top: calc((2rem / 2) - 2.5px);
	 color: #000;
}
 .cursor-on .cursor > span.drag:after {
	 content: "play";
}
 .cursor-on .cursor > span.click:after {
	 content: "click\A click";
}
 .cursor-on .cursor > span.relol:after {
	 content: "click\A me";
	 padding-top: calc((2rem / 2) - 5px);
}
 .cursor-on .cursor > span.new:after {
	 content: "new\A new";
}
 .cursor-on:active .cursor > span {
	 transform: scale(0.75);
}
 .cursor-on:active .cursor > span.link {
	 transform: scale(1);
}
 .cursor-on:active .cursor > span.drag, .cursor-on:active .cursor > span.click, .cursor-on:active .cursor > span.relol, .cursor-on:active .cursor > span.new {
	 transform: scale(2);
}
 .grid {
	 background-color: #fff;
}
 .grid--column--item {
	 background-color: #000;
}
 .grid--column.sticky .grid--column--item {
	 border-color: #000;
}


.cursor-on .cursor > span {
	 background: black;
}
 .cursor-on .cursor > span.invert {
	 transform: scale(2.5);
	 mix-blend-mode: difference;
	 filter: invert(1) grayscale(1) contrast(2);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 class="invert">Work</h1>


<div class="cursor" >
  <span class="off"></span>
 </div>
mix-blend-mode: difference;
filter: invert(1) grayscale(1) contrast(2);

I've setup a playground on Codepen to mess around with, but didn't found a solution yet.

How could this hover effect be recreated with CSS and Javascript?

like image 915
danoszz Avatar asked Apr 12 '18 09:04

danoszz


2 Answers

Here is an idea using clip-path. The trick is to duplicate the text to have two layers above each other with different text color then I reveal the top one using the clip-path that I adjust with the move of the mouse.

var h =document.querySelector('h1');
var p= h.getBoundingClientRect();
var c= document.querySelector('.cursor');

document.body.onmousemove = function(e) {
  /*Adjust the cursor position*/
  c.style.left=e.clientX+'px';
  c.style.top=e.clientY+'px';
  /*Adjust the clip-path*/
  h.style.setProperty('--x',(e.clientX-p.top)+'px');
  h.style.setProperty('--y',(e.clientY-p.left)+'px');
}
body {
  cursor:none;
}
h1 {
  color: #000;
  display:inline-block;
  margin:50px;
  text-align: center;
  position:relative;
}
h1:before {
  position:absolute;
  content:attr(data-text);
  color:#fff;
  background:#000;
  clip-path: circle(20px at var(--x,-100%) var(--y,-100%));
}
.cursor {
  position:fixed;
  width:40px;
  height:40px;
  background:#000;
  border-radius:50%;
  top:0;
  left:0;
  transform:translate(-50%,-50%);
  z-index:-2;
}
<h1 data-text="WORK">WORK</h1>

<span class="cursor"></span>

Here is Another idea using radial-gradient and without duplicating the text that can work with multiple elements at the same time:

document.body.onmousemove = function(e) {
  document.documentElement.style.setProperty('--x',(e.clientX)+'px');
  document.documentElement.style.setProperty('--y',(e.clientY)+'px');
}
body {
  cursor:none;
}

.mask {
  background: 
    radial-gradient(circle 20px 
                    at var(--x,0) var(--y,0), 
                    #fff 99%,black 100%) 
                    fixed;
  background-clip: text;
  -webkit-background-clip: text;
  color:transparent;
  -webkit-text-fill-color: transparent;
}
html::before {
  content:"";
  position:fixed;
  width:40px;
  height:40px;
  background:#000;
  border-radius:50%;
  top:var(--y,0);
  left:var(--x,0);
  transform:translate(-50%,-50%);
  z-index:-2;
}
<h1 class="mask">WORK</h1>
<p class="mask">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse quis risus sapien. Maecenas dui orci, blandit et commodo eget, egestas quis odio. Donec eu tortor turpis. Aliquam convallis et nisi ut varius. Proin sapien erat, auctor in efficitur vel, efficitur sit amet justo. In pretium iaculis tempus. Vivamus congue</p>

<p class="mask">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse quis risus sapien. Maecenas dui orci, blandit et commodo eget, egestas quis odio. Donec eu tortor turpis. Aliquam convallis et nisi ut varius. Proin sapien erat, auctor in efficitur vel, efficitur sit amet justo. In pretium iaculis tempus. Vivamus congue</p>

Related question with similar ideas: Stacking circles produces a black bar on border radius

like image 96
Temani Afif Avatar answered Oct 12 '22 11:10

Temani Afif


Made a solution using mix-blend-mode: difference; for the .cursor.

The advantage is - we don't need to add any classes or javascript to all elements that we are hovering with our circle-mouse over.

The disadvantage is - this solution less stable due to mix-blend-mode is a bit raw technology. It requires to set height in body and html, and strict background-color for body.

The part with .cursor CSS and JavaScript - I partly borrowed form the solution of @Temani Afif. Thank you and hope you don't mind, because it's pointless to try to write it better. But i added +window.scrollX and +window.scrollY for correct .cursor work while scrolling.

More about mix-blend-mode you cay read here https://caniuse.com/#search=mix-blend-mode and https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode

document.body.onmousemove = function(e) {
  document.documentElement.style.setProperty('--x', (e.clientX+window.scrollX) + 'px');
  document.documentElement.style.setProperty('--y', (e.clientY+window.scrollY) + 'px');
}
html {
  height: 100%; /* requires for stable body height */
}

body {
  min-height: 100%; /* requires for 'mix-blend-mode' */
  cursor: none;
  color: #000;
  background-color: #fff; /* requires for 'mix-blend-mode' */
}

.cursor {
  position: absolute;
  width: 40px;
  height: 40px;
  background: #fff;
  border-radius: 50%;
  top: var(--y, 0);
  left: var(--x, 0);
  transform: translate(-50%, -50%);
  z-index: 2;
  mix-blend-mode: difference;
}
<h1>WORK</h1>
<span class="cursor"></span>
like image 30
focus.style Avatar answered Oct 12 '22 10:10

focus.style