<!--
Copyright (c) 2018 Ricardo Mendieta
Released under the MIT license
http://opensource.org/licenses/mit-license.php
-->
<div class="original">参考:<q><a href="https://codepen.io/mendieta/pen/WgvENJ">Ink Cursor</a></q></div>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800">
<defs>
<filter id="goo">
<!-- ぼかし -->
<feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur" />
<!-- 透過度に対してコントラストをつける -->
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -15" result="goo" />
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
<div id="cursor" class="Cursor"></div>
/*
Copyright (c) 2018 Ricardo Mendieta
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
.Cursor {
pointer-events: none;
position: fixed;
display: block;
border-radius: 0;
transform-origin: center center;
top: 0;
left: 0;
z-index: 1000;
filter: url("#goo");
color: #000;
span {
position: absolute;
color: #000;
display: block;
width: 30px;
height: 30px;
border-radius: 50%;
transform-origin: center center;
transform: translate(-50%, -50%);
background-color: #000;
}
}
html,body {
height: 100vh;
width: 100vw;
background-color: #fff;
overflow: hidden;
}
svg {
display: none;
}
.original {
font-size: 20px;
}
View Compiled
/**
* Copyright (c) 2018 Ricardo Mendieta
* Released under the MIT license
* http://opensource.org/licenses/mit-license.php
*/
const cursor = document.getElementById('cursor')
const amount = 26 //生成する円の数
const sineDots = Math.floor(amount * 0.4) //蠢かせる円の数
const width = 30 //円の大きさ
const idleTimeout = 100 //カーソルが動いてる状態から静止してうごめく状態になるまでの時間[ms]
let mousePosition = { x: 0, y: 0}
let dots: Dot[] = []
let timeoutID: number
let idle = false
class Dot {
x = 0 // x座標
y = 0 // y座標
private anglespeed = 0.05 //動くスピード(可変)
private element = document.createElement('span')
private lockX = this.x // カーソルが止まった時のx座標
private lockY = this.y // カーソルが止まった時のy座標
private scale: number // 円の大きさ
private range: number // 円が動く範囲
private angleX = 0 // 0 ~ 2π の乱数:(後述)
private angleY = 0 // 0 ~ 2π の乱数:(後述)
constructor(
private index: number, // 円のindex(0 ~ amount)
){
// 円の大きさ(可変)
// indexが大きくなる = 後に生成された円ほど小さくなる
this.scale = 1 - 0.04 * this.index
// 円が動く範囲(可変)
this.range = width / 2 - width / 2 * this.scale + 2
this.element.classList.add(String(this.index))
gsap.set(this.element, { scale: this.scale });
cursor?.appendChild(this.element)
}
//カーソルが止まった時に1度だけ呼び出される
public lock() {
this.lockX = this.x;//とまったときのx座標の位置
this.lockY = this.y;//とまったときのy座標の位置
this.angleX = Math.PI * 2 * Math.random();// (0 ~ 2πの乱数)。カーソルが止まった時に乱数固定
this.angleY = Math.PI * 2 * Math.random();
}
//止まった円の描画
//メインのストーカー(最大幅の円)はカーソルの位置で固定
//それより小さいストーカーが蠢いている
public draw() {
//idle: うごめいているフラグ、 sinDotes: これ以下のインデックスはうごめかせない
if (!idle || this.index <= sineDots) {//うごめいていない(とまっていない) or インデックスがsinDots以下(蠢かせない円) === うごめかせる円もうごめかせない円も描写
gsap.set(this.element, {x: this.x, y: this.y})
} else {//うごめいている(とまっている) and インデックスがsinDots以上(うごめかせる円) == うごめかせるたまだけの描写
this.angleX += this.anglespeed;//うごめく速度
this.angleY += this.anglespeed;//うごめく速度
// this.lockX = 0
this.y = this.lockY + Math.sin(this.angleY) * this.range; //y方向のうごめき
this.x = this.lockX + Math.cos(this.angleX) * this.range; //x方向のうごめき
gsap.set(this.element, { x: this.x, y: this.y });
}
}
}
// 円の生成
const buildDots = () => {
for (let i = 0; i < amount; i++) {
let dot = new Dot(i)
dots.push(dot)
}
}
const onMouseMove = (e: MouseEvent) => {
mousePosition.x = e.clientX - width / 2
mousePosition.y = e.clientY - width / 2
resetIdleTimer()
}
const onTouchMove = (e: TouchEvent) => {
mousePosition.x = e.touches[0].clientX - width / 2
mousePosition.y = e.touches[0].clientY - width / 2
resetIdleTimer()
}
//カーソルが動いてる状態から静止してうごめく状態にする
const startIdleTimer = () => {
timeoutID = setTimeout(goInactive, idleTimeout);//idolTimeout[ms]後に蠢く状態にする
idle = false;//蠢いていない
}
const resetIdleTimer= () => {
clearTimeout(timeoutID);
startIdleTimer();
}
//うごめくきっかけの関数
//カーソルが動いている状態 => 静止してうごめく際に1回だけ呼ばれる
const goInactive = () => {
idle = true;//蠢いている
for (let dot of dots) {
dot.lock();
}
}
// 描画更新
const positionCursor = () => {
let x = mousePosition.x
let y = mousePosition.y
dots.forEach((dot, index, dots) => {
let nextDot = dots[index + 1] || dots[0]
dot.x = x
dot.y = y
dot.draw()
//カーソルが動いている時
// 0.4は可変
if (!idle || index <= sineDots) {
const dx = (nextDot.x - dot.x) * 0.4;
const dy = (nextDot.y - dot.y) * 0.4;
x += dx;
y += dy;
}
})
}
// レンダリング
const render = () => {
positionCursor()
requestAnimationFrame(render)
}
// 初期化
const init = () => {
window.addEventListener('mousemove', onMouseMove)
window.addEventListener('touchmove', onTouchMove)
buildDots()
render()
}
init()
View Compiled
This Pen doesn't use any external CSS resources.