I had to animate the rotation using vanila JS and encountered the problem with Firefox. I used setInterval() to animate each animation step and then clearInteval() to stop the animation. In this example I animated the rotation of an element. It works fine in Chrome, but doesn't quite finish the animation in Firefox, as if Firefox takes longer to process each step. I created an example, demonstrating this behaviour.
const circle = document.getElementById('circle')
const angle = document.getElementById('angle')
const degToMove = 90 //rotate 90 degrees
const animStepLength = 10 // 10ms is one animation step
const animLength = 300 //whole animation length -> 30 animation steps -> 3deg rotation per step
const rotateCircle = () => {
rotInterval = setInterval(()=>{
//what rotation value currently is
let currentVal = circle.style.transform.match(/[-+]?\d+/);
//add 3 deg rotation per step to it
circle.style.transform = `rotate(${+currentVal[0] + (degToMove*animStepLength) / animLength}deg)`
//text output
angle.innerHTML = `${+currentVal[0] + (degToMove*animStepLength) / animLength} deg`
}, animStepLength)
setTimeout(() => {
//after all steps are done clear the interval
clearInterval(rotInterval)
}, animLength);
}
circle.addEventListener('click', rotateCircle)
body{
display: flex;
align-items: center;
justify-content: center;
margin: 0;
height: 100vh;
font-family: sans-serif;
}
#circle{
border-radius: 50%;
background-color: skyblue;
width: 100px;
height: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
<div id="circle" style='transform: rotate(0deg)'>
<span>
Click to rotate
</span>
<span id="angle">0 deg</span>
</div>
Also available as jsfiddle
While Chrome rotates to 90 -> 180 -> 270 -> 360 ...
Firefox goes to 57 -> 114 -> 171 -> 228 -> ... in this particular example.
Well, this is basically +1 rad increase, but it has to do something with the selected values for animLength and animStepLength. If I select them differently, Firefox shows different values.
The simple CSS animation would work here but there are reasons for me to use JS here.
You can never guarantee that a setTimeout or setInterval handler will be called when you ask it to. You should always compare the current time against the initial time to figure out what kind of progress your animation should show, usually by figuring out what percentage of the animation has elapsed. Look for the elapsedPercentage variable in the example below.
Using setInterval is considered bad practice because of that reason. The suggested way to animate is to use nested requestAnimationFrame;
The script below can use lots of improvements but it does show you how to properly update your animation based on how long it's been since the animation started.
const circle = document.getElementById('circle');
const angle = document.getElementById('angle');
const degToMove = 90; //rotate 90 degrees
let rotationStartTime = null;
let targetRotation = 0;
let rotationStart = 0
let animationTime = 3000;
function startRotating() {
rotationStartTime = new Date().getTime();
rotationStart = parseInt(circle.style.transform.match(/[-+]?\d+/)[0]);
targetRotation += degToMove;
rotateCircle();
}
function rotateCircle() {
const currentVal = parseInt(circle.style.transform.match(/[-+]?\d+/)[0]);
const currentTime = new Date().getTime();
const elapsedPercentage = (currentTime - rotationStartTime) / animationTime;
let newVal = Math.min(targetRotation, Math.round(rotationStart + (elapsedPercentage * degToMove)));
circle.style.transform = `rotate(${newVal}deg)`;
//text output
angle.innerHTML = `${newVal} deg`;
if (newVal < targetRotation) {
window.requestAnimationFrame(rotateCircle);
}
}
circle.addEventListener('click', startRotating)
body{
display: flex;
align-items: center;
justify-content: center;
margin: 0;
height: 100vh;
font-family: sans-serif;
}
#circle{
border-radius: 50%;
background-color: skyblue;
width: 100px;
height: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
<div id="circle" style='transform: rotate(0deg)'>
<span>
Click to rotate
</span>
<span id="angle">0 deg</span>
</div>
Related
Intentionally posting this question on stackoverflow instead of posting it on salesforce.stackexchange.com as this problem is specific to JS/CSS
I have below LWC playground where it is not working as expected
https://app.lwc.studio/edit/CTHEQCGrn18rKmQQ0zs1 (unfortunately you might have to login to webcomponents.dev to access this link)
but on the below code pen the same is working as expected not sure what am I doing wrong.
https://codepen.io/gs650x/pen/qByPQKP
I have below HTML
<section class="resizeable-container" >
<div class="resizeable-item">
DIV1
</div>
<div class="resizer-x" onmousedown={handleOnMouseDown} onmouseup={handleOnMouseUp}></div>
<div class="resizeable-item">
DIV2
</div>
</section>
CSS
.resizeable-container {
display: flex;
min-height: 80vh;
}
.resizeable-item {
flex: 50%;
overflow: auto;
}
.resizer-x {
position: relative;
display: flex;
justify-content: center;
align-items: center;
background: hsl(212, 100%, 17%);
padding: 4px;
}
.resizer-x {
z-index: 2;
cursor: col-resize;
}
.resizer-x::before,
.resizer-x::after {
content: "";
width: 2px;
height: 16px;
margin: 2px;
background: lightgray;
}
Below is the javascript
renderedCallback() {
if (!resizer)
resizer = this.template.querySelector(".resizer-x")
//In case mouse up event occurs outside the resizer element
document.addEventListener("mouseup", this.handleOnMouseUp)
}
handleOnMouseDown(event) {
event.preventDefault();
document.addEventListener("mousemove", this.handleOnMouseMove)
document.addEventListener("mouseup", this.handleOnMouseUp)
}
handleOnMouseMove(event) {
event.preventDefault();
const clientX = event.clientX;
const deltaX = clientX - (resizer._clientX || clientX);
resizer._clientX = clientX;
const { previousElementSibling, nextElementSibling } = resizer
// LEFT
if (deltaX < 0) {
const width = Math.round(parseInt(window.getComputedStyle(previousElementSibling).width) + deltaX)
previousElementSibling.style.flex = `0 ${clientX < 10 ? 0 : clientX}px`
nextElementSibling.style.flex = "1 0"
}
// RIGHT
if (deltaX > 0) {
const width = Math.round(parseInt(window.getComputedStyle(nextElementSibling).width) - deltaX)
nextElementSibling.style.flex = `0 ${width < 10 ? 0 : width}px`
previousElementSibling.style.flex = "1 0"
}
}
handleOnMouseUp(event) {
event.preventDefault();
document.removeEventListener("mousemove", this.handleOnMouseMove)
document.removeEventListener("mouseup", this.handleOnMouseUp)
delete event._clientX
}
This works well on my 32 inches monitor but it is not working on my 14 inch laptop
expectation is to move the cursor and resizer-x at the same time but resizer-x moves first and then the cursor moves and mouse up only works if it is coming from the resizer-x anywhere else it is not working. I have added eventListener to document not on a particular object but still removeEventListener to stop handleMouseMove function doesn't stop.
Below is the code pen where it is working as expected but in LWC it doesn't behave in the same manner
https://codepen.io/gs650x/pen/qByPQKP
Let's imagine an example of some content that dynamically inserted upon data fetch from server. The purpose is to display it like a bunch of items in a scroll-snapped container.
The problem is browser somehow randomly tries to scroll to some element but I want the container to stay at the first element no matter how many elements are inserted. The only exception is user that manually scrolls the container.
It works fine unless I add scroll-snap property.
Here is the code snippet
const startingIdx = 3;
const batchSize = 10;
const delay = 500;
const root = document.querySelector('.pills');
const appendBatch = (from) => {
for(let i = 0; i < batchSize; i++){
const element = document.createElement('div');
element.classList.add('pill');
element.innerText = String(from + i);
root.appendChild(element);
}
}
for(let i = 0; i < 3; i++){
setTimeout(() => appendBatch(startingIdx + i*batchSize), i*delay);
}
.pills {
display: flex;
gap: 20px;
width: 100%;
overflow: auto;
scroll-snap-type: x mandatory;
}
.pill {
width: 120px;
height: 40px;
background: rgba(92, 138, 255, 0.15);
display: flex;
align-items: center;
justify-content: center;
border-radius: 20px;
flex-shrink: 0;
scroll-snap-align: start;
}
<main>
<div class="pills">
<div class="pill">1</div>
<div class="pill">2</div>
</div>
</main>
And the demo where you can reproduce it yourself: https://977940.playcode.io/
This is what I'm talking about
Just add that in your script. When page is loaded it scrolls to the left side.
window.addEventListener("DOMContentLoaded", () => {
root.scrollLeft = 0;
});
Seems like adding scroller.scrollBy(0,0); before every batch insertion would save the day as it forces browser to snap to existing element
Source: https://web.dev/snap-after-layout/#interoperability
I have created a marquee slider. After a few iterations the marquee lacks.
I want to have the marquee to have full view-width. As I can see the marquee is lacking less on smaller devices. I have created other similar marquees before with different elements and never had that problem. The empty list elements I have implemented to have a spacing between the text elements, but the icons should be next to the text as they are currently.
(function marquee() {
var marqueeWrapper = $('.js-marquee');
var FPS = (60/100); // 60fps
var SLIDESPEED = 1000; // default | lower is faster
marqueeWrapper.each(function (index, element) {
var _marqueeWrapper = $(element);
var _marqueeTracks = $('>ul', _marqueeWrapper);
var _marqueeSlides = $('>ul>li', _marqueeWrapper);
var _marqueeWidth = parseFloat(_marqueeSlides.last().position().left + _marqueeSlides.last().outerWidth(true));
var shifted = _marqueeWrapper.attr('data-marquee-shift') || false;
var SPEED = (_marqueeWrapper.attr('data-marquee-speed') * _marqueeSlides.length) || (SLIDESPEED * _marqueeSlides.length);
var frames = SPEED * FPS;
var steps = _marqueeWidth / frames; // distance elems will move each frames
var posX = 0;
var tempSteps;
function _clone() {
var times = Math.ceil(_marqueeWrapper.outerWidth(true) / _marqueeWidth) + 1;
_marqueeTracks.each(function () {
$('>ul', _marqueeWrapper).empty();
var sliders = _marqueeSlides;
for (i = 1; i <= times; i++) {
sliders.clone().appendTo(($(this)));
}
})
}
function _animated() {
posX += -steps;
_marqueeTracks.css({
transform: 'translate3d(' + posX + 'px, 0, 0)'
});
if (Math.abs(posX) >= _marqueeWidth) {
posX = 0;
}
window.requestAnimationFrame(_animated);
}
function _pause() {
tempSteps = steps;
return steps = 0;
}
function _resume() {
return steps = tempSteps;
}
function _shiftPosition() {
if(shifted) return posX = -(_marqueeSlides.first().outerWidth(true)) / 2 ;
}
/*
function _registerEvents() {
_marqueeTracks.on('mouseenter', _pause);
_marqueeTracks.on('mouseleave', _resume);
$(window).on('resize', debounce(_clone, 300))
}*/
function init() {
_shiftPosition()
_clone();
_animated();
/*_registerEvents();*/
}
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this,
args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};// debounce
init();
})
})();
.marquee {
background: black;
width: 100%;
overflow: hidden;
}
.marquee__track {
display: flex;
padding: 0;
margin: 0;
list-style: none;
}
.marquee__item {
display: flex;
justify-content: center;
align-items: center;
color: white;
height: 100px;
margin-right: 10px;
padding-bottom: 10px;
}
.marquee__item {
height: 80px;
}
.marquee__item_vegan {
flex: 0 0 120px;
font-size: 50px;
font-family: Gothic821;
}
.marquee__item_gluten {
flex: 0 0 160px;
font-size: 50px;
font-family: Gothic821;
}
.marquee__item_natural {
flex: 0 0 130px;
font-size: 50px;
font-family: Gothic821;
}
.marquee__item_empty {
flex: 0 0 180px;
}
.marquee__item_small {
flex: 0 0 80px;
}
.marquee__item_icon {
width: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div data-marquee-speed="100" data-marquee-shift="false" class="marquee js-marquee">
<ul class="marquee__track">
<li class="marquee__item marquee__item_small"><img class="marquee__item_icon" src="{{ 'Haken_weiss.svg' | asset_url }}"/></li>
<li class="marquee__item marquee__item_vegan">VEGAN</li>
<li class="marquee__item marquee__item_empty"></li>
<li class="marquee__item marquee__item_small"><img class="marquee__item_icon" src="{{ 'Haken_weiss.svg' | asset_url }}"/></li>
<li class="marquee__item marquee__item_gluten">GLUTENFREI</li>
<li class="marquee__item marquee__item_empty"></li>
<li class="marquee__item marquee__item_small"><img class="marquee__item_icon" src="{{ 'Haken_weiss.svg' | asset_url }}"/></li>
<li class="marquee__item marquee__item_natural">NATURAL</li>
<li class="marquee__item marquee__item_empty"></li>
</ul>
</div>
The given code seems rather complex for a simple marquee.
While you will need to invoke a little JS at the start (on a load or a resize) to make sure the timing is right for the speed required (it varies depending on viewport width) there seems little to be gained from using JS for the actual animation.
And there may be some loss in using JS which does not guarantee real-time timing (your system may be busy doing other things) and that can introduce lag. Using a CSS method can help to smooth things as there is less need to move back and forth between CPU and GPU.
The given code also performs the animation in steps, moving position, which can be jerkier than translating.
This snippet is stripped down to demonstrate the basic ideas in using HTML and CSS without JS.
As in the code in the question, a second copy of the items is made. This allows the marquee to appear continuous, as the first item disappears to the left it can be seen coming in from the right.
The units used are related to the viewport width so the whole thing is responsive. A CSS animation is used to get the movement, moving the whole 50% of its width, then when the copy comes in it is overwritten by the first part so the movement looks continuous (this is the same technique as used in the given code but with pure CSS rather than JS/CSS).
.marquee,
.marquee * {
margin: 0;
padding: 0;
border-width: 0;
}
.marquee {
background: black;
overflow: hidden;
}
.marquee>ul {
display: flex;
width: 200vw;
justify-content: space-around;
list-style: none;
animation: move 5s linear infinite;
white-space: nowrap;
}
.marquee>ul li {
font-size: 4vw;
color: white;
margin: 5vw;
position: relative;
}
.marquee__item_vegan,
.marquee__item_gluten,
.marquee__item_natural {
--bg: linear-gradient(white, white);
/* just for demo */
}
.marquee>ul li::before {
content: '';
width: 4vw;
height: 4vw;
left: -4vw;
display: inline-block;
position: absolute;
background-image: var(--bg);
background-repeat: no-repeat;
background-size: 80% 80%;
background-position: bottom left;
}
#keyframes move {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
<div class="marquee">
<ul>
<li class="marquee__item_vegan">VEGAN</li>
<li class="marquee__item_gluten">GLUTENFREI</li>
<li class="marquee__item_natural">NATURAL</li>
<li class="marquee__item_vegan">VEGAN</li>
<li class="marquee__item_gluten">GLUTENFREI</li>
<li class="marquee__item_natural">NATURAL</li>
</ul>
</div>
Notes: you may want to reinstate some JS to calculate the timing - depends on your use case.
The spurious list items have been removed. The imgs which are really just visual clues are added as before pseudo elements. The empty items are not needed. (It's best to keep formatting and content separate, especially for those using assistive technology such as screen readers).
I would like to have my own cursor only displayed over certain images with a certain class.
I've already written a few lines for that.
Here is my code:
function registerCursorHoverEffect() {
if (!isTouch()) {
const el = document.body;
var cursorDiv = document.createElement("div");
cursorDiv.setAttribute("id", "cursor");
cursorDiv.setAttribute("class", "light-spot light-spot--cursor light-spot--center-center light-spot--color-red light-spot--filled light-spot--outside");
el.before(cursorDiv);
document.getElementById("cursor").innerHTML = '<svg class="cursor-main" xmlns="http://www.w3.org/2000/svg" width="70" height="70" viewport="0 0 100 100" style="stroke: white; fill:white;font-size:300px; z-index: 9999999;position: absolute; top: 40px; right: 40px; bottom: 0;"><path d="M59.71,31.29l-10-10a1,1,0,0,0-1.42,1.42L56.59,31H5a1,1,0,0,0,0,2H56.59l-8.3,8.29a1,1,0,0,0,0,1.42,1,1,0,0,0,1.42,0l10-10A1,1,0,0,0,59.71,31.29Z"/></svg>';
var cursorDivAppend = document.createElement("div");
cursorDivAppend.setAttribute("id", "cursor-append");
cursorDivAppend.setAttribute("class", "cursor-append");
el.before(cursorDivAppend);
// Mousemove
$(document).on('mousemove', function (event) {
if($('.col-3:hover').length != 0) {
const appendScale = 1
var destinationX = event.pageX;
var destinationY = event.pageY;
var transformScale = `translate(calc(${destinationX}px - 50%), calc(${destinationY}px - 50%)) scale(${appendScale})`
$('#cursor').css('transform', transformScale);
}
})
}
}
The following screenshot shows what is generated when you hover over an image with a certain class:
The critical part of it all is here:
// Mousemove
$(document).on('mousemove', function (event) {
if($('.col-3:hover').length != 0) {
const appendScale = 1
var destinationX = event.pageX;
var destinationY = event.pageY;
var transformScale = `translate(calc(${destinationX}px - 50%), calc(${destinationY}px - 50%)) scale(${appendScale})`
$('#cursor').css('transform', transformScale);
}
})
event.pageX should always give me the current mouse position of the $(document) and not of the event itself. Exactly the same for event.pageY. But I always get the current position of the event and I don't know why.
Can somebody help me please?
UPDATE:
However, the width of the cursor element hinders me because I get the coordinates of the $ (documents). The event is only triggered when I am outside the cursor.
Here is a screenshot and to clarify the problem:
I would like to have my own cursor only displayed over certain images with a certain class.
You can do that only with css,
.cursors {
display: flex;
justify-content: flex-start;
align-items: stretch;
height: 100vh;
}
.cursors > div {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
box-sizing: border-box;
padding: 10px 2px;
text-align: center;
}
.cursors > div:nth-child(odd) {
background: #eee;
}
.cursors > div:hover {
opacity: 0.25;
}
.png {
cursor: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/9632/heart.png"), auto;
}
.gif {
cursor: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/9632/happy.png"), auto;
}
.rotated {
transform: rotate(45deg); /* Equal to rotateZ(45deg) */
background-color: pink;
}
<div class="cursors">
<div><img class="png rotated"src="https://uxwing.com/wp-content/themes/uxwing/images/patreon_btn.png"/> </div>
<div class="gif">GIF</div>
</div>
I am trying to create a little mouse moving animation. When cursor comes near to the button I call it borderline - Certain distance from the button, the button moves to the cursor direction.
Here I have shown two steps dashed borderline with css just for clarity and understanding.
I have created the nearest borderline from code by calculating the center point of button and decreasing and adding width and height of the button for both x and y axis.
And I want to solve this in the same process I'm working not by adding other event-listener tothe parent-elements of the button.
Here is what I have tried..
const button = document.querySelector(".button");
let { width, height, x: buttonX, y: buttonY } = button.getBoundingClientRect(); // gives you width, height, left-X,top-y of the button
buttonX = buttonX + width / 2; // center point of button on x-axis
buttonY = buttonY + height / 2; // center point of button on y-axis
/*************** Functions ***************/
let distance = width;
let mouseHasEntered = true;
let mouseIsInButtonTerritory;
function mouseMove(e) {
const x = e.x; // current x of cursor
const y = e.y; // current y of cursor
const leftBorderLine = buttonX - distance;
const rightBorderLine = buttonX + distance;
const topBorderLine = buttonY - distance;
const bottomBorderline = buttonY + distance;
const xWalk = (x - buttonX) / 2; // the distance to move the button when mouse moves on X axis
const yWalk = (y - buttonY) / 2; // the distance to move the button when mouse moves on Y axis
mouseIsInButtonTerritory =
x > leftBorderLine &&
x < rightBorderLine &&
y > topBorderLine &&
y < bottomBorderline; // becomes true if mouse is inside all of these border-line
if (mouseIsInButtonTerritory) {
if (mouseHasEntered) {
// this must happen only once to create outside borderline
//creating another level borderline by incresing distance;
// while cursor is returing the button comes out of nearest border-line and return from this borderline
distance = distance + distance;
mouseHasEntered = false;
}
catchCursor(xWalk, yWalk); // call the function when mouse in in the button's territory
} else {
resetPositon();
}
}
function catchCursor(xWalk, yWalk) {
// translates the button in the direction where cursor is.
button.style.transform = `translate(${xWalk}px, ${yWalk}px)`;
}
function resetPositon() {
// resets the postion of the button as it was initial.
button.style.transform = `translate(${0}px, ${0}px)`;
mouseHasEntered = true;
// when button is return to it's position (mouseHasEntered = true) lets to increase the initial borderline of button for the next time
}
/*************** Event-handler ***************/
window.addEventListener("mousemove", mouseMove);
window.addEventListener("mouseout", resetPositon);
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--gutter-lg: 4rem;
--gutter-md: 3rem;
--gutter-sm: 1rem;
--gutter-xm: 1rem;
--color-white: #fff;
--color-black: #000;
}
body {
background: var(--color-black);
font: 16px verdana;
color: var(--color-white);
}
.banner {
display: flex;
height: 100vh;
}
.button {
margin: auto;
cursor: pointer;
transition: all 0.2s ease-out;
}
.button-wrap-wrapper {
width: 192px;
height: 192px;
border: 1px dashed #fff;
margin: auto;
display: flex;
}
.button-wrap {
width: 128px;
height: 128px;
margin: auto;
/* background: orange; */
display: flex;
justify-content: center;
align-items: center;
border: 1px dashed #fff;
}
.button__like-text {
display: block;
color: var(--color-black);
background: var(--color-white);
width: var(--gutter-lg);
height: var(--gutter-lg);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
<section class="banner">
<div class="button-wrap-wrapper">
<div class="button-wrap">
<div class="button">
<span class="button__like-text">
Like
</span>
</div>
</div>
</div>
</section>
What is not working as expected is: mouseIsInButtonTerritory becomes true and I am trying to increase the borderline here
if (mouseHasEntered) {
// this must happen only once to create outside borderline
//creating another level borderline by incresing distance;
// while cursor is returing the button comes out of nearest border-line and return from this borderline
distance = distance + distance;
}
the button keep following the cursor all the time.
What I am trying to solve is if the cursor is coming out of the both borderlines the button must cross the first borderline and come near to the last borderline and go back where it was in initial phase.
I am not getting where I am doing wrong. is there a anything that is missing ?
you should reset the distance when mouse leave. (I'm not sure, just guess this is what you want, since you write mouseHasEntered = true when reset)
since you handle mouse leave yourself (the else mouseIsInButtonTerritory part) do not listen to window.
const button = document.querySelector(".button");
let { width, height, x: buttonX, y: buttonY } = button.getBoundingClientRect(); // gives you width, height, left-X,top-y of the button
buttonX = buttonX + width / 2; // center point of button on x-axis
buttonY = buttonY + height / 2; // center point of button on y-axis
/*************** Functions ***************/
let distance = width;
let mouseHasEntered = true;
let mouseIsInButtonTerritory;
function mouseMove(e) {
const x = e.x; // current x of cursor
const y = e.y; // current y of cursor
const leftBorderLine = buttonX - distance;
const rightBorderLine = buttonX + distance;
const topBorderLine = buttonY - distance;
const bottomBorderline = buttonY + distance;
const xWalk = (x - buttonX) / 2; // the distance to move the button when mouse moves on X axis
const yWalk = (y - buttonY) / 2; // the distance to move the button when mouse moves on Y axis
mouseIsInButtonTerritory =
x > leftBorderLine &&
x < rightBorderLine &&
y > topBorderLine &&
y < bottomBorderline; // becomes true if mouse is inside all of these border-line
if (mouseIsInButtonTerritory) {
if (mouseHasEntered) {
// this must happen only once to create outside borderline
//creating another level borderline by incresing distance;
// while cursor is returing the button comes out of nearest border-line and return from this borderline
distance = distance + distance;
mouseHasEntered = false;
}
catchCursor(xWalk, yWalk); // call the function when mouse in in the button's territory
} else {
resetPositon();
}
}
function catchCursor(xWalk, yWalk) {
// translates the button in the direction where cursor is.
button.style.transform = `translate(${xWalk}px, ${yWalk}px)`;
}
function resetPositon() {
// resets the postion of the button as it was initial.
button.style.transform = `translate(${0}px, ${0}px)`;
if(!mouseHasEntered)distance/=2;
mouseHasEntered = true;
// when button is return to it's position (mouseHasEntered = true) lets to increase the initial borderline of button for the next time
}
/*************** Event-handler ***************/
window.addEventListener("mousemove", mouseMove);
//window.addEventListener("mouseout", resetPositon);
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--gutter-lg: 4rem;
--gutter-md: 3rem;
--gutter-sm: 1rem;
--gutter-xm: 1rem;
--color-white: #fff;
--color-black: #000;
}
body {
background: var(--color-black);
font: 16px verdana;
color: var(--color-white);
}
.banner {
display: flex;
height: 100vh;
}
.button {
margin: auto;
cursor: pointer;
transition: all 0.2s ease-out;
}
.button-wrap-wrapper {
width: 192px;
height: 192px;
border: 1px dashed #fff;
margin: auto;
display: flex;
}
.button-wrap {
width: 128px;
height: 128px;
margin: auto;
/* background: orange; */
display: flex;
justify-content: center;
align-items: center;
border: 1px dashed #fff;
}
.button__like-text {
display: block;
color: var(--color-black);
background: var(--color-white);
width: var(--gutter-lg);
height: var(--gutter-lg);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
<section class="banner">
<div class="button-wrap-wrapper">
<div class="button-wrap">
<div class="button">
<span class="button__like-text">
Like
</span>
</div>
</div>
</div>
</section>
If I understood your intention correctly, I think you need to:
add buttonWrapWrapper selector
const button = document.querySelector(".button");
const buttonWrapWrapper = document.querySelector(".button-wrap-wrapper");
attach the event listeners to buttonWrapWrapper instead of window:
buttonWrapWrapper.addEventListener("mousemove", mouseMove);
buttonWrapWrapper.addEventListener("mouseout", resetPositon);