光标颜色 - 不同背景之间的平滑过渡

cursor color - smooth transition between diferent backgrounds

在我创建的网站上,有一个光标需要平滑地改变其颜色。
当它在白色背景上时,光标需要是蓝色 #0059ff(这很重要,我稍后会解释原因),当它在蓝色背景上时,光标需要是白色;并且过渡需要像这样平滑:

为了使用 mix-blend-mode 获得白色,我正在使用 adjust-hue($color, 180)(在 SCSS 中)计算反转颜色并将该颜色应用于光标。

当背景颜色为 #0000ff 时,光标应为 #ffff00.

我已经使用 mix-blend-mode: difference 开始了一个原型,它适用于“原色”(基本上是 #ff0000#ff00ff 等颜色)。
结果:

当我尝试将“主要”蓝色 #0000ff 更改为项目 #0059ff 所需的蓝色时,问题就开始了。反转的颜色计算为 #ffa600 结果是“不满意”,因为我希望光标在某些背景颜色上为白色,而在白色背景上为所述颜色。

计算差异不适用于这种颜色,我不知道如何做到这一点,以便当光标不在白色背景上时,光标变为蓝色(-ish),当它在蓝色背景上时它变成了白色。

到目前为止我的全部代码:
(SCSS 已编译,因此它可以 运行 在 StackSnippet 中)

const bigBall = document.querySelector('.cursor-ball-big');
const smallBall = document.querySelector('.cursor-ball-small');
const allHoverable = document.querySelectorAll('a, .hoverable');

TweenMax.to(bigBall, .3, {fill: 'none'});

allHoverable.forEach(hoverable => {
    hoverable.addEventListener('mouseenter', () => {
        TweenMax.to(bigBall, .3, {scale: 4});
        TweenMax.to(bigBall.querySelector('circle'), .3, {strokeWidth: 1});
    });
    hoverable.addEventListener('mouseleave', () => {
        TweenMax.to(bigBall, .3, {scale: 1});
        TweenMax.to(bigBall.querySelector('circle'), .3, {strokeWidth: 2});
    });
});

document.body.addEventListener('mousemove', e => {
    const {clientX, clientY} = e;

    TweenMax.to(smallBall, .1, {x: clientX - 5, y: clientY - 7});
    TweenMax.to(bigBall, .4, {x: clientX - 15, y: clientY - 17});
});
:root {
  --color1: #0059FF;
  --color2: #FFFFFF;
  --cursor: #ffa600;
}

body {
  height: 100vh;
  cursor: none;
  margin: 0;
  display: flex;
  font-family: monospace;
}

.cursor {
  pointer-events: none;
  mix-blend-mode: difference;
}
.cursor .cursor-ball {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;
}
.cursor .cursor-ball.cursor-ball-small circle {
  fill: var(--cursor);
}
.cursor .cursor-ball.cursor-ball-big circle {
  stroke: var(--cursor);
}

a {
  border-bottom: 2px solid transparent;
  padding: 10px 0;
  margin-top: 25px;
  text-decoration: none;
  display: inline-block;
  cursor: none;
}

.left,
.right {
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.left {
  background-color: var(--color1);
}
.left a {
  border-color: var(--color2);
}
.left h1,
.left p,
.left a {
  color: var(--color2);
}

.right {
  background-color: var(--color2);
}
.right a {
  border-color: var(--color1);
}
.right h1,
.right p,
.right a {
  color: var(--color1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<div class="cursor">
    <div class="cursor-ball cursor-ball-big">
        <svg xmlns="http://www.w3.org/2000/svg" height="30" width="30">
            <circle cx="15" cy="15" r="12" stroke-width="2"/>
        </svg>
    </div>
    <div class="cursor-ball cursor-ball-small">
        <svg height="10" width="10">
            <circle cx="5" cy="5" r="4" stroke-width="0"/>
        </svg>
    </div>
</div>

<div class="left">
    <a href="#" title="Hover me">Hover me</a>
</div>

<div class="right">
    <a href="#" title="Hover me">Hover me</a>
</div>

I have no idea how to make it so that when the cursor is not over the white background then the cursor becomes blue (-ish) and when it's over the blue background it becomes white.

在这种情况下,mix-blend 模式非常有限。当你想要完全不相关的颜色时,就不可能使用它。

但是,我能够使用 clip-path:

达到预期的效果

const allHoverable = document.querySelectorAll('a, .hoverable');
  const sBall = document.querySelector('#ball1 > circle');
  const bBall = document.querySelector('#ball1 > text');
  const sBall2 = document.querySelector('#ball2 > circle');
  const bBall2 = document.querySelector('#ball2 > text');

  allHoverable.forEach(hoverable => {
    hoverable.addEventListener('mouseenter', () => {
      TweenMax.to(bBall, .3, { fontSize: 100, xPercent: -24, yPercent: 12 });
      TweenMax.to(bBall2, .3, { fontSize: 100, xPercent: -24, yPercent: 12 });
    });
    hoverable.addEventListener('mouseleave', () => {
      TweenMax.to(bBall, .3, { fontSize: 50, xPercent: 0, yPercent: 0 });
      TweenMax.to(bBall2, .3, { fontSize: 50, xPercent: 0, yPercent: 0 });
    });
  });

  document.body.addEventListener('mousemove', e => {
    const { clientX, clientY } = e;
    TweenMax.to(sBall, .1, { x: clientX - 5, y: clientY - 7 });
    TweenMax.to(bBall, .4, { x: clientX - 25, y: clientY + 10 });
    TweenMax.to(sBall2, .1, { x: clientX - 5 - window.innerWidth / 2, y: clientY - 7 });
    TweenMax.to(bBall2, .4, { x: clientX - 25 - window.innerWidth / 2, y: clientY + 10 });
  });
:root {
  --color1: #0059FF;
  --color2: #FFFFFF;
  --cursor: #ffa600;
}

body {
  height: 100vh;
  cursor: none;
  margin: 0;
  display: flex;
  font-family: monospace;
  position: relative;
}

a {
  border-bottom: 2px solid transparent;
  padding: 10px 0;
  margin-top: 25px;
  text-decoration: none;
  display: inline-block;
  cursor: none;
}

.left,
.right {
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.left2,
.right2 {
  height: 100%;
  width: 50%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}


/* left */

.left {
  background: var(--color1);
}

.left a {
  border-color: var(--color2);
}

.left h1,
.left p,
.left a {
  color: var(--color2);
}


/* left2 */

.left2 {
  position: absolute;
  top: 0;
  left: 0;
  clip-path: url(#ball1);
  background: var(--color2);
}

.left2 a {
  border-color: var(--color1);
}

.left2 h1,
.left2 p,
.left2 a {
  color: var(--color1);
}


/* right */

.right {
  background-color: var(--color2);
}

.right a {
  border-color: var(--color1);
}

.right h1,
.right p,
.right a {
  color: var(--color1);
}


/* right2 */

.right2 {
  position: absolute;
  top: 0;
  right: 0;
  clip-path: url(#ball2);
  background-color: var(--color1);
}

.right2 a {
  border-color: var(--color2);
}

.right2 h1,
.right2 p,
.right2 a {
  color: var(--color2);
}

.bBall {
  font-family: 'Josefin Slab', serif;
  font-size: 50px;
  font-weight: 1000;
}


/* for debugging delete later */
a {
  font-size: 4rem;
  font-weight: bolder;
  letter-spacing: -2px;

   -moz-user-select: none;
   -khtml-user-select: none;
   -webkit-user-select: none;
   -ms-user-select: none;
   user-select: none;

   -webkit-user-drag: none;
   -khtml-user-drag: none;
   -moz-user-drag: none;
   -o-user-drag: none;
   user-drag: none;
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Josefin+Slab:wght@100&display=swap" rel="stylesheet">

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>

<div class="left">
  <a href="#" title="Hover me">Hob</a>
</div>
<div class="left2">
  <a href="#" title="Hover me">Hob</a>
</div>

<div class="right">
  <a href="#" title="Hover me">Hob</a>
</div>
<div class="right2">
  <a href="#" title="Hover me">Hob</a>
</div>

<svg height="0" width="0">
    <defs>
      <clipPath id="ball1">
        <circle cx="0" cy="0" r="5" stroke-width="0" />
        <text x="0" y="0" class="bBall">O</text>
      </clipPath>

      <clipPath id="ball2">
        <circle cx="0" cy="0" r="5" stroke-width="0" />
        <text x="0" y="0" class="bBall">O</text>
      </clipPath>
    </defs>
  </svg>

我已经在 Chrome 97 on Windows 10 上测试了输出。
如果所需的字体不可用,它的外观如下:


说明

  • 我创建了元素 left2,其尺寸与 left 完全相同。除了颜色是颠倒的。背景为白色,文字为蓝色。
  • left2正好叠加left
  • clip-path#ball1(小球)添加到left2
  • #ball1 剪辑 left2 左边暴露在外,除了下面 left2 的小区域。
  • 对于外环(大球),没有办法有空心夹道。所以我在 svg 中使用了带有完美圆形字母 'O' 的字体。而在悬停时,我只是增加了字体大小以使其变大。
  • 对右侧重复相同的步骤。创建 right2 等等...
  • 剪辑路径是相对于容器的,因此您会同时看到每一侧都有两个光标。因此必须为右侧创建重复的 clipPath ball2 并将其向左移动 50vw。所以它保持在鼠标指针下方。