Floating infinitely an element with JS - javascript

Quick trouble, I wanna move infinitely from right to left an element (at this moment trying with a mouseover event).
function cucu2 () {
document.getElementById('grande').style.cssFloat = 'right';
document.getElementById('grande').addEventListener('mouseover', cucu, true);
}
function cucu () {
document.getElementById('grande').style.cssFloat = 'left';
document.getElementById('grande').addEventListener('mouseover', cucu2);
}
window.addEventListener('load', cucu);
It works perfectly on the right side, as soon as I mouseover near it moves the element to the left, however, on that side it just works ok the first time, the 2nd one seems to be delayed and actually takes 2-3 seconds with mouse over it to take effect and move the element to the left again. I'm guessing its something about the useCapture value of the listener, though if I set both listeners on either true or false the element moves just one time left, one time right and game over.

The reason is on each mouse over you attach the listeners again don't remove them, by the second time you mouseover both cucu2 and cucu get called keeping #grande at float: right.
A quick way to fix it is remove the calling listeners after running it:
function cucu2 () {
document.getElementById('grande').style.cssFloat = 'right';
document.getElementById('grande').addEventListener('mouseover', cucu, true);
document.getElementById('grande').removeEventListener('mouseover', cucu2);
}
function cucu () {
document.getElementById('grande').style.cssFloat = 'left';
document.getElementById('grande').addEventListener('mouseover', cucu2);
document.getElementById('grande').removeEventListener('mouseover', cucu);
}
window.addEventListener('load', cucu);
#grande {
background-color: yellow;
width: 100px;
height: 100px;
}
<div id="grande"></div>
A better way is to attach an event that will switch between float: left float: right:
window.addEventListener('load', function () {
var element = document.getElementById('grande');
element.addEventListener('mouseover', function() {
//if element is floated left we float it right
element.style.cssFloat = element.style.cssFloat == 'right' ? 'left' : 'right';
});
});
#grande {
background-color: yellow;
width: 100px;
height: 100px;
}
<div id="grande"></div>

Related

How to move an HTML elment with the mouse inside parent but drag/drop outside of parent?

I'm fiddling with drag&drop in HTML and Javascript, setting the draggable attribute of elements and implementing dragstart, dragover and drop events to be able to drag and drop elements into a "drop field" and to drag & drop them out again.
That part works for me.
I now want to be able to move those elements using a similar gesture: press the mouse button over the element I want to move, move the mouse and release the button again, without having to press some modifier like CTRL.
Such a behavior can be implemented by handling mousedown/mousemove and mouseup events as described here.
But what if I want to combine them? To me it looks like dragging an element out of a field when moving it should also be possible, somehow get into each others way. However the workflow still seems valid: just register both events, pretend you just want to move until you leave the parent and then decide to either handle the drop event and return the element to it's original position or have it moved.
My first naive approach would be to just implement both (drag and drop and mouse-move) and somehow make sure, positions and event handling don't interfere.
Another approach would be to forget about the mouse events and stick to drag&drop instead which had to be configured to provide seamless moving.
Since I expect my resulting code to become quite dirty I was hoping for some more sophisticated approach to exist for a hybrid drag&drop and move behavior.
Can you give me a hint? how would you do this?
Here is some current state which allows creating a new element via drag&drop and move it around. As you can see I had to deactivate draggable for the mouse-events to work.
<!DOCTYPE html>
<html>
<head><style>
body, html, div, figure {
margin: 0; padding: 0;
background-color: grey;
position: absolute;
text-align: center;
}
.fullsize {
background-color: rgb(200, 250, 250);
width: 15cm; height: 15cm;
}
.dragZone {
background-color: rgb(200, 250, 200);
width: 3cm; height: 3cm;
border-style: solid;
border-width: 2px;
}
#source {
background-color: rgb(200, 200, 250);
left: 17cm; top: 2cm;
}
</style></head>
<body>
<div class="dragZone" id="source" draggable=true>drag me</div>
<div class="fullsize" id="target_area">target</div>
</body>
<script>
(function() {
const target_area = document.getElementById("target_area");
target_area.addEventListener("drop", (event) => {
const relpos = JSON.parse(event.dataTransfer.getData("relpos") || "null");
if (!relpos) return;
const new_element = document.createElement("div");
new_element.setAttribute("class", "dragZone");
new_element.draggable = true;
new_element.style.left = `${event.offsetX - relpos[0]}px`;
new_element.style.top = `${event.offsetY - relpos[1]}px`;
new_element.innerHTML = "drag&drop or move me";
var isDown = false;
new_element.addEventListener('mousedown', (e) => {
console.log(`mouse down ${e}`);
isDown = true;
e.srcElement.draggable=false;
}, true);
new_element.addEventListener('mouseup', (e) => {
console.log(`mouse up ${e}`);
isDown = false;
e.srcElement.draggable=true;
}, true);
new_element.addEventListener('mousemove', (e) => {
e.preventDefault();
if (!isDown) return;
const elem = e.srcElement;
const rect = elem.getBoundingClientRect();
elem.style.left = `${rect.x + e.movementX}px`;
elem.style.top = `${rect.y + e.movementY}px`;
}, true);
target_area.appendChild(new_element);
});
target_area.addEventListener("dragover", (event) => {
event.preventDefault();
});
document.getElementById("source").addEventListener("dragstart", (event) => {
event.stopPropagation();
event.dataTransfer.setData("relpos", JSON.stringify([event.offsetX, event.offsetY]));
});
})();
</script>
</html>
Found it - instead implementing your own movement based on mouse events and fiddling with the drag/drop events you can just use the drag&drop mechanism for both.
To make it work you have to deactivate pointer-events for the dragged item to avoid unwanted dragenter/dragleave events for the parent and turn it back on again afterwards (it has to be activated by default to enable dragging in the first place).
draggable_element.addEventListener("dragstart", (e) => {
e.srcElement.style.pointerEvents = "none";
... // rest of code
});
elem.addEventListener("dragend", (e) => {
e.srcElement.style.pointerEvents = "auto";
... // rest of code
});
Here is a working example: https://jsfiddle.net/03a9s4ur/10/

How do I create a vertical scroll mousewheel function?

I've created this script:
jQuery(".resultlist").mousewheel(function(event, delta) {
this.scrollLeft -= (delta);
event.preventDefault();
});
which fires a horizontal scroll function over the .resultlist container and this is working as expected. I need to disable this on screen widths underneath 545px so I've wrapped it in a resize function.
/* Trigger Resize Script */
jQuery(document).ready(function(){
function resizeForm(){
var width = (window.innerWidth > 0) ? window.innerWidth : document.documentElement.clientWidth;
if(width > 545){
jQuery(".resultlist").mousewheel(function(event, delta) {
this.scrollLeft -= (delta);
event.preventDefault();
});
} else {
// invert
}
}
window.onresize = resizeForm;
resizeForm();
});
/* End Trigger Resize Script */
The problem I have is that the script still runs if else is true, I did start working on a function that would include and then delete a separate script based on screenwidth but this became very cumbersome and is surely not the right way to achieve this.
I need to convert the mousewheel function so that it behaves like a normal vertical scroll instead, so that I can switch between my current horizontal scroll and a normal vertical scroll inside the resize function.
How do I amend the below function to scroll vertically?
jQuery(".resultlist").mousewheel(function(event, delta) {
this.scrollLeft -= (delta);
event.preventDefault();
});
There's a lot of going on in your code. One of the misconcepts is, that it creates a ton of mousewheel listeners on the elements when resizing the window. Here's how you can achieve what you need:
'use strict';
// Allows an event handler to run only when the event has stopped to fire
function debounce(callback, delay = 200) {
let timeout;
return function() {
const context = this,
args = arguments;
window.clearTimeout(timeout);
timeout = window.setTimeout(function() {
callback.apply(context, args);
}, delay);
};
}
jQuery(document).ready(function() {
let width, dirProp;
function resizeForm() {
width = jQuery(window).width();
dirProp = (width > 545) ? 'scrollLeft' : 'scrollTop';
}
function scroll(event) {
event.preventDefault();
// Use the original event object to access WheelEvent properties
this[dirProp] += (event.originalEvent.deltaY);
}
jQuery(".resultlist").on('wheel', scroll);
jQuery(window).on('resize', debounce(resizeForm));
resizeForm();
});
.resultlist {
position: relative;
width: 200px;
height: 200px;
overflow: auto;
}
.list-content {
position: relative;
height: 500px;
width: 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="resultlist">
<div class="list-content">Text</div>
</div>
The code uses standard WheelEvent and a debouncer to prevent the resize event to be handled continuously (resize event fires tens of times per second during resizing). The resize handler updates width and dirProp variables, which then are used in the WheelEvent handler to determine which way to scroll the element (with the bracket notation you can use a variable as a property name).
It's notable, that the strict mode is a must with this code, otherwise the debouncer might actually consume more time than it was purposed to save.
You might want to add some kind of "scroll home" function to reset the scrollable element when the window size is changed. You can test the code at jsFiddle.

How to do eventListener by holding right click using VANILLA JS

I want to display the div wherever the cursor is holding right click.
in my case i have this code
<div class="d-none" id="item"></div>
#item{
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: royalblue;
/* transform: translate(calc(287px - 50%), calc(77px - 50%)); */
}
.d-none{
display: none;
}
var myMouseX, myMouseY;
function getXYPosition(e) {
myMouseX = (e || event).clientX;
myMouseY = (e || event).clientY;
getPosition(myMouseX, myMouseY);
function getPosition(x, y) {
console.log('X = ' + x + '; Y = ' + y);
let div = document.querySelector("#item");
if (div.classList.contains('d-none')) {
div.classList.remove('d-none');
} else {
div.classList.add('d-none');
}
divX = x + "px";
divY = y + "px";
div.style.transform = `translate(calc(`+divX+` - 50%) , calc(`+divY+` - 50%))`;
}
}
window.addEventListener('click', function () {
getXYPosition()
})
or you can see my Fiddle
Its work on left click by default using window.addEventListener('click')
so how do i change from left click to holding right click a few seconds
The MouseEvent API (with its mousedown and mouseup events) lets us check the event.button property to learn which mouse button the user is activating. And we can keep track of how much time passes between mousedown and mouseup to decide what to do when the mouse button is released, such as running a custom showOrHideDiv function.
And the contextmenu event fires after a right-click (unless the relevant context menu is already visible, I guess.) We can suppress the default contextmenu behavior if necessary -- although this power should be used sparingly if at all.
Note that the technique used here is problematic in that it assumes the user will never use their keyboard to see the context menu, which will eventually cause accessibility snafus and other unpleasant surprises for users. This is why hijacking the default right-click behavior should be avoided if possible (maybe in favor of something like Shift + right-click) unless the user explictly opts in to the new behavior.
// Defines constants and adds main (`mousedown`) listener
const
div = document.querySelector("#item"),
RIGHT = 2,
DELAY = 150;
document.addEventListener('mousedown', forceDelay);
// Main listener sets subsequent listeners
function forceDelay(event){
// Right mouse button must be down to proceed
if(event.button != RIGHT){ return; }
// Enables contextmenu and disables custom response
document.removeEventListener('contextmenu', suppressContextMenu);
document.removeEventListener('mouseup', showOrHideDiv);
// After 150ms, disables contextmenu and enables custom response
setTimeout(
function(){
document.addEventListener('contextmenu', suppressContextMenu);
document.addEventListener('mouseup', showOrHideDiv);
},
DELAY
);
}
// The `contextmenu` event listener
function suppressContextMenu(event){
event.preventDefault();
}
// The `mouseup` event listener
function showOrHideDiv(event){
if(event.button != RIGHT){ return; }
const
x = event.clientX,
y = event.clientY;
div.classList.toggle('d-none'); // classList API includes `toggle`
div.style.transform = `translate(calc(${x}px - 50%), calc(${y}px - 50%))`;
}
#item{ position: absolute; top: 0; left: 0; width: 100px; height: 100px; background: royalblue; }
.d-none{ display: none; }
<div id="item" class="d-none"></div>
EDIT
Note: The script works properly when tested in a standalone HTML file using Chrome, but (at least with my laptop's touchpad) behaves strangely when run here in a Stack Overflow snippet. If you experience similar issues, you can paste it into a <script> element in an HTML file (with the CSS in a <style> element) to see it working.

Having a button to control a timeline GSAP dynamic - positioning increment counter

I am trying to control a GSAP animation using a forward & backward button. The expected behaviour I am aiming for is:
When the user hovers over the forward button the animation moves forward (increments in px).
When the user mouseleaves the button then the animation pauses.
When the user hovers over the backwards button the animation moves the other way.
The issue I have encountered is that I have tried to add a dynamic positioning variable in place which increments when the user hovers over the 'forward' button. This is not working as expected - instead it just moves once instead of waiting until the user's mouse leaves to stop.
I tried to add a setInterval to the button event listener to increment the positioning so that when the user hovered over the button it would move px at a time, which did work, but it would not stop causing the browser to crash. I also added a mouseleave to clear the setInterval but I don't think it was good practise.
var masterTimeline = new TimelineMax();
var mousedown = false;
var forwardBTN = document.getElementById("forward");
var backwardBTN = document.getElementById("backward");
var pauseBTN = document.getElementById("pause");
var blueboxElement = document.getElementById("blueBox");
var direction = '+';
var counter = 0;
var distance = 0;
var value1 = direction + '=' + distance;
var tween;
forwardBTN.addEventListener("mouseenter", function(){
// setInterval(function() {
directionMove('moveForward');
// }, 500);
});
backwardBTN.addEventListener("mouseenter", function(){
directionMove('moveBackward')
});
pauseBTN.addEventListener("click", function(){
directionMove('pause');
});
function directionMove(playk) {
if (playk == 'moveBackward') {
var direction = '-';
value1 = direction + '=' + distance; // how to update value
masterTimeline.to(blueboxElement, 0.1, {css: {x: value1}, ease: Linear.easeNone}); // no need move by default
}
else if (playk == 'moveForward') {
var direction = '+';
value1 = direction + '=' + distance; //how to update value
masterTimeline.to(blueboxElement, 0.1, {css: {x: value1}, ease: Linear.easeNone}); // no need move by default
}
else if (playk == 'pause') {
masterTimeline.kill();
console.log("killed");
//
}
}```
The expected behaviour is to move forward incrementally without stopping until the user moves off the forward button but at present it is just moving a single amount one time.
Here is a CodePen link if this helps:
https://codepen.io/nolimit966/pen/pXBeZv?editors=1111
I think you're overcomplicating this a bit (at least in the demo). The entire point of GSAP is moving things along a timeline. What you're trying to do is essentially forcibly use a timeline to work in a non-timeline way, which is why I think you're having trouble.
If you step back and just think about your three requirements, I think it becomes a lot more simple. It's always good to think about it in words like you did because it can help you simplify it and understand it better.
The key steps are to:
Create a timeline for the movement of the box.
Play the timeline forward when the forward button is hovered.
2b. Pause it when it's no longer hovered.
Play the timeline backward when the reverse button is hovered.
3b. Pause it when it's no longer hovered.
In code that looks like this:
var tl = new TimelineMax({paused: true});
var forwardBTN = document.getElementById("forward");
var backwardBTN = document.getElementById("backward");
var pauseBTN = document.getElementById("pause");
var blueboxElement = document.getElementById("blueBox");
tl.to(blueboxElement, 10, {x: window.innerWidth - blueboxElement.clientWidth, ease: Linear.easeNone});
function pause() {
tl.pause();
}
forwardBTN.addEventListener("mouseenter", function() {
tl.play();
});
forwardBTN.addEventListener("mouseleave", pause);
backwardBTN.addEventListener("mouseenter", function() {
tl.reverse();
});
backwardBTN.addEventListener("mouseleave", pause);
Demo
By the way, you're much more likely to get a faster response over on the official GreenSock forums :)
Do you mean something like this?
var tl = new TimelineMax({paused: true});
tl.to('.element', 3, {
x: 800,
});
$(document).on({
mouseenter: function () {
if($(this).hasClass('forward')){
tl.play();
}
else if($(this).hasClass('backwards')){
tl.reverse();
}
},
mouseleave: function () {
tl.pause();
}
}, ".btn");
.wrapper{
width: 100%;
padding: 40px;
}
.element{
width: 100px;
height: 100px;
background: red;
}
.btn{
display: inline-block;
padding: 15px;
background: #bbb;
margin-top: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="element_wrapper">
<div class="element"></div>
</div>
<div class="forward btn">Forward</div>
<div class="backwards btn">Backwards</div>
</div>

How to position an element under the cursor if its parent has "margin: 0 auto"?

I'm building a simple draggable functionality:
<div id="app">
<div id="i-plane">
<div
id="i-interact"
v-bind:style="position"
v-on:mousedown="handleDown"
v-on:mousemove="handleMove"
v-on:mouseout="handleUp">
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
field: {
x: 200,
y: 200
},
dragging: false
},
computed: {
position: function() {
return 'transform: translate(' + this.field.x + 'px,' + this.field.y + 'px)'
}
},
methods: {
handleUp: function(e) {
this.dragging = false
},
handleDown: function(e) {
this.dragging = true
},
handleMove: function(e) {
if (this.dragging) {
this.field.x = e.clientX
this.field.y = e.clientY
}
}
}
});
</script>
<style>
#i-plane {
background: red;
width: 400px;
height: 400px;
margin: 0 auto
}
#i-interact {
background: black;
width: 40px;
height: 40px;
}
</style>
If you click #i-interact the element is not positioned under the cursor but it flies to the right.
How to modify the code so #i-interact moves under the cursor?
Codepen: https://codepen.io/alexcheninfo/pen/XNLNvM
There are a couple of issues here. First, since you're using translate to move the element, you'll need to know its relative position. So, rather than specifying the position on the screen where the mouse is when it's moving, you'll have to specify how many pixels the mouse has moved relative to where it started on mouse down. (The other alternative would be to position the element absolutely, but I'm assuming you want to stick with translate.)
The other issue is that your mouse handlers are all listening to events on the element that you're moving. This will result in very unreliable behavior because if, for example, you move your mouse outside of the element by moving the mouse quickly, your movements will no longer be tracked.
See my fork of your CodePen. I have added an object to know the originating start coordinates and I've made the mouse event listeners document-wide and moved them into created():
created() {
document.addEventListener('mousedown', this.handleDown)
document.addEventListener('mouseup', this.handleUp)
document.addEventListener('mousemove', this.handleMove)
},
This works well enough in your specific example, but it is still not perfect because the start position is based on where you originally click into the element to move it, and also you may just want the mouse events tracked within a given container. To make it better you could upon mouseup actually move the element to the new position and reset the translate to zero, then on the next mousedown you could set the new start position.
As there are a lot of possible routes you could take here and I don't want to just guess at what your requirements are, so I'm providing just a basic working version of your example. Hopefully it suits your needs sufficiently!

Categories