<div class="wrapper">
<div class="js_stickBtn button"></div>
<div class="js_stickBtn_low button __low"></div>
</div>
.wrapper{
display:flex;
align-items:center;
justify-content:center;
height:100vh;
}
.button{
width:8rem;
height:8rem;
background-color:red;
border-radius:50%;
margin:0 2rem;
}
.button.__low{
background-color:blue;
}
const durationVal = 0.6;
//タッチデバイス判定
const isTouch = () => {
const touch_event = window.ontouchstart;
const touch_points = navigator.maxTouchPoints;
if (touch_event !== undefined && 0 < touch_points) {
return true;
} else {
return false;
}
};
//rAF制御
class LimitFrameRate {
constructor(framesPerSecond) {
this.interval = Math.floor(1000 / framesPerSecond);
this.previousTime = performance.now();
}
isLimitFrames(timestamp) {
const deltaTime = timestamp - this.previousTime;
const isLimitOver = deltaTime <= this.interval;
if (!isLimitOver) {
this.previousTime = timestamp - (deltaTime % this.interval);
}
return isLimitOver;
}
}
//くっつくボタンのclass
class StickBtn {
constructor(target, easeMove, fps) {
this.mouseX = 0;
this.mouseY = 0;
this.targetOffsetX = 0;
this.targetOffsetY = 0;
this.easeMove = easeMove;
this.limitFrames = new LimitFrameRate(fps);
this.target = target;
this.id = null;
this.stickAnimLoop = function (timestamp) {
if (this.limitFrames.isLimitFrames(timestamp)) {
this.start();
return;
}
console.log('tick');
let xTo = gsap.quickTo(this.target, 'x', {
duration: durationVal,
ease: 'power1.out',
}),
yTo = gsap.quickTo(this.target, 'y', {
duration: durationVal,
ease: 'power1.out',
});
xTo(this.mouseX * this.easeMove);
yTo(this.mouseY * this.easeMove);
this.start();
};
this.start = function () {
this.id = requestAnimationFrame(this.stickAnimLoop.bind(this));
};
this.stop = function () {
cancelAnimationFrame(this.id);
gsap.to(this.target, {
x: 0,
y: 0,
duration: durationVal,
ease: 'back.out(4)',
overwrite: true,
});
};
this.mouseEnter = function (e) {
e.stopPropagation();
this.targetOffsetX = e.target.clientWidth / 2;
this.targetOffsetY = e.target.clientHeight / 2;
this.start();
};
this.mouseEnterInit = this.mouseEnter.bind(this);
this.mouseMove = function (e) {
e.stopPropagation();
this.mouseX = e.offsetX - this.targetOffsetX;
this.mouseY = e.offsetY - this.targetOffsetY;
};
this.mouseMoveInit = this.mouseMove.bind(this);
this.mouseOut = function (e) {
e.stopPropagation();
this.mouseX = 0;
this.mouseY = 0;
this.stop();
};
this.mouseOutInit = this.mouseOut.bind(this);
}
//登録メソッド
add() {
if (!isTouch()) {
this.target.addEventListener('mouseenter', this.mouseEnterInit);
this.target.addEventListener('mousemove', this.mouseMoveInit);
this.target.addEventListener('mouseleave', this.mouseOutInit);
} else {
return;
}
}
//解除メソッド
remove() {
this.target.removeEventListener('mouseenter', this.mouseEnterInit);
this.target.removeEventListener('mousemove', this.mouseMoveInit);
this.target.removeEventListener('mouseleave', this.mouseOutInit);
}
}
//初期化
//インスタンスを格納する配列
let stickBtnInstanceArr = [];
const stickBtnInit = (isAdd) => {
if (isAdd) {
const highTarget = [document.getElementsByClassName('js_stickBtn')];
const lowTarget = [document.getElementsByClassName('js_stickBtn_low')];
if (highTarget.length) {
highTarget.forEach((element) => {
let stickBtn = new StickBtn(element, 1, 60);
stickBtnInstanceArr.push(stickBtn);
stickBtn.add();
});
}
if (lowTarget.length) {
lowTarget.forEach((element) => {
let stickBtn = new StickBtn(element, 0.08, 60);
stickBtnInstanceArr.push(stickBtn);
stickBtn.add();
});
}
} else {
if (stickBtnInstanceArr.length) {
stickBtnInstanceArr.forEach((element) => {
element.remove();
});
stickBtnInstanceArr = [];
}
}
};
stickBtnInit(true);
This Pen doesn't use any external CSS resources.