I have an angular project wherein the three representation is using threeJs. I wish to add a context menu on right-click to the scene. This is what I have so far. I am also receiving an error called 'menu does not exist'.
window.addEventListener('mousedown', (e: MouseEvent) => { this.rightClickContextMenu(e); }, false);
if (document.addEventListener) {
document.addEventListener('contextmenu', function (e) {
e.preventDefault();
}, false);
} else {
(<any>document).attachEvent('oncontextmenu', function () {
window.event.returnValue = false;
});
}
public rightClickContextMenu(e: MouseEvent): void {
var rightclick;
if (!event) var event = window.event;
if ((<any>event).which) rightclick = ((<any>event).which == 3);
else if ((<any>event).button) rightclick = ((<any>event).button == 2);
if (!rightclick) return;
const x = e.clientX - this.rect.left;
const y = e.clientY - this.rect.top;
var intersect;
let scenePointer = new th.Vector2();
scenePointer.x = (x / this.canvas.clientWidth) * 2 - 1;
scenePointer.y = (y / this.canvas.clientHeight) * - 2 + 1;
// Determine the component in-focus using raycasting
this.raycaster.setFromCamera(scenePointer, this.camera);
var intersects = this.raycaster.intersectObjects(this.scene.children);
if (intersects.length) {
intersect = intersects[0].object;
menu.style.left = x + "px";
menu.style.top = y + "px";
menu.style.display = "";
}
else {
intersect = undefined;
}
}
So far, on left-click, the color of the mesh changes but nothing happens on right click. I was wondering how do I add on a context menu to the same on right click. Modifications to my code to highlight the correct one would be much appreciated from you guys. I apologise, I am very new to threejs in case this question sounds trivial.
Stop event propagation on the control where u want this feature and then capture the event and diplay
Related
Is there a way to allow native scrolling easily without heavy JS modifications when you reach the border of a div via custom drag and drop via touchmove listener?
When you drag the text in the div here you'll see the div inside is scrolling automatically
I provided an example with touchmove listeners but this one does not scroll, when you reach a border with your mouse
Is there an easy way to include a scrolling behavior to the 2nd example?
const element = document.body.querySelector('#draggable');
const isInContainer = (x,y) => {
const elements = document.elementsFromPoint(x, y)
return elements.find(el => el && el.classList && el.classList.contains('container')) || false;
}
const onMouseMove = (e) => {
if(isInContainer(e.pageX, e.pageY)){
element.style.top = e.pageY + 'px';
element.style.left = e.pageX + 'px';
}
}
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp)
}
element.addEventListener('mousedown', (e) => {
e.preventDefault();
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp)
});
In case someone has a native better solution I'm willing to accept that one... for the time being this would be my current way to solve the issue.
Note: I made a custom interval for scrolling and don't use the mousemove event so users don't have to move the mouse to trigger it. moving outside will start the interval moving inside will clear it.
// the container that should scroll
const scrollBody = document.getElementById('scrollContainer');
// parameter to check which directions should scroll
const scrollPositions = {
left: false,
right: false,
up: false,
down: false,
}
// how far should be scrolled
const nextScrollDistance = {
x: 0,
y: 0
}
// scroll interval
let scrollInterval= null;
const startScrolling = (scrollBody) => {
if (scrollInterval !== null) {
return true;
}
const intervalCallback = () => {
if (scrollInterval !== null && nextScrollDistance.x === 0 && nextScrollDistance.y === 0) {
window.clearInterval(scrollInterval);
scrollInterval = null;
} else {
scrollBody.scrollLeft += nextScrollDistance.x;
scrollBody.scrollTop += nextScrollDistance.y;
}
}
scrollInterval = window.setInterval(intervalCallback, 50);
}
const onMouseMove = (e) => {
if(isInContainer(e.pageX, e.pageY)){
element.style.top = e.pageY + 'px';
element.style.left = e.pageX + 'px';
}
const rects = scrollBody.getBoundingClientRect();
// check directions
// max x that can be scrolled
const maxX = scrollBody.scrollWidth - scrollBody.clientWidth;
// max y that can be scrolled
const maxY = scrollBody.scrollHeight - scrollBody.clientHeight;
// check all directions if it's even possible to scroll
const canScrollTop = Math.round(scrollBody.scrollTop) > 0;
const canScrollBottom = Math.round(scrollBody.scrollTop) < maxY;
const canScrollLeft = Math.round(scrollBody.scrollLeft) > 0;
const canScrollRight = Math.round(scrollBody.scrollLeft) < maxX;
// current x and y coordinates of the mouse
const x = e.pageX;
const y = e.pageY;
// dynamic value to decrease the speed.. otherwise it might scroll too fast
const minifier = 2;
// the modifiers for scrollTop and scrollLeft
nextScrollDistance.y = 0;
nextScrollDistance.x = 0;
if (canScrollBottom && y > rects.bottom) {
// distance between the right border and the mouse
const distance = Math.abs(y - rects.bottom);
// the next time it scrolls -> scroll distance / minifier
nextScrollDistance.y = Math.round(distance / minifier)
scrollPositions.down = true;
} else {
scrollPositions.down = false;
}
// all other directions...
if (canScrollTop && y < rects.top) {
const distance = Math.abs(y - rects.top);
nextScrollDistance.y = Math.round(distance / minifier) * -1;
scrollPositions.up = true;
} else {
scrollPositions.up = false;
}
if (canScrollRight && x > rects.right) {
const distance = Math.abs(x - rects.right);
nextScrollDistance.x = Math.round(distance / minifier)
scrollPositions.right = true;
} else {
scrollPositions.right = false;
}
if (canScrollLeft && x < rects.left) {
const distance = Math.abs(x - rects.left);
nextScrollDistance.x = Math.round(distance / minifier) * -1;
scrollPositions.left = true;
} else {
scrollPositions.left = false;
}
// in case one of those are set.. trigger scrolling
if (nextScrollDistance.x || nextScrollDistance.y) {
startScrolling();
}
}
I am rolling out a custom drag drop implementation which is a simplified version of the Dragula library. Here are my fiddles:
With Dragula: http://jsfiddle.net/8m4jrfwn/
With my custom drag drop implementation: http://jsfiddle.net/7wLtojs4/1/
The issue I am seeing is that my custom drag and drop implementation is a bit to free-form. I would like for the user to only be able to drop on the top or bottom of another div with the class box. How can I make this possible? Code here:
function handleMouseDown(e) {
window.dragging = {};
dragging.pageX = e.pageX;
dragging.pageY = e.pageY;
dragging.elem = this;
dragging.offset = $(this).offset();
$('body')
.on('mouseup', handleMouseUp)
.on('mousemove', handleDragging);
}
function handleDragging(e) {
let left = dragging.offset.left + (e.pageX - dragging.pageX);
let top = dragging.offset.top + (e.pageY - dragging.pageY);
$(dragging.elem)
.offset({top: top, left: left});
}
function handleMouseUp() {
$('body')
.off('mousemove', handleDragging)
.off('mouseup', handleMouseUp);
}
let boxElement = '.item';
$(boxElement).mousedown(handleMouseDown);
I does not see any logic here about what will happen on mouseup event. Seems you need detect on mouseup new order of elements and then exchange their positions.
For this you should know positions of all of them. On mouseup you can reorder them based on new positions.
In my realization(https://jsfiddle.net/jaromudr/c99oueg5/) I used next logic:
onMove(draggable) {
const sortedDraggables = this.getSortedDraggables()
const pinnedPositions = sortedDraggables.map((draggable) => draggable.pinnedPosition)
const currentIndex = sortedDraggables.indexOf(draggable)
const targetIndex = indexOfNearestPoint(pinnedPositions, draggable.position, this.options.radius, getYDifference)
if (targetIndex !== -1 && currentIndex !== targetIndex) {
arrayMove(sortedDraggables, currentIndex, targetIndex)
this.bubling(sortedDraggables, draggable)
}
}
bubling(sortedDraggables, currentDraggable) {
const currentPosition = this.startPosition.clone()
sortedDraggables.forEach((draggable) => {
if (!draggable.pinnedPosition.compare(currentPosition)) {
if (draggable === currentDraggable && !currentDraggable.nativeDragAndDrop) {
draggable.pinnedPosition = currentPosition.clone()
} else {
draggable.pinPosition(currentPosition, (draggable === currentDraggable) ? 0 : this.options.timeExcange)
}
}
currentPosition.y = currentPosition.y + draggable.getSize().y + this.verticalGap
})
}
where on move we detect if position in list was changed and move other draggables to their new positions.
On drag end we just move currentDraggable to pinnedPosition
I have a function that activates the dragControls, while the "m" key is pressed. For some reason it is not deactivated when the "m" is unpressed. How can I disable the dragControls?
I have tried to encapsulate the dragControls to be activated if the statement is true, else dragControls = null. But whenever the first activation happens, it is not deactivated, even when the statement is false.
While and do while loops are just freezing the browser.
init() {
// EVENT LISTENERS:
map.addEventListener('mousedown', this.movePoi, false);
document.addEventListener('keydown', this.onDocumentKeyDown, false);
document.addEventListener('keyup', this.onDocumentKeyUp, false);
},
// HELPER FUNCTIONS:
onDocumentKeyDown(event) {
let keycode = event.which;
if (keycode === 77) {
this.moveIt = true;
this.controls.enabled = false;
console.log("Key is pressed");
console.log(this.moveIt);
}
},
onDocumentKeyUp(event){
let keycode = event.which;
console.log(keycode);
if (keycode === 77) {
this.moveIt = false;
this.controls.enabled = true;
console.log("Key is unpressed");
console.log(this.moveIt);
}
},
mouseOverScene (event) {
event.preventDefault();
let rect = event.target.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
this.mouse.x = ( x / this.mapWidth) * 2 - 1;
this.mouse.y = - ( y / this.mapHeight ) * 2 + 1;
this.rayCaster.setFromCamera(this.mouse, this.camera);
},
//POI movement around the scene:
movePoi (event) {
event.preventDefault();
let controlsDrag;
if (this.moveIt) {
controlsDrag = new DragControls(this.spheres, this.camera, this.renderer.domElement);
} else {
controlsDrag = null;
}
}
EXPECTED: the objects should be dragged around by the left-click mouse, while the "m" key is pressed (the orbitControls are also disabled when this happens. This part works fine). When the "m" is not pressed, they should return the undraggable state and the orbitControls are enabled again.
ACTUAL: All of the above happens, BUT the objects are still draggable after the "m" is unpressed. The orbitControls are obviously enabled, which brings the whole next level of haywireness happening on the screen.
Not tested but you should try calling the movePoi function at the end of onDocumentKeyUp. At a glance it seems like the verification of whether "m" is pressed or not is only made when the mouse left button is clicked and not when the "m" key is unpresssed. Hope that helps.
So the solution goes as following:
init() {
this.controlsDrag = new DragControls(this.spheres, this.camera, this.renderer.domElement);
this.controlsDrag.deactivate();
// EVENT LISTENERS:
map.addEventListener('mousedown', this.startMovePoi, false);
this.controlsDrag.addEventListener('mouseup', this.stopMovePoi,false);
},
//POI movement around the scene:
startMovePoi () {
let controls = this.controls;
this.controlsDrag.activate();
this.controlsDrag.addEventListener('dragstart', function () {
controls.enabled = false;
});
this.controlsDrag.addEventListener('dragend', function () {
controls.enabled = true;
});
},
stopMovePoi(){
this.controlsDrag.deactivate();
}
Needed some code refactoring before.
I am trying to change the length of two bars (div) by mouse dragging (extending one example in eloquetjavascript book chapter 14, which involves changing the length of one bar by dragging the mouse.) The intended behavior is clicking on any bar, moving the mouse when holding the left mouse key would change the length of that bar.
Here is my implementation (also available on JSfiddle)
<script>
var lastX; // Tracks the last observed mouse X position
var rect1 = document.getElementById("bar1");
var rect2 = document.getElementById("bar2");
rect1.addEventListener("mousedown", function(){watchmousedown(rect1)});
rect2.addEventListener("mousedown", function(){watchmousedown(rect2)});
function watchmousedown(rec) {
if (event.which == 1) {
lastX = event.pageX;
addEventListener("mousemove",function(){moved(rec)});
event.preventDefault(); // Prevent selection
} else {
removeEventListener("mousedown", watchmousedown)}
}
function moved(rec) {
if (event.which != 1) {
removeEventListener("mousemove", moved);
} else {
var dist = event.pageX - lastX;
var newWidth = Math.max(10, rec.offsetWidth + dist);
rec.style.width = newWidth + "px";
lastX = event.pageX;
}
}
</script>
The problem is I can only change the length of the bar where the first mouse click event happened. I assume I didn't handle the mousedown event correctly (probably need a reset some how).
I am new to javascript, help on programming style is also appreciated.
Thanks!
Add rec. to addEventListener("mousemove", function () { so that the event listener is bound to the rec you clicked on instead of to the window.
function watchmousedown(rec) {
if (event.which == 1) {
lastX = event.pageX;
rec.addEventListener("mousemove", function () {
moved(rec)
});
event.preventDefault(); // Prevent selection
} else {
rec.removeEventListener("mousedown", watchmousedown)
}
}
Edit: I there are some event handlers not being cleaned up properly. I don't know if this would be my final code, but this is closer to how I would do it:
var lastX; // Tracks the last observed mouse X position
var rect1 = document.getElementById("bar1");
var rect2 = document.getElementById("bar2");
var moveRect1 = function () {
console.log(arguments);
moved(rect1)
};
var moveRect2 = function() {
console.log(arguments);
moved(rect2);
}
var watchRect1 = function () {
console.log(arguments);
watchmousedown(moveRect1)
};
var watchRect2 = function () {
console.log(arguments);
watchmousedown(moveRect2)
};
rect1.addEventListener("mousedown", watchRect1);
rect2.addEventListener("mousedown", watchRect2);
window.addEventListener("mouseup", function() {
removeEventListener("mousemove", moveRect1);
removeEventListener("mousemove", moveRect2);
});
function watchmousedown(moverec) {
if (event.which == 1) {
lastX = event.pageX;
addEventListener("mousemove", moverec);
event.preventDefault(); // Prevent selection
}
}
function moved(rec) {
if (event.which == 1) {
var dist = event.pageX - lastX;
var newWidth = Math.max(10, rec.offsetWidth + dist);
rec.style.width = newWidth + "px";
lastX = event.pageX;
}
}
Edit: removed a line that didn't do anything
This question already has answers here:
Moveable/draggable <div>
(9 answers)
Closed 5 years ago.
I want to create a movable/draggable div in native javascript without using jquery and libraries. Is there a tutorial or anythign?
OK, here's my personal code that I use for lightweight deployments (projects where using a library is either not allowed or overkill for some reason). First thing first, I always use this convenience function so that I can pass either an id or the actual dom element:
function get (el) {
if (typeof el == 'string') return document.getElementById(el);
return el;
}
As a bonus, get() is shorter to type than document.getElementById() and my code ends up shorter.
Second realize that what most libraries are doing is cross-browser compatibility. If all browsers behave the same the code is fairly trivial. So lets write some cross-browser functions to get mouse position:
function mouseX (e) {
if (e.pageX) {
return e.pageX;
}
if (e.clientX) {
return e.clientX + (document.documentElement.scrollLeft ?
document.documentElement.scrollLeft :
document.body.scrollLeft);
}
return null;
}
function mouseY (e) {
if (e.pageY) {
return e.pageY;
}
if (e.clientY) {
return e.clientY + (document.documentElement.scrollTop ?
document.documentElement.scrollTop :
document.body.scrollTop);
}
return null;
}
OK, the two functions above are identical. There're certainly better ways to write them but I'm keeping it (relatively) simple for now.
Now we can write the drag and drop code. The thing I like about this code is that everything's captured in a single closure so there are no global variables or helper functions littering the browser. Also, the code separates the drag handle from the object being dragged. This is useful for creating dialog boxes etc. But if not needed, you can always assign them the same object. Anyway, here's the code:
function dragable (clickEl,dragEl) {
var p = get(clickEl);
var t = get(dragEl);
var drag = false;
offsetX = 0;
offsetY = 0;
var mousemoveTemp = null;
if (t) {
var move = function (x,y) {
t.style.left = (parseInt(t.style.left)+x) + "px";
t.style.top = (parseInt(t.style.top) +y) + "px";
}
var mouseMoveHandler = function (e) {
e = e || window.event;
if(!drag){return true};
var x = mouseX(e);
var y = mouseY(e);
if (x != offsetX || y != offsetY) {
move(x-offsetX,y-offsetY);
offsetX = x;
offsetY = y;
}
return false;
}
var start_drag = function (e) {
e = e || window.event;
offsetX=mouseX(e);
offsetY=mouseY(e);
drag=true; // basically we're using this to detect dragging
// save any previous mousemove event handler:
if (document.body.onmousemove) {
mousemoveTemp = document.body.onmousemove;
}
document.body.onmousemove = mouseMoveHandler;
return false;
}
var stop_drag = function () {
drag=false;
// restore previous mousemove event handler if necessary:
if (mousemoveTemp) {
document.body.onmousemove = mousemoveTemp;
mousemoveTemp = null;
}
return false;
}
p.onmousedown = start_drag;
p.onmouseup = stop_drag;
}
}
There is a reason for the slightly convoluted offsetX/offsetY calculations. If you notice, it's just taking the difference between mouse positions and adding them back to the position of the div being dragged. Why not just use the mouse positions? Well, if you do that the div will jump to the mouse pointer when you click on it. Which is a behavior I did not want.
You can try this
HTML
<div id="one" style="height:50px; width:50px; border:1px solid #ccc; background:red;">
</div>
Js Script for draggable div
window.onload = function(){
draggable('one');
};
var dragObj = null;
function draggable(id)
{
var obj = document.getElementById(id);
obj.style.position = "absolute";
obj.onmousedown = function(){
dragObj = obj;
}
}
document.onmouseup = function(e){
dragObj = null;
};
document.onmousemove = function(e){
var x = e.pageX;
var y = e.pageY;
if(dragObj == null)
return;
dragObj.style.left = x +"px";
dragObj.style.top= y +"px";
};
Check this Demo
This code corrects the position of the mouse (so the dragged object doesn't jump when you start dragging) and works with touch screens/phones as well
var dragObj = null; //object to be moved
var xOffset = 0; //used to prevent dragged object jumping to mouse location
var yOffset = 0;
window.onload = function()
{
document.getElementById("menuBar").addEventListener("mousedown", startDrag, true);
document.getElementById("menuBar").addEventListener("touchstart", startDrag, true);
document.onmouseup = stopDrag;
document.ontouchend = stopDrag;
}
function startDrag(e)
/*sets offset parameters and starts listening for mouse-move*/
{
e.preventDefault();
e.stopPropagation();
dragObj = e.target;
dragObj.style.position = "absolute";
var rect = dragObj.getBoundingClientRect();
if(e.type=="mousedown")
{
xOffset = e.clientX - rect.left; //clientX and getBoundingClientRect() both use viewable area adjusted when scrolling aka 'viewport'
yOffset = e.clientY - rect.top;
window.addEventListener('mousemove', dragObject, true);
}
else if(e.type=="touchstart")
{
xOffset = e.targetTouches[0].clientX - rect.left; //clientX and getBoundingClientRect() both use viewable area adjusted when scrolling aka 'viewport'
yOffset = e.targetTouches[0].clientY - rect.top;
window.addEventListener('touchmove', dragObject, true);
}
}
function dragObject(e)
/*Drag object*/
{
e.preventDefault();
e.stopPropagation();
if(dragObj == null) return; // if there is no object being dragged then do nothing
else if(e.type=="mousemove")
{
dragObj.style.left = e.clientX-xOffset +"px"; // adjust location of dragged object so doesn't jump to mouse position
dragObj.style.top = e.clientY-yOffset +"px";
}
else if(e.type=="touchmove")
{
dragObj.style.left = e.targetTouches[0].clientX-xOffset +"px"; // adjust location of dragged object so doesn't jump to mouse position
dragObj.style.top = e.targetTouches[0].clientY-yOffset +"px";
}
}
function stopDrag(e)
/*End dragging*/
{
if(dragObj)
{
dragObj = null;
window.removeEventListener('mousemove', dragObject, true);
window.removeEventListener('touchmove', dragObject, true);
}
}
div{height:400px; width:400px; border:1px solid #ccc; background:blue; cursor: pointer;}
<div id="menuBar" >A</div>
<div draggable=true ondragstart="event.dataTransfer.setData('text/plain', '12345')">
drag me
</div>
<div ondragover="return false;" ondrop="this.innerHTML=event.dataTransfer.getData('text/plain')">
drop on me
</div>