scroll while dragging element with d3.js - javascript

I am using d3's drag behavior on an svg element that might need to be dropped outside of the visible area. I have it setup within two divs with overflow set to auto to enable scrolling. I have this only working with some browsers but not all.
The issue is that in some browsers, you will be able to scroll while dragging, but in others the window will not scroll while dragging. I have as of yet been unable to find a way to make this work consistently.
for a working example see the fiddle: http://jsfiddle.net/glanotte/qd5Td/1/
This is working as expected on:
chrome - mac/windows
safari - mac
But not working on
Firefox - mac/windows
IE - windows
html:
<div id="outerFrame">
<div id="innerFrame">
<svg width="600" height="600">
</svg>
</div>
</div>
css:
#outerFrame{
width: 300px;
height: 300px;
border: 1px solid red;
overflow: auto;
}
#innerFrame{
width: 600px;
height: 600px;
background-color: lightgrey;
}
javascript:
var drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", dragmove)
.on("dragend", dragend);
function dragstart() {
d3.select(this).style("border", "1px solid red");
}
function dragmove(d) {
var coordinates = d3.mouse(d3.select("svg").node());
d3.select(this).attr({
x: coordinates[0] - 50,
y: coordinates[1] - 25
})
}
function dragend() {
d3.select(this).style("border", null);
}
d3.select("svg")
.append("rect")
.attr({x: 100, y: 100, width: 100, height: 50})
.call(drag);

You have unfortunately struck upon a bug in Firefox which has been noticed before by mbostock and marked as WONT-FIX.
As per suggestion in the bug report, you can make it work, but only by scrolling the container manually: http://jsfiddle.net/62CYD/
The implementation is pretty simple and can be improved by:
Using animations
Taking into account width of scroll bars, like done in DOMUtilityService in ng-grid.
Taking current mouse position into account to avoid snapping of the dragged item and smoother scrolling.
Use setTimeout to continue scrolling even if dragging stops
function dragmove(d) {
var svg = d3.select("svg").node(),
$parent = $('#outerFrame'),
w = $parent.width(), h = $parent.height(),
sL = $parent.scrollLeft(), sT = $parent.scrollTop();
var coordinates = d3.mouse(svg),
x = coordinates[0],
y = coordinates[1];
if (x > w + sL) {
$parent.scrollLeft(x - w);
} else if (x < sL) {
$parent.scrollLeft(x);
}
if (y > h + sT) {
$parent.scrollTop(y - h);
} else if (y < sT) {
$parent.scrollTop(y);
}
d3.select(this).attr({
x: x - 50,
y: y - 25
})
}

Related

detect how many pixles the cursor is away from the center of the screen

I am trying to detect how many detect how many pixels the cursor is away from the center of the window.
Adding the returned value to a var using javascript or jQuery?.
Even better, if this could be attached to an element. So the var updates as you move and your mouse closer and further away from the elements centre point.
I'm not even sure this is possible hence why there is no code. Thanks
Is this what you want? attach onmousemove event to window and use window height&width subtracting current mouse position?
var disFromCenterX = 0,
disFromCenterY = 0
window.onmousemove = function(e) {
disFromCenterX = Math.abs(window.innerWidth/2 - e.pageX);
disFromCenterY = Math.abs(window.innerHeight/2 - e.pageY);
console.log("distance from center x: " + disFromCenterX);
console.log("distance from center y: " + disFromCenterY);
}
same logic on element
var disFromCenterX = 0,
disFromCenterY = 0
document.querySelector("#div").onmousemove = function(e) {
var rect = this.getBoundingClientRect();
disFromCenterX = Math.abs(this.clientWidth/2 - (e.pageX - rect.left));
disFromCenterY = Math.abs(this.clientHeight/2 - (e.pageY - rect.top));
console.log("distance from center x: " + disFromCenterX);
console.log("distance from center y: " + disFromCenterY);
}
#div {
height: 400px;
width: 300px;
border: 1px solid black;
}
<div id="div"></div>

Line of code that is changing the DOM element

I want to know how to pinpoint the line/position of code which is altering a specific DOM element or it's style.
With Chrome DOMListner I see which elements get changed and what is the change but I cannot figure out which line of script caused that DOM change.
Example
jsfiddle
HTML
<div class="red circle absolute"></div>
CSS
body {
margin: 0;
padding: 0;
font-size: 10px;
}
.red {
background-color: #F44336;
/* Material design 500 tint Red color */
}
.circle {
height: 3em;
width: 3em;
border-radius: 50%;
}
.absolute {
position: absolute;
top: 0;
left: 0;
}
JS
document.onmousemove = function (e) {
// source: http://stackoverflow.com/questions/11245119/how-to-get-mouse-pointer-position-using-javascript-for-internet-explorer
// as on: 28.09.2015
var x = (window.Event) ? e.pageX : event.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
var y = (window.Event) ? e.pageY : event.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
var el = document.getElementsByTagName('div')[0];
el.style.left = (x - 15) + 'px';
el.style.top = (y - 15) + 'px';
}
jsfiddle shows a circle which follows the mouse cursor. Circle is positioned absolutely and onmousemove event triggers the change of circle position. This example is over-simplified and one can easily see where the top and left properties of a DOM element are changed.
I would like to find a method of finding the exact line/position of code for any JS script I stumble upon. Thanks
In Chrome's developer tools, this option is available if you right click on a specific element. Selecting Break on... and then one of the sub-options will cause Chrome to break on the JavaScript line that made the change, so long as you had the tools open at the time.

Snap.SVG - Why is my draggable rectangles become slower and slower when being dragged?

I'm implementing a feature on my app which make use of Snap.SVG to create rectangles which can be dragged. This is my code: (I uploaded it here: http://jsfiddle.net/u8vnL9tg/)
<!DOCTYPE html>
<html>
<head>
<title>TEST WHITEOUT FEATURE</title>
<style type="text/css">
#mySvg{
width: 700px;
height: 700px;
margin: 0 auto;
display: block;
border: 1px solid black;
}
.white-out{
cursor: move;
}
</style>
</head>
<body>
<svg id="mySvg">
</svg>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg-min.js"></script>
<script type="text/javascript">
var s = Snap("#mySvg");
$('#mySvg').mousedown(function(e){
if (e.which === 1){
if ($(e.target).is('.white-out')){
return;
} else {
$('#mySvg').css({
'cursor' : 'cell'
});
// Calculate relative mouse position
var offset = $('#mySvg').offset();
var relativeMouseX = e.pageX - offset.left;
var relativeMouseY = e.pageY - offset.top;
// Create a rectangle
var selectionRect = s.rect(relativeMouseX, relativeMouseY, 1, 1);
selectionRect.addClass('white-out');
$('#mySvg').on('mousemove', function(e){
// Calculate relative mouse position
var offset = $('#mySvg').offset();
var newRelativeMouseX = e.pageX - offset.left;
var newRelativeMouseY = e.pageY - offset.top;
var width = Math.abs(newRelativeMouseX - relativeMouseX);
var height = Math.abs(newRelativeMouseY - relativeMouseY);
var new_x, new_y;
new_x = (newRelativeMouseX < relativeMouseX) ? (relativeMouseX - width) : relativeMouseX;
new_y = (newRelativeMouseY < relativeMouseY) ? (relativeMouseY - height) : relativeMouseY;
selectionRect.attr({
'width': width,
'height': height,
'top': new_y,
'left': new_x,
'fill': '#6699FF',
'opacity': 0.5
});
});
$('#mySvg').on('mouseup', function(e) {
// Unbind the mousemove event
$('#mySvg').off('mousemove');
// Set opacity to 1 - so it's really a whiteout
selectionRect.attr({
'opacity': 1
});
selectionRect.drag();
// Set cursor to default
$('#mySvg').css({
'cursor' : 'default'
});
});
}
}
});
</script>
</body>
</html>
At the moment, I have successfully create the rectangles and make them draggable. The problem I'm facing here is that after some attempts (more than 5 times, for example) of drag-and-drop a rectangle, the rectangle becomes MUCH slower and slower. After about 10 times of drag-and-drop, it even hardly move ! I don't know why this is happening though I'm following the examples and tutorials on the Internet, and especially the SnapSVG documentation.
Really hope that you guys could help ! Thanks so much in advanced !

Locate mouse position relative to transformed plane

I've set up a jsfiddle illustrating my situation: http://jsfiddle.net/j5o0w5qc/1/
Basically, I've got three nested HTML elements: a viewport div on the outside, a stage div in the middle, and a canvas on the inside. The stage div provides a perspective setting for 3d transformations applied to the canvas. The viewport has overflow: hidden; so we don't see anything outside of the viewport. It also has a listener attached, listening for mousedown.
In my actual app that I'm building, the canvas might be transformed to any arbitrary 3d transformation, involving translation and rotation in 3d space. What I would like to happen is for the viewport div to intercept a click, and draw a spot on the canvas in the place you clicked. I'm intercepting the event with the viewport div, and I'm using offsetX and offsetY in Chrome. This works great for Chrome, but I know I can't rely on offsetX and offsetY in other browsers, so I'd like to use pageX and pageY, normalized via jQuery, but I'm not sure quite how to do that.
What I've currently got in the jsfiddle works great in Chrome, except when you click in the viewport NOT on the canvas. When you click on the canvas, it draws a dot there, regardless of the canvas's transformation. Chrome is doing all the hard work and giving me exactly what I want with offsetX and offsetY. However, when you click in the viewport NOT on the canvas, I guess it's giving me offsetX and offsetY values relative to the viewport, rather than the canvas, and then interpreting that and drawing a dot on the canvas. For example, if I transform the canvas and then click in the upper right corner of the viewport, a dot appears in the upper right corner of the canvas, regardless of where that corner actually appears on the page.
In Firefox, however, it works great as long as there is no transformation applied to the canvas, but as soon as the canvas is transformed, all of a sudden, the dot being drawn is displaced, and I can't figure out how to take my pageX and pageY values and figure out exactly where in the canvas I am clicking.
Does anyone have any brilliant solutions? I've been bashing my head against this problem for far too long. I'm pretty sure I need to manually calculate some 3d transformation matrices or something, and I've spent hours writing methods to return the inverse of a matrix, and to multiply a matrix by a vector, and all sorts of stuff, but none of it has actually solved the problem for me, and I'm not sure what I'm missing.
Stackoverflow says code is required with jsfiddle links, so here's all my code:
HTML:
<div id="viewport">
<div id="stage">
<canvas id="myCanvas" width="300" height="300"></canvas>
</div>
</div>
<div id="stuff">
<button onclick="transformMe()">Transform</button>
<input id="blah" type="text" size="45"></input>
</div>
CSS:
#viewport, #stage, #myCanvas {
width: 300px;
height: 300px;
position: absolute;
top: 0;
left: 0;
}
#viewport {
border: 1px solid #000;
overflow: hidden;
}
#stage {
perspective: 1000px;
transform-style: preserve-3d;
}
#myCanvas {
background-color: green;
transform-style: preserve-3d;
}
#stuff {
position: absolute;
top: 350px;
}
Javascript:
var counter = 0;
$('#viewport').mousedown(function _drawOnCanvas (e)
{
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var xpos, ypos;
if (typeof e.offsetX=='undefined')
{
xpos = e.pageX - $('#myCanvas').offset().left;
ypos = e.pageY - $('#myCanvas').offset().top;
}
else
{
xpos = e.offsetX;
ypos = e.offsetY;
}
ctx.fillRect(xpos-5, ypos-5, 10, 10);
});
function transformMe()
{
counter++;
var angle = (counter * 30) % 360;
$('#myCanvas').css('transform','perspective(1000px) rotate3d(5,6,7,' + angle + 'deg)');
$('input').val('counter: ' + counter + ', angle: ' + angle);
};
For Firefox, you can use event.layerX and event.layerY. Think of them as Firefox's versions of offsetX & offsetY.
DEMO: http://jsfiddle.net/dirtyd77/j5o0w5qc/3/
JAVASCRIPT:
var counter = 0;
$('#viewport').mousedown(function _drawOnCanvas (e)
{
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var xpos, ypos;
if (typeof e.offsetX=='undefined')
{
xpos = e.originalEvent.layerX;
ypos = e.originalEvent.layerY;
}
else
{
xpos = e.offsetX;
ypos = e.offsetY;
}
ctx.fillRect(xpos-5, ypos-5, 10, 10);
});
function transformMe()
{
counter++;
var angle = (counter * 30) % 360;
$('#myCanvas').css('transform','perspective(1000px) rotate3d(5,6,7,' + angle + 'deg)');
$('input').val('counter: ' + counter + ', angle: ' + angle);
};
If you change viewport to myCanvas in line 3 of the either Kyle S or Dom's jsfiddles:
$('#myCanvas').mousedown(function _drawOnCanvas (e)
it no longer places a dot when you "click in the viewport NOT on the canvas."
It seems there's a new issue with Firefox - if there's a transformation it only lets you paint on half ( the bottom left of diagonal - but depends on transformation ).

How to correctly resize an element by dragging?

I have two divs serving as two panels one to the left and one to the right.
They take 70% and 30% of the area.
I have a separator between them.
When I drag the separator to the left or right, I want that to remain as the position of the separator. i.e., I should be able to dynamically resize the left and right divs by dragging.
Here is the code I have:
http://jsbin.com/witicozi/1/edit
HTML:
<!DOCTYPE html>
<html>
<head>
<body>
<div style='height: 100px'>
<div id='left'>...</div>
<div id='separator'></div>
<div id='right'>...</div>
</div>
</body>
</html>
CSS:
#left {
float: left;
width: 70%;
height: 100%;
overflow: auto;
}
#separator {
float: left;
width: 3px;
height: 100%;
background-color: gray;
cursor: col-resize;
}
#right {
height: 100%;
overflow: auto;
}
JavaScript:
document.querySelector('#separator').addEventListener('drag', function (event) {
var newX = event.clientX;
var totalWidth = document.querySelector('#left').offsetWidth;
document.querySelector('#left').style.width = ((newX / totalWidth) * 100) + '%';
});
The problems:
The resizing happens, but the separator jumps around randomly. It even falls down many times. I have no idea what's happening.
The mouse cursor changes to a hand when the dragging begins. I want it to remain a col-resize.
It is very hard to drag.
No JQuery please.
If you use console.log(event), it shows that event.clientX doesn't return exactly what you are looking for. The following JavaScript worked for me in chrome.
document.getElementById('separator').addEventListener('drag', function(event) {
var left = document.getElementById('left');
var newX = event.offsetX + left.offsetWidth;
left.style.width = newX + 'px';
});
The event.offsetX value that it is returning is the location (in px) of the upper left hand corner of the left div. This will give you the same result but using percentages so that when the resize the window the columns adjust:
document.getElementById('separator').addEventListener('drag', function(event) {
var left = document.getElementById('left');
var newX = event.offsetX + left.offsetWidth;
left.style.width = (newX / window.innerWidth * 100) + '%';
});
Taking a bit of a different approach: rather than using the drag and drop functionality, I used some coupled mouse down and mouse up listeners. This has better cross-browser compatibility (at least as far as my testing goes) and it has the added benefit of being able to easily control the cursor.
var resize = function(event) {
var newX = event.clientX;
document.getElementById('left').style.width = (newX / window.innerWidth * 100) + '%';
};
document.getElementById('separator').addEventListener('mousedown', function(event) {
document.addEventListener('mousemove', resize);
document.body.style.cursor = 'col-resize';
});
document.addEventListener('mouseup', function(event) {
document.removeEventListener('mousemove', resize);
document.body.style.cursor = '';
});

Categories

Resources