Призрак следующий за курсором
Призрак следующий за курсором
HTML
<div id="ghost" class="ghost">
<div class="ghost__head">
<div class="ghost__eyes"></div>
<div class="ghost__mouth"></div>
</div>
<div class="ghost__tail">
<div class="ghost__rip"></div>
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="goo">
<feGaussianBlur
in="SourceGraphic"
stdDeviation="10"
result="ghost-blur" />
<feColorMatrix
in="ghost-blur"
mode="matrix"
values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 16 -7"
result="ghost-gooey" />
</filter>
</defs>
</svg>
SCSS
$bg-color: #161616;
html,
body {
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
background: $bg-color;
pointer-events: none;
}
.ghost {
position: absolute;
z-index: 1;
// filter:url('#goo');
transform-origin: center;
width: 90px;
margin: 20px 0 0 -45px;
&__eyes,
&__mouth {
position: absolute;
z-index: 1;
width: 15px;
height: 15px;
top: 34px;
left: 50%;
transform: translate(-50%);
border-radius: 50px;
background: $bg-color;
margin-left: -20px;
transform-origin: center;
}
&__eyes {
box-shadow: 40px 0 0 $bg-color;
}
&__mouth {
margin: 0;
top: 60px;
transform: scale(0);
border-radius: 20px 20px 12px 12px;
width: 20px;
trasform-origin: center bottom;
overflow: hidden;
}
&__tail {
position: absolute;
z-index: -1;
top: 82px;
height: 55px;
width: 100%;
filter: url(#goo);
&:before {
content: '';
background: #fff;
position: absolute;
bottom: 35px;
left: 0;
height: 100px;
width: 100%;
border-radius: 40px 40px 5px 5px;
}
}
&__rip {
width: 15px;
height: 28px;
background: #fff;
position: absolute;
top: 15px;
left: 0;
box-shadow: -62px 0 0 #fff, -31px 0 0 #fff, 31px 0 0 #fff, 62px 0 0 #fff, 93px 0 0 #fff;
border-radius: 50%;
animation: ghost-rips 1.2s linear infinite;
}
}
@keyframes ghost-rips {
0% {
left: 0;
top: 12px;
}
50%{
left: 31px;
top: 20px;
}
100%{
left: 62px;
top: 12px;
}
}
JS
/*--------------------
Get Mouse
--------------------*/
let mouse = { x: window.innerWidth / 2, y: window.innerHeight / 2, dir: '' };
let clicked = false;
const getMouse = (e) => {
mouse = {
x: e.clientX || e.pageX || e.touches[0].pageX || 0 || window.innerWidth / 2,
y: e.clientY || e.pageX || e.touches[0].pageY || 0 || window.innerHeight / 2,
dir: (getMouse.x > e.clientX) ? 'left' : 'right'
}
};
['mousemove', 'touchstart', 'touchmove'].forEach(e => {
window.addEventListener(e, getMouse);
});
window.addEventListener('mousedown', (e) => {
e.preventDefault();
clicked = true;
});
window.addEventListener('mouseup', () => {
clicked = false;
});
/*--------------------
Ghost Follow
--------------------*/
class GhostFollow {
constructor (options) {
Object.assign(this, options);
this.el = document.querySelector('#ghost');
this.mouth = document.querySelector('.ghost__mouth');
this.eyes = document.querySelector('.ghost__eyes');
this.pos = {
x: 0,
y: 0
}
}
follow() {
this.distX = mouse.x - this.pos.x;
this.distY = mouse.y - this.pos.y;
this.velX = this.distX / 8;
this.velY = this.distY / 8;
this.pos.x += this.distX / 10;
this.pos.y += this.distY / 10;
this.skewX = map(this.velX, 0, 100, 0, -50);
this.scaleY = map(this.velY, 0, 100, 1, 2.0);
this.scaleEyeX = map(Math.abs(this.velX), 0, 100, 1, 1.2);
this.scaleEyeY = map(Math.abs(this.velX * 2), 0, 100, 1, 0.1);
this.scaleMouth = Math.min(Math.max(map(Math.abs(this.velX * 1.5), 0, 100, 0, 10), map(Math.abs(this.velY * 1.2), 0, 100, 0, 5)), 2);
if (clicked) {
this.scaleEyeY = .4;
this.scaleMouth = -this.scaleMouth;
}
this.el.style.transform = 'translate(' + this.pos.x + 'px, ' + this.pos.y + 'px) scale(.7) skew(' + this.skewX + 'deg) rotate(' + -this.skewX + 'deg) scaleY(' + this.scaleY + ')';
this.eyes.style.transform = 'translateX(-50%) scale(' + this.scaleEyeX + ',' + this.scaleEyeY + ')';
this.mouth.style.transform = 'translate(' + (-this.skewX*.5-10) + 'px) scale(' + this.scaleMouth + ')';
}
}
/*--------------------
Map
--------------------*/
function map (num, in_min, in_max, out_min, out_max) {
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/*--------------------
Init
--------------------*/
const cursor = new GhostFollow();
/*--------------------
Render
--------------------*/
const render = () => {
requestAnimationFrame(render);
cursor.follow();
}
render();