I have a frameless window with a titlebar set up much like in the node-webkit sample apps (that is, with buttons that have -webkit-app-region: no-drag; so that they accept user input).
Because I'd like my app to be completely themeable, I decided to implement the minimize/maximize/close buttons as SVG and manipulate their color/opacity through JavaScript.
Example
As an example, I have the following close button close.svg:
<svg class="close-icon" xmlns="http://www.w3.org/2000/svg" width="30" height="26">
<g>
<title>Close</title>
<path class="close-icon-bg" fill="#c8c8c8" d="M0 0h30v26h-30z"/>
<path class="close-icon-fg" d="M11.766 8l-1.766 1.766 3.221 3.221-3.221 3.247 1.766 1.766 3.221-3.247 3.247 3.247 1.766-1.766-3.247-3.247 3.247-3.221-1.766-1.766-3.247 3.221-3.221-3.221z"/>
</g>
</svg>
I then have the following in my titlebar div:
<object id="close-btn" data="img/close.svg" type="image/svg+xml"></object>
Then in JavaScript I have the following:
function refreshTheme(theme) {
var but2 = {
"default": {
"bg": {"color": "#000000", "opacity": "0.0"},
"fg": {"color": "#000000", "opacity": "0.2"}
},
"hover": {
"bg": {"color": "#ac4142", "opacity": "1.0"},
"fg": {"color": "#e0e0e0", "opacity": "1.0"}
},
"click": {
"bg": {"color": "#000000", "opacity": "1.0"},
"fg": {"color": "#e0e0e0", "opacity": "1.0"}
}
};
createButton("close", but2, dummy);
}
function createButton(name, theme, func) {
var svgobj = document.getElementById(name + "-btn"), svg = svgobj.contentDocument;
styleIcon(svg, name, theme.default);
svgobj.onmouseover = function() {
styleIcon(svg, name, theme.hover);
};
svgobj.onmouseout = function() {
styleIcon(svg, name, theme.default);
};
svgobj.onmousedown = function() {
styleIcon(svg, name, theme.click);
};
svgobj.onclick = function() {
func();
};
}
function styleIcon(icon, name, theme) {
var bg = icon.getElementsByClassName(name + "-icon-bg")[0];
bg.setAttribute("fill", theme.bg.color);
bg.setAttribute("fill-opacity", theme.bg.opacity);
var fg = icon.getElementsByClassName(name + "-icon-fg")[0];
fg.setAttribute("fill", theme.fg.color);
fg.setAttribute("fill-opacity", theme.fg.opacity);
}
Essentially what happens is, I have a hard-coded JSON object but2 encapsulating the button states, which I pass to createButton(), which is responsible for setting up the various mouse events. I can confirm that the onmouseover and onmouseout events work well:
The top screenshot shows onmouseout, the bottom onmouseover.
The Problem
What doesn't work are the onmousedown and onclick events! They appear to be set up correctly in the debug window and no errors are present in the console, but they never get fired.
I was under the impression that <object> tags accept all standard HTML events, so this should work. Is there something I'm missing? Some node-webkit caveat maybe? Something to do with SVG's?
How can I get the onmousedown and onclick events to fire properly?
It turns out this is an actual problem with SVG's in browsers. The solution, as noted here, is to have the SVG hold the mouse up/down/click events in a transparent (fill-opacity="0.0") rectangle spanning the whole image.
In my case, the solution was:
var svg_click = svg.getElementsByClassName(name + "-icon-click")[0];
svg_click.onmousedown = function() {
styleIcon(svg, name, theme.click);
};
// etc...
I'm not terribly pleased with it, considering it should also work from the <object> tag, but hey...it's still a solution.
Related
I have designed a map on AI (adobe Illustrator) with sections and areas and exported the final map as SVG file to display on html page. Also I have the details for these sections in a separate excel sheet, I want when mouseover any section it will make a popup with the details for that section.
I need your advice on how to accomplish that.
Any help is appreciated,
The data should be converted to json or a javascript object like this:
var xlsData = {
"RedRect": "This is the Red Rectangle!",
"Star": "This is the Star Shape!"
}
The best way is to use the javascript event load on a svg object to attach the mouse events. Because jQuery prevents to bind load events to object elements, we have to use javascript addEventListener to setup the load event.
How to listen to a load event of an object with a SVG image?
Inside the SVG file, we have two objects with the ids RedRect and Star:
<rect id="RedRect" x="118" y="131" class="st0" width="153" height="116"/>
<polygon id="Star" class="st2" points="397,252.3 366.9,245.4 344.2,266.3 341.5,235.6 314.6,220.4 343,208.3 349.1,178.1
369.4,201.4 400,197.8 384.2,224.3 "/>
Now, all we have to do is attach our events when the svg objects loads:
<object id="svg" type="image/svg+xml" data="test-links.svg">Your browser does not support SVG</object>
$('object')[0].addEventListener('load', function() {
$('#RedRect', this.contentDocument).on({
'mouseenter': function() {
$('#hover-status').text('#svg #RedRect Mouse Enter');
$('#hover-data').text(xlsData['RedRect']);
},
'mouseleave': function() {
$('#hover-status').text('#svg #RedRect Mouse Leave');
$('#hover-data').html(' ');
}
});
$('#Star', this.contentDocument).on({
'mouseenter': function() {
$('#hover-status').text('#svg #Star Mouse Enter');
$('#hover-data').text(xlsData['Star']);
},
'mouseleave': function() {
$('#hover-status').text('#svg #Star Mouse Leave');
$('#hover-data').html(' ');
}
});
}, true);
Plunker example
I'm trying to attach Javascript events to SVG elements using code that is inside the "module pattern". I'm very new to Javascript, as I'm sure will be evident by the code below. I've attempted to recreate the situation using a much simpler version of the code I am actually using. Ironically, I am actually further away from getting it to work with this example, but my brain hurts from attempting to get it to work and researching elsewhere on this site. So, I am reaching out to all the clever people out there to see if they can help. The problem I am having (when I get it to work!) is that the Javascript events do not appear to be binding to the DOM elements and just firing on page load. When I then fire the event again (by moving the mouse) I get a Javascript error caused by the D3 library:
"Uncaught TypeError: n.apply is not a function"
If anyone can look past the rushed effort at recreating a module pattern and get this example to work - hopefully I will be able to incorporate the same into my actual code (the only real difference between the example and real code is that there are a bunch of "extender" modules rather than everything being in one).
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
<script>
function mouseOver(i) {
return this.targets[i].select("#" + this.targets[i].container).select("circle").attr("fill", "blue");
}
function mouseOut(i) {
return this.targets[i].select("#" + this.targets[i].container).select("circle").attr("fill", "green");
}
var example = (function () {
return {
addRect: function addRect(container) {
this.box = d3.select("#" + container).append("svg");
this.rect = this.box.append("rect")
.attr({
"fill": "red",
"width": 50,
"height": 50,
});
},
addCircle: function addControl(targets) {
this.targets = targets
for (target in this.targets) {
tt = this.targets[target]
tt.circle = tt.box.append("circle")
.attr({
"fill": "green",
"cx": -10,
"cy":-10,
"r": 10,
"transform": "translate(30,30)"
})
};
},
addControl: function addControl(targets) {
this.targets = targets
for (target in this.targets) {
tt = this.targets[target]
tt.control = tt.box.append("rect")
.attr({
"fill": "none",
"width": 50,
"height": 50,
})
.on("mouseover", mouseOver(target))
.on("mouseout", mouseOut(target))
};
},
};
})();
var first = new example.addRect("box1");
var second = new example.addRect("box2");
var circs = new example.addCircle([first, second]);
var controls = new example.addControl([first, second]);
</script>
</body>
</html>
Problem1:
The event gets fired on page load
Reason:
You are doing
.on("mouseover", mouseOver(target)) //this is wrong the second parameter should have been a function.
something like this
.on("mouseover", function(){mouseOver(target)})
Problem 2:
tt.control = tt.box.append("rect")
.attr({
"fill": "none",//this should not be none else mouse over will not work as you expect
"width": 50,
"height": 50,
})
.on("mouseover", mouseOver(target))
.on("mouseout", mouseOut(target))
It should be done with opacity 0 so that its not visble
for (target in this.targets) {
tt = this.targets[target]
tt.control = tt.box.append("rect")
.attr({
"fill": "blue",
"width": 50,
"height": 50,
"opacity": 0
})
Problem 3:
You can set the parameter to the DOM rather than passing it in the function.
Something like this
for (target in this.targets) {
tt = this.targets[target]
tt.control = tt.box.append("rect")
.attr({
"fill": "blue",
"width": 50,
"height": 50,
"opacity": 0
})
.on("mouseover", function() {
mouseOver(tt)
})
.on("mouseout", function() {
mouseOut(tt)
});
tt.control.node().dataset = tt;//binding the data to the dom
};
And in the mouse event get the bound object like this:
function mouseOver(target) {
//gettting the data from the dom
event.target.dataset.circle.attr("fill", "blue");
}
function mouseOut(target) {
event.target.dataset.circle.attr("fill", "green");
}
Working code here.
I would like to add category icons to a Wordpress page, each icon animated with snap.svg.
I added the div and inside an svg in the loop that prints the page (index.php). All divs are appearing with the right size of the svg, but blank.
The svg has a class that is targeted by the js file.
The js file is loaded and works fine by itself, but the animation appears only in the first div of that class, printed on each other as many times it is counted by the loop (how many posts there are on the actual page from that category).
I added "each()" and the beginning of the js, but is not allocating the animations on their proper places. I also tried to add double "each()" for the svg location and adding the snap object to svg too, but that was not working either.
I tried to add unique id to each svg with the post-id, but i could not pass the id from inside the loop to the js file. I went through many possible solutions I found here and else, but none were adaptable, because my php and js is too poor.
If you know how should I solve this, please answer me. Thank you!
// This is the js code (a little trimmed, because the path is long with many randoms, but everything else is there):
jQuery(document).ready(function(){
jQuery(".d-icon").each(function() {
var dicon = Snap(".d-icon");
var dfirepath = dicon.path("M250 377 C"+ ......+ z").attr({ id: "dfirepath", class: "dfire", fill: "none", });
function animpath(){ dfirepath.animate({ 'd':"M250 377 C"+(Math.floor(Math.random() * 20 + 271))+ .....+ z" }, 200, mina.linear);};
function setIntervalX(callback, delay, repetitions, complete) { var x = 0; var intervalID = window.setInterval(function () { callback(); if (++x === repetitions) { window.clearInterval(intervalID); complete();} }, delay); }
var dman = dicon.path("m136 ..... 0z").attr({ id: "dman", class:"dman", fill: "#222", transform: "r70", });
var dslip = dicon.path("m307 ..... 0z").attr({ id: "dslip", class:"dslip", fill: "#196ff1", transform:"s0 0"});
var dani1 = function() { dslip.animate({ transform: "s1 1"}, 500, dani2); }
var dani2 = function() { dman.animate({ transform: 'r0 ' + dman.getBBox().cx + ' ' + dman.getBBox(0).cy, opacity:"1" }, 500, dani3 ); }
var dani3 = function() { dslip.animate({ transform: "s0 0"}, 300); dman.animate({ transform: "s0 0"}, 300, dani4); }
var dani4 = function() { dfirepath.animate({fill: "#d62a2a"}, 30, dani5); }
var dani5 = function() { setIntervalX(animpath, 200, 10, dani6); }
var dani6 = function() { dfirepath.animate({fill: "#fff"}, 30); dman.animate({ transform: "s1 1"}, 100); }
dani1(); }); });
I guess your error is here:
var dicon = Snap(".d-icon");
You are passing a query selector to the Snap constructor, this means Snap always tries to get the first DOM element with that class, hence why you're getting the animations at the wrong place.
You can either correct that in two ways:
Declare width and height inside the constructor, for example var dicon = Snap(800, 600);
Since you are using jQuery you can access to the current element inside .each() with the $(this) keyword. Since you are using jQuery instead of the dollar you could use jQuery(this).
Please keep in mind this is a jQuery object and probably Snap will require a DOM object. In jQuery you can access the dom object by appending a [0] after the this keyword. If var dicon = Snap( jQuery(this) ); does not work you can try with var dicon = Snap( jQuery(this)[0] );
Additionally, you have several .attr({id : '...', in your code. I assume you are trying to associate to the paths an ID which are not unique. These should be relatively safe since they sit inside a SVG element and I don't see you are using those ID for future selection.
But if you have to select those at a later time I would suggest to append to these a numerical value so you wont have colliding ID names.
I'm trying to make a page inspection tool, where:
The whole page is shaded
Hovered elements are unshaded.
Unlike a lightbox type app (which is similar), the hovered items should remain in place and (ideally) not be duplicated.
Originally, looking at the image lightbox implementations, I thought of appending an overlay to the document, then raising the z-index of elements upon hover. However this technique does not work in this case, as the overlay blocks additional mouse hovers:
$(function() {
window.alert('started');
$('<div id="overlay" />').hide().appendTo('body').fadeIn('slow');
$("p").hover(
function () {
$(this).css( {"z-index":5} );
},
function () {
$(this).css( {"z-index":0} );
}
);
Alternatively, JQueryTools has an 'expose' and 'mask' tool, which I have tried with the code below:
$(function() {
$("a").click(function() {
alert("Hello world!");
});
// Mask whole page
$(document).mask("#222");
// Mask and expose on however / unhover
$("p").hover(
function () {
$(this).expose();
},
function () {
$(this).mask();
}
);
});
Hovering does not work unless I disable the initial page masking. Any thoughts of how best to achieve this, with plain JQuery, JQuery tools expose, or some other technique? Thankyou!
What you can do is make a copy of the element and insert it back into the DOM outside of your overlay (with a higher z-index). You'll need to calculate its position to do so, but that's not too difficult.
Here is a working example.
In writing this I re-learned the fact that something with zero opacity cannot trigger an event. Therefore you can't use .fade(), you have to specifically set the opacity to a non-zero but very small number.
$(document).ready(function() { init() })
function init() {
$('.overlay').show()
$('.available').each(function() {
var newDiv = $('<div>').appendTo('body');
var myPos = $(this).position()
newDiv.addClass('available')
newDiv.addClass('peek')
newDiv.addClass('demoBorder')
newDiv.css('top',myPos.top+'px')
newDiv.css('left',myPos.left+'px')
newDiv.css('height',$(this).height()+'px')
newDiv.css('width',$(this).width()+'px')
newDiv.hover(function()
{newDiv.addClass('full');newDiv.stop();newDiv.fadeTo('fast',.9)},function()
{newDiv.removeClass('full');newDiv.fadeTo('fast',.1)})
})
}
Sorry for the prototype syntax, but this might give you a good idea.
function overlay() {
var div = document.createElement('div');
div.setStyle({
position: "absolute",
left: "0px",
right: "0px",
top: "0px",
bottom: "0px",
backgroundColor: "#000000",
opacity: "0.2",
zIndex: "20"
})
div.setAttribute('id','over');
$('body').insert(div);
}
$(document).observe('mousemove', function(e) {
var left = e.clientX,
top = e.clientY,
ele = document.elementFromPoint(left,top);
//from here you can create that empty div and insert this element in there
})
overlay();
Many of you already familiar with Firebug Inspect option, which allow to move around in loaded web page and select web page Elements for inspection.
Maybe someone know any similar JavaScript which could do the same? I need to allow user to select and remove web page element at runtime. User visit web page, move mouse on elements and web element become selected, user click on element to remove it.
Any reference where I could start?
Regards,
Tomas
I like a challenge.
Using jQuery, I've just made a simple example of how I would go about removing elements, visually. Check out a demo at roosteronacid.com/visualremove.
$(function ()
{
$("* :not(body)").mouseenter(function ()
{
var that = $(this);
var offset = that.offset();
var a = $("<a />",
{
title: "Click to remove this element",
css: {
"position": "absolute",
"background-color": "#898989",
"border": "solid 2px #000000",
"cursor": "crosshair",
width: that.width() + 6,
height: that.height() + 2
},
offset: {
top: offset.top - 4,
left: offset.left - 4
},
click: function ()
{
that.remove();
a.remove();
},
mouseleave: function ()
{
a.fadeTo(200, 0, function ()
{
a.remove();
});
}
});
that.after(a.fadeTo(200, 0.5));
});
});
There's Firebug Lite, Firebug packed as a javascript, to put in other browser than ff: http://getfirebug.com/firebuglite