I've created several shapes in SVG and added an effect that changes the shapes color when hovered over.
But how can I make the shapes "pop up" slightly when hovered over?
Would appreciate any help.
You can modify the transform of each element. Probably something like this:
function onEnter (e) {
e.target.setAttribute('transform', 'scale(2,2)');
}
function onLeave (e) {
e.target.removeAttribute('transform');
}
But in fact you do not need to take the long slow way using the attributes, instead you can use the transform interface of SVG elements. The documentation therefore is on the page linked above, too. But dealing with this might be a bit »unintuitive« the first time, since each element has a transform list with several transform items.
I think it would be a good Idea to dive in the svg transform interface (and affine transformations in general), since it is very powerful, but it requires a »learning curve« before you are ready to use it.
If you want to short circuit this you can also use a helper framework like: svg.js, snapsvg or raphael which all have common methods to help you dealing with thing like that.
EDIT
The functions above are just examples for one could do it. The onEnter and onLeave methods should be called only if the mouse enters the svg element or when it leaves. So you could add a mousemove listener and check if the target is one of your desired elements:
var firstElement = document.getElementById('<yourelementsid>');
isOverFirstElement = false;
window.addEventListener('mousemove', function (e) {
if (e.target === firstElement && isOverFirstElement == false) {
isOverFirstElement = true;
onEnter(e);
} else if (e.target !== firstElement && isOverFirstElement == true) {
isOverFirstElement = false;
onLeave(e);
}
}, false);
With code like that you can detect a mouse enter or leave and react accordingly.
I've got it to do what I wanted by adding
target.setAttributeNS(null, 'transform', "translate(0 0)");
as such:
function unselectedColour(evt) {
var target = evt.target;
target.setAttributeNS(null, 'fill', 'white');
target.setAttributeNS(null, 'transform', "translate(0 0)");
}
function selectedColourBuilding(evt) {
var target = evt.target;
target.setAttributeNS(null, 'fill', 'purple');
target.setAttributeNS(null, 'transform', "translate(-3 -3)");
}
To make the 'pop out' stable, keeping it at it's current position when scaling, you can compute the center of its bounding box then translate the element's center to the origin, scale it, then translate back to its current location
e.g.
function onEnter(evt)
{
var target=evt.target
var bb=target.getBBox()
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
var cx=bbx+.5*bbw
var cy=bby+.5*bbh
target.setAttribute("transform","translate("+(cx)+" "+(cy)+")scale(1.2 1.2)translate("+(-cx)+" "+(-cy)+")")
}
function onExit(evt)
{
var target=evt.target
target.removeAttribute("transform")
}
Related
Is is possible in KonvaJS to drag a shape (like a rectangle) from one stage to another? Any tips?
You just need to listen to dragmove event and then move the shape to another stage when it goes outside of view.
Like this:
circle.on('dragmove', () => {
if (circle.getStage() === stage1 && circle.y() > stage1.height()) {
circle.y(0)
circle.moveTo(layer2);
layer1.draw();
layer2.draw();
}
if (circle.getStage() === stage2 && circle.y() < 0) {
circle.y(stage1.height());
circle.moveTo(layer1);
layer1.draw();
layer2.draw();
}
});
The conditions may be different, depending on your use case.
Demo: http://jsbin.com/kuculewibe/edit?css,js,output
I need to manually construct/fire a mousedown event in a way that can be handled by any relevant event handlers (either in straight JS, jQuery, or interact.js), just as a natural mousedown event would. However, the event does not seem to trigger anything the way I expect it to.
I am trying to make some irregularly shaped images draggable using the interact.js library. The images are simply rectangular img elements with transparent portions. On each element, I have defined 2 interact.js event listeners:
Checking if the click was inside the image area, disabling drag if not (fires on "down" event)
Handling the drag (fires on "drag" event)
However, if the img elements are overlapping, and the user clicks in the transparent area of the top element but on the filled area of the lower element, the lower element should be the target of the drag. After trying several things (see below), I have settled on the solution of: re-ordering the z-indexes of the elements at the same time that I disable drag in step 1, then re-firing the mousedown event on all lower elements. I'm using the "native" event type (not jQuery or interact.js) in hopes that it will literally just replicate the original mousedown event.
// code to re-assign "zIndex"s
function demote(element, interactible, event){
// block dragging on element
interactible.draggable(false);
// get all images lower than the target
var z = element.css("zIndex");
var images = $("img").filter(function() {
return Number($(this).css("zIndex")) < z;
});
// push the target to the back
element.css("zIndex",1);
// re-process all lower events
$(images).each( function () {
// move element up
$(this).css("zIndex",Number($(this).css("zIndex"))+1);
// re-fire event if element began below current target
elem = document.getElementById($(this).attr('id'));
// Create the event.
e = new MouseEvent("mousedown", {clientX: event.pageX, clientY: event.pageY});
var cancelled = !elem.dispatchEvent(e);
});
}
Unfortunately, this does not work, as the mousedown event does not register with any of the handlers. Why?
I have all the (relevant) code at this JSFiddle: https://jsfiddle.net/tfollo/xr27938a/10/
Note that this JSFiddle does not seem to work as smoothly as it does in a normal browser window, but I think it demonstrates the intended functionality.
Other things I have tried:
Lots of people have proposed different schemes to handle similar problems (i.e. forwarding events to lower layers, using pointer-events: none, etc), but none seem to work to trigger the interact.js handler and start a drag interaction on the right element. I also tried using interaction.start (provided by interact.js) but it seems buggy--there is at least one open issue on the topic and when I tried to start a new drag interaction on a target of my choice I got lots of errors from within the library's code.
I'm not against revisiting any of these solutions per se, but I would also really like to know why manually firing a mousedown event won't work.
The idea is to listen down event on parent element and to choose manually drag target. Also I didn't use z-index for choosing which image to drag, because z-index doesn't work with position:static. Instead of that I just gave priorities to images, it's all up to you. https://jsfiddle.net/yevt/6wb5oxx3/3/
var dragTarget;
function dragMoveListener (event) {
var target = event.target,
// keep the dragged position in the data-x/data-y attributes
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
// translate the element
target.style.webkitTransform =
target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
// update the posiion attributes
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
function setDragTarget(event) {
//choose element to drag
dragTarget = $('#parent').find('img')
.filter(function(i, el) {
var clickCandicateRect = el.getBoundingClientRect();
return insideBoundingRect(event.x, event.y, clickCandicateRect);
}).sort(byPriority).get(0);
}
function insideBoundingRect(x, y, rect) {
return (rect.left <= x) && (rect.top <= y) && (rect.right >= x) && (rect.bottom >= y);
}
function byPriority (a, b) {
return $(b).data('priority') - $(a).data('priority');
}
function startDrag(event) {
var interaction = event.interaction;
var target = dragTarget;
if (!interaction.interacting()) {
interaction.start({ name: 'drag' }, event.interactable, target);
}
}
//Give priority as you wish
$('#face1').data('priority', 2);
$('#face2').data('priority', 1);
interact('#parent').draggable({
//use manualStart to determine which element to drag
manualStart: true,
onmove: dragMoveListener,
restrict: {
restriction: "parent",
endOnly: true,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
},
})
.on('down', setDragTarget)
.on('move', startDrag);
Have you tried to set the css property pointer-events: none on the higher levels (it could also be set via javascript)?
I have a Fabric.js canvas and I want to implement the full-canvas panning that software packages usually do with a "hand" tool. It's when you press one of the mouse buttons, then move over the canvas while holding the mouse button and the visible portion of the canvas changes accordingly.
You can see in this video what I want to achieve.
In order to implement this functionality I wrote the following code:
$(canvas.wrapperEl).on('mousemove', function(evt) {
if (evt.button == 2) { // 2 is the right mouse button
canvas.absolutePan({
x: evt.clientX,
y: evt.clientY
});
}
});
But it doesn't work. You can see in this video what happens.
How can I modify my code in order:
For panning to work like in the first video?
For the event handler to consume the event? It should prevent the context menu from appearing when the user presses or releases the right mouse button.
An easy way to pan a Fabric canvas in response to mouse movement is to calculate the cursor displacement between mouse events and pass it to relativePan.
Observe how we can use the screenX and screenY properties of the previous mouse event to calculate the relative position of the current mouse event:
function startPan(event) {
if (event.button != 2) {
return;
}
var x0 = event.screenX,
y0 = event.screenY;
function continuePan(event) {
var x = event.screenX,
y = event.screenY;
fc.relativePan({ x: x - x0, y: y - y0 });
x0 = x;
y0 = y;
}
function stopPan(event) {
$(window).off('mousemove', continuePan);
$(window).off('mouseup', stopPan);
};
$(window).mousemove(continuePan);
$(window).mouseup(stopPan);
$(window).contextmenu(cancelMenu);
};
function cancelMenu() {
$(window).off('contextmenu', cancelMenu);
return false;
}
$(canvasWrapper).mousedown(startPan);
We start panning on mousedown and continue panning on mousemove. On mouseup, we cancel the panning; we also cancel the mouseup-cancelling function itself.
The right-click menu, also known as the context menu, is cancelled by returning false. The menu-cancelling function also cancels itself. Thus, the context menu will work if you subsequently click outside the canvas wrapper.
Here is a page demonstrating this approach:
http://michaellaszlo.com/so/fabric-pan/
You will see three images on a Fabric canvas (it may take a moment or two for the images to load). You'll be able to use the standard Fabric functionality. You can left-click on the images to move them around, stretch them, and rotate them. But when you right-click within the canvas container, you pan the whole Fabric canvas with the mouse.
I have an example on Github using fabric.js Canvas panning: https://sabatinomasala.github.io/fabric-clipping-demo/
The code responsible for the panning behaviour is the following: https://github.com/SabatinoMasala/fabric-clipping-demo/blob/master/src/classes/Panning.js
It's a simple extension on the fabric.Canvas.prototype, which enables you to toggle 'drag mode' on the canvas as follows:
canvas.toggleDragMode(true); // Start panning
canvas.toggleDragMode(false); // Stop panning
Take a look at the following snippet, documentation is available throughout the code.
const STATE_IDLE = 'idle';
const STATE_PANNING = 'panning';
fabric.Canvas.prototype.toggleDragMode = function(dragMode) {
// Remember the previous X and Y coordinates for delta calculations
let lastClientX;
let lastClientY;
// Keep track of the state
let state = STATE_IDLE;
// We're entering dragmode
if (dragMode) {
// Discard any active object
this.discardActiveObject();
// Set the cursor to 'move'
this.defaultCursor = 'move';
// Loop over all objects and disable events / selectable. We remember its value in a temp variable stored on each object
this.forEachObject(function(object) {
object.prevEvented = object.evented;
object.prevSelectable = object.selectable;
object.evented = false;
object.selectable = false;
});
// Remove selection ability on the canvas
this.selection = false;
// When MouseUp fires, we set the state to idle
this.on('mouse:up', function(e) {
state = STATE_IDLE;
});
// When MouseDown fires, we set the state to panning
this.on('mouse:down', (e) => {
state = STATE_PANNING;
lastClientX = e.e.clientX;
lastClientY = e.e.clientY;
});
// When the mouse moves, and we're panning (mouse down), we continue
this.on('mouse:move', (e) => {
if (state === STATE_PANNING && e && e.e) {
// let delta = new fabric.Point(e.e.movementX, e.e.movementY); // No Safari support for movementX and movementY
// For cross-browser compatibility, I had to manually keep track of the delta
// Calculate deltas
let deltaX = 0;
let deltaY = 0;
if (lastClientX) {
deltaX = e.e.clientX - lastClientX;
}
if (lastClientY) {
deltaY = e.e.clientY - lastClientY;
}
// Update the last X and Y values
lastClientX = e.e.clientX;
lastClientY = e.e.clientY;
let delta = new fabric.Point(deltaX, deltaY);
this.relativePan(delta);
this.trigger('moved');
}
});
} else {
// When we exit dragmode, we restore the previous values on all objects
this.forEachObject(function(object) {
object.evented = (object.prevEvented !== undefined) ? object.prevEvented : object.evented;
object.selectable = (object.prevSelectable !== undefined) ? object.prevSelectable : object.selectable;
});
// Reset the cursor
this.defaultCursor = 'default';
// Remove the event listeners
this.off('mouse:up');
this.off('mouse:down');
this.off('mouse:move');
// Restore selection ability on the canvas
this.selection = true;
}
};
// Create the canvas
let canvas = new fabric.Canvas('fabric')
canvas.backgroundColor = '#f1f1f1';
// Add a couple of rects
let rect = new fabric.Rect({
width: 100,
height: 100,
fill: '#f00'
});
canvas.add(rect)
rect = new fabric.Rect({
width: 200,
height: 200,
top: 200,
left: 200,
fill: '#f00'
});
canvas.add(rect)
// Handle dragmode change
let dragMode = false;
$('#dragmode').change(_ => {
dragMode = !dragMode;
canvas.toggleDragMode(dragMode);
});
<div>
<label for="dragmode">
Enable panning
<input type="checkbox" id="dragmode" name="dragmode" />
</label>
</div>
<canvas width="300" height="300" id="fabric"></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.15/fabric.min.js"></script>
Not sure about FabricJS, but it could be in such a way:
for it to work like in the first video:
By making use of CSS cursor property, toggling it on mousedown and mouseup events using javascript.
the event handler consume the event (prevent the context menu from appearing, when the user releases the right mouse button):
Using javascript we return false on contextmenu event
CODE: with a little problem ( * )
using jQuery JS Fiddle 1
$('#test').on('mousedown', function(e){
if (e.button == 2) {
// if right-click, set cursor shape to grabbing
$(this).css({'cursor':'grabbing'});
}
}).on('mouseup', function(){
// set cursor shape to default
$(this).css({'cursor':'default'});
}).on('contextmenu', function(){
//disable context menu on right click
return false;
});
Using raw javascript JS Fiddle 2
var test = document.getElementById('test');
test.addEventListener('mousedown', function(e){
if (e.button == 2) {
// if right-click, set cursor shape to grabbing
this.style.cursor = 'grabbing';
}
});
test.addEventListener('mouseup', function(){
// set cursor shape to default
this.style.cursor = 'default';
});
test.oncontextmenu = function(){
//disable context menu on right click
return false;
}
( * ) Problem:
The above snippets works as it should but there's a cross-browser issue, if you check the above fiddles in Firefox - or Opera -you'll see the correct behavior, when checked in Chrome and IE11 - didn't checked it with Edge or Safari - I found that Chrome and IE don't support the grabbing cursor shape, so in the above code snippets, change the cursor lines into this:
jQuery: $(this).css({'cursor':'move'}); JS Fiddle 3
Raw javascript: this.style.cursor = 'move'; JS Fiddle 4
Now we have a working code but without the hand cursor. but there is the following solution:-
SOLUTIONS:
Chrome and Safari support grab and grabbing with the -webkit- prefix like:
$(this).css({'cursor': '-webkit-grabbing'});
but you need to make browser sniffing first, if Firefox then the default and standard code, if Chrome and Safari then with the -webkit- prefix, and this still makes IE out of the game.
Have a look at this example, test it with Chrome, Safari, Firefox, Opera and IE you can see that cursor: url(foo.bar) works and supported in ALL browsers.
Chrome, Safari, Firefox and Opera shows the yellow smile image smiley.gif, but IE shows the red ball cursor url(myBall.cur).
So I think you can make use of this, and a grabbing hand image like this
Or this:
You can use an image like the above, a png or gif format with all browsers except IE which supports .cur, so you need to find a way to convert it into a .cur. Google search shows many result of convert image to cur
Note that, although this cursor:url(smiley.gif),url(myBall.cur),auto; - with fallback support separated by comma, works well in the W3Schools example shown above, I couldn't get it to work the same way in javascript, I tried $(this).css({'cursor': 'grabbing, move'}); but it didn't work.
I also tried doing it as CSS class
.myCursor{ cursor: grabbing, -webkit-grabbing, move; }
Then with jQuery $(this).addClass('myCursor'); but no avail either.
So you still need to make browser sniffing whether you are going the second solution or a hybrid fix of the both solutions, this is my code which I've used couple times to detect browser and it worked well at the time of this post but you porbabely won't need the "Mobile" and "Kindle" parts.
// Detecting browsers
$UA = navigator.userAgent;
if ($UA.match(/firefox/i)) {
$browser = 'Firefox';
} else if ($UA.indexOf('Trident') != -1 && $UA.indexOf('MSIE') == -1) {
$browser = 'MSIE';
} else if ($UA.indexOf('MSIE') != -1) {
$browser = 'MSIE';
} else if ($UA.indexOf('OPR/') != -1) {
$browser = 'Opera';
} else if ($UA.indexOf("Chrome") != -1) {
$browser = 'Chrome';
} else if ($UA.indexOf("Safari")!=-1) {
$browser = 'Safari';
}
if($UA.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Nokia|Mobile|Opera Mini/i)) {
$browser = 'Mobile';
}else if($UA.match(/KFAPWI/i)){
$browser = 'Kindle';
}
console.log($browser);
Resources:
https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
http://www.w3schools.com/cssref/pr_class_cursor.asp
https://css-tricks.com/almanac/properties/c/cursor/
Google images search of grabbing hand cursor
i have made an example on jsfiddle , that we can actually drag the whole canvas with all its objects, into a parent div, like the picture,and i will try to explain it step by step.
First of all i download the drag library jquery.dradscroll.js, you can find it on the net. This is a small js file that with little changes it can helps us complete the task.
download link: http://www.java2s.com/Open-Source/Javascript_Free_Code/jQuery_Scroll/Download_jquery_dragscroll_Free_Java_Code.htm
create the container that holds our canvas.
<div class="content">
<canvas id="c" width="600" height="700" ></canvas>
</div>
little css
.content{
overflow:auto;
width:400px;
height:400px;
}
javascript:
a. create the canvas.
b. make default cursor , when it is over canvas , open hand
canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';
c. override the __onMouseDown function , for change to the closedhand cursor( at end).
fabric.Canvas.prototype.__onMouseDown = function(e){
// accept only left clicks
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
if (!isLeftClick && !fabric.isTouchSupported) {
return;
}
if (this.isDrawingMode) {
this._onMouseDownInDrawingMode(e);
return;
}
// ignore if some object is being transformed at this moment
if (this._currentTransform) {
return;
}
var target = this.findTarget(e),
pointer = this.getPointer(e, true);
// save pointer for check in __onMouseUp event
this._previousPointer = pointer;
var shouldRender = this._shouldRender(target, pointer),
shouldGroup = this._shouldGroup(e, target);
if (this._shouldClearSelection(e, target)) {
this._clearSelection(e, target, pointer);
}
else if (shouldGroup) {
this._handleGrouping(e, target);
target = this.getActiveGroup();
}
if (target && target.selectable && !shouldGroup) {
this._beforeTransform(e, target);
this._setupCurrentTransform(e, target);
}
// we must renderAll so that active image is placed on the top canvas
shouldRender && this.renderAll();
this.fire('mouse:down', { target: target, e: e });
target && target.fire('mousedown', { e: e });
if(!canvas.getActiveObject() || !canvas.getActiveGroup()){
flag=true;
//change cursor to closedhand.cur
canvas.defaultCursor = 'url("http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur") 15 15, crosshair';
}//end if
override the __onMouseUp event ,to change back the cursor to openhand.
fabric.Canvas.prototype.__onMouseUp = function(e){
if(flag){
canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';
flag=false;
}
};
You initialize the dragScroll() to work on the content that hosts the canvas:
$('.content').dragScroll({});
Some small changes on the jquery.dragScroll.js file, so as to understand when to drag the canvas and when not. On mousedown() event we add an if statement to check whether we have an active object or group.If true ,no canvas drag.
$($scrollArea).mousedown(function (e) {
if (canvas.getActiveObject() || canvas.getActiveGroup()) {
console.log('no drag');return;
} else {
console.log($('body'));
if (typeof options.limitTo == "object") {
for (var i = 0; i < options.limitTo.length; i++) {
if ($(e.target).hasClass(options.limitTo[i])) {
doMousedown(e);
}
}
} else {
doMousedown(e);
}
}
});
on mousedown event we grab the DOM element (.content) and get the top & left position
function doMousedown(e) {
e.preventDefault();
down = true;
x = e.pageX;
y = e.pageY;
top = e.target.parentElement.parentElement.scrollTop; // .content
left = e.target.parentElement.parentElement.scrollLeft;// .content
}
If we dont want to have the scrollbars visible:
.content{
overflow:hidden;
width:400px;
height:400px;
}
There is a small problem though,jsfiddle, accepts only https libraries ,so it blocks fabricjs, except if you add it from 'https://rawgit.com/kangax/fabric.js/master/dist/fabric.js', but again it still blocks it some times (at least on my chrome and mozilla).
jsfiddle example : https://jsfiddle.net/tornado1979/up48rxLs/
you may have better luck ,than me, on your browser , but it would definitelly work on your live app.
Anyway,
i hope helps ,good luck.
I know this has already been answered, but I redid the pen created in this answer using the new version of fabricjs (4.5.0)
Pen: https://codepen.io/201flaviosilva/pen/GROLbQa
In this case, I used the middle button in the mouse to pan :)
// Enable mouse middle button
canvas.fireMiddleClick = true;
// Mouse Up Event
if (e.button === 2) currentState = STATE_IDLE;
// Mouse Down Event
if (e.button === 2) currentState = STATE_PANNING;
:)
I currently have html enabled tooltips that also display "sub graphs". However, it would be nice if it was possible to have all tooltips pop up in a fixed location or have an offset that adjusted their relative poition.
This is an example of the kind of tooltip that I have (blank data). I'd like to move it to the right. Any suggestions would be appreciated, including any javascript trickery.
whilst the answer is very good it is a little outdated now. Google has implemented CSS control so there is greater flexibility without the need to hack the JavaScript.
.google-visualization-tooltip { position:relative !important; top:0 !important;right:0 !important; z-index:+1;}
will provide a tooltip fixed at the bottom of the chart, live example: http://www.taxformcalculator.com/federal-budget/130000.html
alternatively you could just tweak the left margin...
.google-visualization-tooltip { margin-left: 150px !important; z-index:+1;}
Note that pulling the container forward with z-index reduces (but does not stop entirely) visibility flicker as the mouse moves. The degree of flicker will vary on chart size, call etc. Personally, I prefer to fix the tool tip and make it part of the design as per the first example. Hope this helps those who are deterred by the JS hack (which is good but really no longer necessary).
The tooltip position is set inline, so you need to listen for DOM insertion of the tooltip and change the position manually. Mutation events are deprecated, so use a MutationObserver if it is available (Chrome, Firefox, IE11) and a DOMNodeInserted event handler if not (IE 9, 10). This will not work in IE8.
google.visualization.events.addOneTimeListener(myChart, 'ready', function () {
var container = document.querySelector('#myChartDiv > div:last-child');
function setPosition () {
var tooltip = container.querySelector('div.google-visualization-tooltip');
tooltip.style.top = 0;
tooltip.style.left = 0;
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
for (var i = 0; i < m.length; i++) {
if (m[i].addedNodes.length) {
setPosition();
break; // once we find the added node, we shouldn't need to look any further
}
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
The MutationObserver should be fine, but the events may need some work; I didn't test them.
I had more or less the same question as Redshift, having been trying to move the tooltip relative to the node being hovered over. Using asgallant's fantastic answer I've implemented his code as below.
I haven't been able to test whether this works with the MutationObserver because during my testing in Firefox, Chrome and IE11 it always fails that test and uses addEventListener. The docs suggest it should work though.
I had to introduce a timeout to actually manipulate the styles as otherwise the left and top position of the element was always reported as 0. My assumption is that the event fired upon addition of the node but the DOM wasn't quite ready. This is just a guess though and I'm not 100% happy with implementing it in this way.
var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
google.visualization.events.addOneTimeListener(chart, 'ready', function () {
var container = document.querySelector('#line_chart > div:last-child');
function setPosition(e) {
if (e && e.target) {
var tooltip = $(e.target);
setTimeout(function () {
var left = parseFloat(tooltip.css('left')) - 49;
var top = parseFloat(tooltip.css('top')) - 40;
tooltip.css('left', left + 'px');
tooltip.css('top', top + 'px');
$(".google-visualization-tooltip").fadeIn(200);
}, 1);
}
else {
var tooltip = container.querySelector('.google-visualization-tooltip');
var left = parseFloat(tooltip.style.left) - 49;
var top = parseFloat(tooltip.style.top) - 40;
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
$(".google-visualization-tooltip").fadeIn(200);
}
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
if (m.length && m[0].addedNodes.length) {
setPosition(m);
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
chart.draw(data, options);
}
EDIT: Updated to get the MutationObserver working following asgallant's comment.
I have a series of buttons of fixed height and width, those need to be draggable and droppable anywhere inside the parent div.
As per client request, I cannot use any external libraries, meh... I could have done this with jQuery in seconds but, I guess this is one of its drawbacks: you don't get to learn more basic stuff...
How would I go about doing it? A problem with this is that those buttons are inside a div that is also draggable so I need to be careful about positioning, I can only probably use relative.
Any ideas how I can go about doing it? Thanks in advance.
Peter-Paul Koch wrote an excellent how-to on drag and drop. I only remembered this 3/4 of the way through writing my own, so I wrapped that up in a fiddle.
function makeDraggable(draggable, container){
// In case you don't want to have a container
var container = container || window;
// So we know what to do on mouseup:
// At this point we're not sure the user wants to drag
var dragging = false;
// The movement listener and position modifier
function dragHandler(moveEvent){
moveEvent.preventDefault();
dragging = true;
// Ascertain where the mouse is
var coordinates = [
moveEvent.clientX,
moveEvent.clientY
];
// Style properties we need to apply to the element
var styleValues = {
position : 'absolute',
left : coordinates[0] + 'px',
top : coordinates[1] + 'px'
};
// Apply said styles
for(property in styleValues){
if(styleValues.hasOwnProperty(property)){
draggable.style[property] = styleValues[property];
}
}
}
function dropHandler(upEvent){
// Only interfere if we've had a drag event
if(dragging === true){
// We don't want the button click event to fire!
upEvent.preventDefault();
// We don't want to listen for drag and drop until this is clicked again
container.removeEventListener('mousemove', dragHandler, false);
draggable.removeEventListener('mouseup', dropHandler, false);
dragging = false;
}
}
// Where all the fun happens
draggable.addEventListener('mousedown', function dragListener(downEvent){
downEvent.preventDefault();
// The drag event
container.addEventListener('mousemove', dragHandler, false);
// The end of drag, if dragging occurred
draggable.addEventListener('mouseup', dropHandler, false);
}, false);
}