I asked this question: PDF.JS overlay can not be made draggable.
I noticed that PDF.js seemed to be blocking the document.onmousemove and document.onmouseup events from being fired.
I thought the solution would be to use the element's onmousemove and onmouseup however that comes with its problems.
For example, if you move your mouse too fast the element's onmousemove and onmouseup events will stop firing. Same things goes if you try and drag it out of bounds, the element's events will stop firing. You can try for yourself in the snippet below.
// Modified from https://www.w3schools.com/howto/howto_js_draggable.asp
function dragElement(elmnt, _bounds) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
elmnt.onmousedown = dragMouseDown;
let bounds = [..._bounds];
bounds[2] -= +elmnt.style.width.slice(0, -2);
bounds[3] -= +elmnt.style.height.slice(0, -2);
function dragMouseDown(e)
{
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
elmnt.onmouseup = closeDragElement; // Originally document.onmouseup
elmnt.onmousemove = elementDrag; // Originally document.onmousemove
}
function elementDrag(e)
{
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
let yC = elmnt.offsetTop - pos2, xC = elmnt.offsetLeft - pos1;
if (xC < bounds[0]) xC = bounds[0];
else if (xC > bounds[2]) xC = bounds[2];
if (yC < bounds[1]) yC = bounds[1];
else if (yC > bounds[3]) yC = bounds[3];
elmnt.style.top = yC + "px";
elmnt.style.left = xC + "px";
}
function closeDragElement()
{
elmnt.onmouseup = null; // Originally document.onmouseup
elmnt.onmousemove = null; // Originally document.onmousemove
}
}
dragElement(toDrag, [0, 0, 200, 200]);
<div style="
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 200px;
border: 1px solid black
" onmousedown="event.preventDefault()" onmouseup="event.preventDefault()">
<h1 style="text-align:center">My PDF</h1>
<div style="
position: absolute;
left: 150px;
top: 150px;
width: 25px;
height: 25px;
border: 1px solid black;
">
</div>
<div id=toDrag style="
cursor: move;
position: absolute;
left: 10px;
top: 25px;
width: 25px;
height: 25px;
border: 1px solid black;
background-color: #cac;
border-radius: 25px
">
</div>
</div>
My question is there anyway of detecting mousemove and mouseup even if event.preventDefault() has been fired.
My best guess would be something like:
document.querySelectorAll('*').forEach(e => e.addEventListener('mousemove', elementDrag);
document.querySelectorAll('*').forEach(e => e.addEventListener('mouseup', closeDragElement);
However, I worry that this could impact performance especially on mousemove.
DOM event propagation works in three phases: the capture phase, the target phase and the bubble phase. Roughly, the event first works its way down to the target, reaches the target and then works its way back up.
EventTarget.addEventListener() has an optional parameter useCapture (see docs):
useCapture Optional
A boolean value indicating whether events of this type will be
dispatched to the registered listener before being dispatched to any
EventTarget beneath it in the DOM tree. Events that are bubbling
upward through the tree will not trigger a listener designated to use
capture. Event bubbling and capturing are two ways of propagating
events that occur in an element that is nested within another element,
when both elements have registered a handle for that event. The event
propagation mode determines the order in which elements receive the
event. See DOM Level 3 Events and JavaScript Event order for a
detailed explanation. If not specified, useCapture defaults to false.
That being said, you could try something like that to listen event on capture phase, instead of bubble phase:
document.addEventListener('mousemove', elementDrag, true);
document.addEventListener('mouseup', closeDragElement, true);
Related
I am working on Blazor components in Core 3.1. I need to make a custom draggable dialog in blazor using only javscript(without jquery-ui). Can someone help me in achieving this. I tried some fiddles but controls inside them are not working.
This is just a sample you can modify according to your requirements.
You can write this CSS in site.css or in the style tag
<style>
#mydiv {
position: absolute;
z-index: 9;
background-color: #f1f1f1;
text-align: center;
border: 1px solid #d3d3d3;
}
#mydivheader {
padding: 10px;
cursor: move;
z-index: 10;
background-color: #2196F3;
color: #fff;
}
</style>
In your blazor component
#page "/"
#inject IJSRuntime JsRuntime;
<h1>Draggable DIV Element</h1>
<div id="mydiv">
<div id="mydivheader">Header</div>
<p>This is only one sample</p>
</div>
#code {
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
JsRuntime.InvokeVoidAsync("blazorjs.dragable");
return base.OnAfterRenderAsync(firstRender);
}
}
You write this any JavaScript (.js) file or in the script tag
<script>
window.blazorjs = {
dragable: function () {
dragElement(document.getElementById("mydiv"));
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) {
/* if present, the header is where you move the DIV from:*/
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
} else {
/* otherwise, move the DIV from anywhere inside the DIV:*/
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
/* stop moving when mouse button is released:*/
document.onmouseup = null;
document.onmousemove = null;
}
}
}
}
</script>
You can check code in blazorfiddle, https://blazorfiddle.com/s/uwjknuwx
I just copied from w3schools and implemented in blazor, https://www.w3schools.com/howto/howto_js_draggable.asp
I'm looking to change opacity and then completely hide div on swipe up on certain threshold, like in the video below or in Photoswipe:
https://www.loom.com/share/29741bdadc7846bfbc747d3870815340
Unfortunately most off libraries only allow to register actual event start end, but not the amount of swiped pixels. How would I get the actual swiped distance and connect it to the swipe event?
Note: You can apply the animations used in this example on other elements like an overlay instead. The technique is the same.
Here is some code to move up an element, fade it out and remove it from display. Note that I only implemented the PointerEvent-api. You should also implement a fallback.
A summary about what is going on:
Detect a pointerdown on the element and allow the pointer to be used outside the element with setPointerCapture().
Detect a pointermove on the element. If the mouse/touch is moved up, also move up the element. ( I also restricted movement to the left, right, bottom, but you don't have to)
Detect a pointerup. After releasePointerCapture() the pointer will once more only be available in the default element and not outside it. Depending on the amount the element has moved up, the element is returned to its original position or animated out.
class SwipeOutBehaviour {
constructor(element) {
this.element = element;
this.dy = null;
this.initial_y = null;
this.animation_frame_state = 'completed';
if( window.PointerEvent ) {
this.element.addEventListener('pointerdown', this.start_drag.bind(this), true);
this.element.addEventListener('pointermove', this.drag.bind(this), true);
this.element.addEventListener('pointerup', this.drag_end.bind(this), true);
} else {
//should use composition instead if you re serious, for this example I only implemented PointerEvent some browsers will use Tpuchevent and MouseEvent
}
}
start_drag( event ){
event.preventDefault();
// only respond to a single touch
if( event.touches && event.touches.length > 1 ) return;
// allow pointerevents outside the target
event.target.setPointerCapture(event.pointerId);
// set initial pos
this.initial_y = ( event.targetTouches ) ? event.targetTouches[0].clientY : event.clientY;
}
drag( event ){
event.preventDefault();
if( this.initial_y === null ) return;
if( this.animation_frame_state === 'pending' ) return;
this.dy = ( event.targetTouches ) ? Math.floor( event.targetTouches[0].clientY - this.initial_y ) : Math.floor( event.clientY - this.initial_y );
if( this.dy > 0 ) return;
this.animation_frame_state = 'pending'
window.requestAnimationFrame( () => {
this.element.style.transform = `translateY(${this.dy}px)`
this.animation_frame_state = 'completed';
});
}
drag_end(event) {
event.preventDefault();
if(event.touches && event.touches.length > 0) return;
event.target.releasePointerCapture(event.pointerId);
if( this.dy < -100 ) {
window.requestAnimationFrame( () => {
this.element.style.transition = 'opacity 500ms, translateY 200ms';
this.element.style.transform = `translateY(-175px)`;
this.element.style.opacity = `0`;
this.animation_frame_state = 'completed';
window.setTimeout( () => {
// set display to none, you could remove it from the DOM instead
this.element.style.display = 'none';
}, 500)
});
} else {
window.requestAnimationFrame( () => {
this.element.style.transform = `translateY(0px)`
this.animation_frame_state = 'completed';
});
}
this.initial_y = null;
}
}
let element = document.getElementById('container');
new SwipeOutBehaviour( element );
#container {
margin: auto;
width: 150px;
height: 150px;
border: 1px solid black;
}
#box-of-doom {
margin: auto;
width: 200px;
height: 200px;
border: 1px solid red;
background: orange;
}
p {
text-align: center;
}
<p>Drag the item in the box of doom<p>
<div id='box-of-doom'>
<p>The box of doom<p>
</div>
<div id='container'>
<img alt='a placeholder' src='https://via.placeholder.com/150' />
</div>
Note: This answer is inspired by this documentation/article from Google about touch events, so you may want to read more there.
With a lot of event listeners and computed properties; I made a quick code pen using W3's draggable function, but added the opacity change myself:
// Make the DIV element draggable:
dragElement(document.getElementById("mydiv"));
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) {
// if present, the header is where you move the DIV from:
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
} else {
// otherwise, move the DIV from anywhere inside the DIV:
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
//change background opacity:
const background = document.getElementById("background");
const bgHeight = background.offsetHeight;
const elmntHeight = elmnt.offsetHeight;
const adjustedBottom = bgHeight - elmntHeight;
const percentage = 1 - elmnt.offsetTop / adjustedBottom;
console.log(percentage)
background.style.opacity = percentage;
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
}
}
body {
margin: 0;
}
#background {
background: black;
width: 100vw;
height: 100vh;
position: absolute;
}
#mydiv {
position: absolute;
z-index: 9;
background-color: #f1f1f1;
border: 1px solid #d3d3d3;
text-align: center;
}
#mydivheader {
padding: 10px;
cursor: move;
z-index: 10;
background-color: #2196F3;
color: #fff;
}
<div id="background"></div>
<!-- Draggable DIV -->
<div id="mydiv">
<!-- Include a header DIV with the same name as the draggable DIV, followed by "header" -->
<div id="mydivheader">Click here to move</div>
<p>Move</p>
<p>this</p>
<p>DIV</p>
</div>
</div>
Far from perfect, but hopefully demonstrates an idea to expand on.
I am facing an issue, where I am dragging a div.
While the ghost element looks good on MacOs(yes both on Chrome and FireFox),it appears to be too transparent,in Windows (yes both on Chrome and FireFox.
I tried multiple approaches but nothing seems to work.
So is it possible to style the ghost element?
Also, I tried to make an image of that element on the go, and use it as ghost dragging image, but the transparency issue still remains.
You can do this by implementing dragging of the element yourself in JavaScript. That way, you can apply a CSS class to the element while it is being dragged, which styles it in any way you wish.
const d = 'dragging';
const container = document.getElementById('container');
const el = document.getElementById('drag');
var cOffX = 0;
var cOffY = 0;
el.addEventListener('mousedown', dragStart);
function dragStart(e) {
e = e || window.event;
e.preventDefault();
cOffX = e.clientX - el.offsetLeft;
cOffY = e.clientY - el.offsetTop;
document.addEventListener('mousemove', dragMove);
document.addEventListener('mouseup', dragEnd);
el.classList.add(d);
container.style.cursor = 'move';
};
function dragMove(e) {
e = e || window.event;
e.preventDefault();
el.style.top = (e.clientY - cOffY).toString() + 'px';
el.style.left = (e.clientX - cOffX).toString() + 'px';
};
function dragEnd(e) {
e = e || window.event;
e.preventDefault();
document.removeEventListener('mousemove', dragMove);
document.removeEventListener('mouseup', dragEnd);
el.classList.remove(d);
container.style.cursor = null;
};
#container {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
}
#drag {
position: absolute;
height: 100px;
width: 100px;
background-color: lime;
border-radius: 0;
transition: background-color 0.25s, border-radius 0.25s;
cursor: move;
}
#drag.dragging {
background-color: navy;
border-radius: 50%;
}
<div id="container">
<div id="drag"></div>
</div>
As others have said, the ghosting is browser-based and can't be changed, so it seems you need your own solution if you want to get around that.
I'm trying to use draggable HTML/JS on my code, but I get an issue when I try to loop over each element.
I don't overcome to convert my code from a single element to a foreach loop.
This is my code:
# CSS CODE
<style>
#mydiv {
position: absolute;
z-index: 9;
background-color: #f1f1f1;
text-align: center;
border: 1px solid #d3d3d3;
}
#mydivheader {
padding: 10px;
cursor: move;
z-index: 10;
background-color: #2196F3;
color: #fff;
}
</style>
# HTML CODE
<div id="mydiv">
<div id="mydivheader">
Firstname Lastname
</div>
</div>
# JAVASCRIPT CODE
<script>
//Make the DIV element draggagle:
dragElement(document.getElementById("mydiv"));
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) {
/* if present, the header is where you move the DIV from:*/
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
} else {
/* otherwise, move the DIV from anywhere inside the DIV:*/
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
/* stop moving when mouse button is released:*/
document.onmouseup = null;
document.onmousemove = null;
}
}
</script>
It works for one element like the example above.
But I would like to loop like this:
# HTML CODE
#foreach (var player in Model.PlayersToMatchs)
{
<div id="mydiv#(player.Player.PlayerID)">
<div id=" mydivheader#(player.Player.PlayerID)">
#player.Player.Firstname #player.Player.Lastname
</div>
</div>
}
I tried to modify my Javascript part by changing the first line to take into account the partial id:
dragElement(document.querySelector('[id^="mydiv"]').id);
But How I can rewrite this part:
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) {
/* if present, the header is where you move the DIV from:*/
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
} else {
/* otherwise, move the DIV from anywhere inside the DIV:*/
elmnt.onmousedown = dragMouseDown;
}
Thank you !
As Heretic Monkey said, querySelector only returns the first element, you need to use querySelectorAll to get all elements and then loop the elements array to call the dragElement function.
var elements = document.querySelectorAll('[id^="mydiv"]');
elements.forEach(element=> {
dragElement(element);
}
);
https://jsfiddle.net/f8L300ug/3/
I'm trying to make a simple drag-to-resize function. See the example above.
If the mouse is released outside the <body>, the mouseup event won't be triggered, in contrast to what this post implies: Responding to the onmousemove event outside of the browser window in IE
jsFiddle's drag handler does a good job in this. If the mouse is released outside the element, then unbind the mousemove event; If the mouse is not released outside the element and moves back in, then keep the mousemove event. How can I achieve this?
Here's my code. jsFiddle's resize functionality is not perfect after well. After playing it for a while, I triggered a bug and my CSS panel is gone, so I can't post my CSS code here...
JS:
var initialX;
var leftPanel = document.getElementById("left");
var rightPanel = document.getElementById("right");
var resizePanels = function(e){
var deltaX = e.clientX - initialX;
var bodyWidth = parseInt(window.getComputedStyle(leftPanel).width);
initialX = e.clientX;
var newWidth = bodyWidth + deltaX;
leftPanel.style.width = newWidth + "px";
rightPanel.style.width = (parseInt(window.getComputedStyle(document.body).width) - newWidth)*0.9 + "px";
}
var bodyUnbindResize = function(){
document.body.removeEventListener("mousemove", resizePanels)
document.body.style.cursor = "default";
}
document.getElementById("dragger").addEventListener("mousedown", function(){
document.body.addEventListener("mousemove", resizePanels)
document.body.addEventListener("mouseup", bodyUnbindResize)
document.body.style.cursor = "col-resize";
});
HTML:
<div id="left" class="grid">
</div>
<div id="dragger" class="grid"></div>
<div id="right" class="grid">
</div>
And please improve my code if you have a better way. This is the first time I use vanilla JS to do this, so this might not be the best way. Thanks!
It doesn't seem to work when you attach the listener to document.body, but it does work if you attach it to window. If the mouse isn't released, even when you go out of the bounds of the window, then the browser will still detect the mouse move event, which means it is also able to detect when the mouse is released. This also means that when you go out of the window's bounds, you'll get a negative value. You can easily fix that.
https://jsfiddle.net/f8L300ug/8/
var initialX;
var leftPanel = document.getElementById("left");
var rightPanel = document.getElementById("right");
var resizePanels = function (e) {
// If the mouse is still down when dragging outside,
// to the left of the window, e.clientX will be negative
// and undesired behavior happens.
// To fix this, simply give it a value of 0 when it is below 0 (out of the window)
var clientX = e.clientX < 0 ? 0 : e.clientX;
var deltaX = clientX - initialX;
var bodyWidth = parseInt(window.getComputedStyle(leftPanel).width, 10); // < It's a good idea to always
var newWidth = bodyWidth + deltaX; // specify a radix for parseInt
initialX = clientX;
leftPanel.style.width = newWidth + "px";
rightPanel.style.width = (parseInt(window.getComputedStyle(document.body).width, 10) - newWidth) * 0.9 + "px";
}; // < It's a good idea to always close your `var` statements with a `;`
var bodyUnbindResize = function () {
window.removeEventListener("mousemove", resizePanels);
document.body.style.cursor = "default";
};
document.getElementById("dragger").addEventListener("mousedown", function () {
// Attach the mouse listeners to the window
window.addEventListener("mousemove", resizePanels);
window.addEventListener("mouseup", bodyUnbindResize);
document.body.style.cursor = "col-resize";
});
.grid{
height: 200px;
user-select:none;
webkit-touch-callout: none;
-webkit-user-select: none;
}
#left{
width: 50px;
border-right: 1px solid #ccc;
float:left;
background: black;
}
#dragger{
width: 5px;
float:left;
background-color:#eee;
cursor: col-resize;
}
#right{
width: 100px;
border-left: 1px solid #ccc;
overflow:hidden;
background: blue;
}
<div id="left" class="grid">
</div>
<div id="dragger" class="grid"></div>
<div id="right" class="grid">
</div>
You can also set a maximum width for your elements, so that they don't overflow the body. window.innerWidth gives you the current maximum width of the window.