I am writing an HTML5 app using primarily SVG content, within an HTML page. One of the tasks I need to do is move objects around on the canvas. I find that, in Firefox, the first press-drag-release operation works as expected, but very soon the code stops receiving mousemove events after a mousedown. Instead, I see the "ghostbusters" cursor, as if Firefox thinks I am trying to do a Drag-and-Drop operation. I can eventually restore mousemove after mousedown events for one or two cycles by clicking around the green rectangle, but then the problem recurs. I have even set draggable="false" as seen in the simple test case below, to make sure DnD is disabled. (All of the content was copied from one file, though it looks like it is getting separated here.)
This test case works fine in IE9, Chrome, Opera, and Safari.
A similar "pure" SVG page works in Firefox as well as the other browsers.
I am using Firefox 15.0.1 on a Windows 7 client, serving the page up from a Linux box.
Is there something I am missing? Or is this perhaps a Firefox bug?
<!DOCTYPE HTML5>
<title>Test Mouse Events</title>
<script>
function mouseDown(evt)
{
document.getElementById("udText").textContent = "mousedown at "+evt.clientX+","+evt.clientY;
}
function mouseMove(evt)
{
document.getElementById("moveText").textContent = "mousemove at "+evt.clientX+","+evt.clientY;
}
function mouseUp(evt)
{
document.getElementById("udText").textContent = "mouseup at "+evt.clientX+","+evt.clientY;
}
function init()
{
document.getElementById("field").addEventListener("mousedown", mouseDown, false);
document.getElementById("field").addEventListener("mouseup", mouseUp, false);
document.getElementById("field").addEventListener("mousemove", mouseMove, false);
}
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
id="canvas"
width="800"
height="600"
version="1.1"
baseProfile="full"
onload="init()"
>
<rect id="field" x="50" y="50" width="700" height="500" fill="#20c000" draggable="false"/>
<text id="udText" x="80" y="520" font-size="30" fill="#000000">No up or down events yet</text>
<text id="moveText" x="420" y="520" font-size="30" fill="#000000">No move events yet</text>
</svg>
You should call evt.preventDefault(); in all the mouseXXX handlers. Firefox has default drag/drop handling and you don't want that. It's not a bug in Firefox that it does that.
These changes fix it for me:
function mouseDown(evt)
{
evt.preventDefault();
document.getElementById("udText").textContent = "mousedown at "+evt.clientX+","+evt.clientY;
}
function mouseMove(evt)
{
evt.preventDefault();
document.getElementById("moveText").textContent = "mousemove at "+evt.clientX+","+evt.clientY;
}
function mouseUp(evt)
{
evt.preventDefault();
document.getElementById("udText").textContent = "mouseup at "+evt.clientX+","+evt.clientY;
}
Related
A piece I'm currently working on is calling for tooltips that display when you hover over part of an SVG element, disappear as normal on mouse-out but providing the mouse does not go over the tooltip itself. We are using Materialize CSS which does come with a tooltip component.
A segment of my code is below.
<svg width="400" height="400">
<rect x="190" y="255" width="70" height="25" class="fixture good tooltipped" id="ines" data-position="top" data-delay="50" data-tooltip="Carbonated Drinks<br><a href='#'>View More</a>"" data-html="true"/>
</svg>
As you can see, the reason I want this is so that the user can click the 'View more' link if they mouse onto the actual tooltip. Currently, however, the tooltips disappear even if you mouse onto them.
I know this can be done with other frameworks/libararies, but I have been unable to do this in Materialize CSS so far.
Does anyone know if this is possible as an extensive internet search has turned up nothing.
Materialize tooltip assign a "mouseleave.tooltip" event handler for each involved dom node.
This event is triggered as soon as you will leave the dom element, and after 225 milliseconds (for details refer to source code) the tootip will be hidden.
Moreover, the tooltip has the style pointer-events equal to none: no mouse event can be triggered, so your anchor will be never clickable.
In order to overcome all these steps a possibility is:
save the jQuery mouseout event object
remove the previous object from the jQuery handlers so no mouseleave.tooltip can be triggered.
handle the jQuery hover event for each tooltip (having class: material-tooltip): this to save a property in order to test against the mouse position in or out the tooltip
on mouseenter for your element set the pointer-events to default auto
in the same way on mouseleave set a timeout less than 225 milliseconds in order to test if the mouse is over the tooltip: if not execute the standard jQuery materialize mouseleave.tooltip event
on mouseleave the tooltip do the same step in the previous point.
The snippet (jsfiddle):
$(function () {
var x = jQuery._data( document.getElementById('ines'), "events" )['mouseout'][0];
delete jQuery._data( document.getElementById('ines'), "events" )['mouseout'];
$('.material-tooltip').hover(function(e) {
$(this).attr('hover', 1);
}, function(e) {
$(this).attr('hover', 0);
x.handler.apply( document.getElementById('ines'), x);
});
$('#ines').on('mouseenter', function(e) {
$('.material-tooltip:visible').css('pointer-events', 'auto');
}).on('mouseleave', function(e) {
setTimeout(function() {
var val = $('.material-tooltip:visible').attr('hover');
if (val == undefined || val == 0) {
x.handler.apply( document.getElementById('ines'), x);
}
}, 150);
})
});
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script>
<svg width="400" height="400">
<rect x="190" y="155" width="70" height="25" class="fixture good tooltipped" id="ines" data-position="top"
data-delay="50" data-tooltip="Carbonated Drinks<br><a href='#'>View More</a>"
data-html="true"/>
</svg>
I'm using angular to create dynamic charts. I use css 'transition' in order to give it an effect, that the charts are going up. But when I initialize the charts value, the charts are indeed go up very nicely, but for some reason the chart with the lowest value sometimes gets smeared.
Any ideas why?
I attched a photo to show the unwanted outcome.
Left Chart Gets Smeared http://jsfiddle.net/Lvc0u55v/8846/
Update:
Here is a little example of how it suppose to look. Run it a few times to see it happens. You know what is crazy? I ran it a few times on different computers on Chrome with same version and I saw the error happens only on one of them. Does it might have something to do with the graphic card?
http://jsfiddle.net/Lvc0u55v/8846/
Angular:
var app = angular.module("myApp", [])
app.controller("MyCtrl", function ($scope, $timeout) {
$scope.charts = [{height:0, x:10, width:80, color:"red"},
{height:0, x:110, width:80, color:"blue" },
{height:0, x:210, width:80, color:"purple"},
{height:0, x:310, width:80, color:"green"}]
$timeout(function(){
for(var i = 0;i<4;i++){
$scope.charts[i].height = 200 + Math.random() * 100;
}
}, 1)
})
Html:
<div ng-app="myApp" ng-controller="MyCtrl">
<svg width=400 height=300>
<rect ng-repeat="chart in charts" ng-attr-height="{{chart.height}}" ng-attr-width={{chart.width}} ng-attr-x="{{chart.x}}" fill={{chart.color}} stroke=black stroke-width:1px></rect>
</svg>
</div>
CSS:
rect{
transition: 0.5s;
}
svg{
transform:rotateX(180deg)
}
I've been doing a lot of research into Canvas and SVG, as well as CSS3 3d transforms, and I want to use them all together, and I'm having difficulty getting it all working. I've got an SVG element containing an image-based mask as well as a foreignObject element containing some HTML I'd like to mask, particularly, a canvas. Depending on what I do with the canvas, I'm getting different results in different browsers, and usually not what I want. Applying typical canvas operations, such as rect(), and applying typical css transformations, such as rotate3d(), will, in most browsers, cause the SVG image mask to no longer be applied to the canvas, and the entire canvas will be visible, including the masked part.
Safari (desktop): If the canvas has ever been drawn on or transformed in any way (even, for example, rotate3d(1,1,0,0deg)), it will not be masked.
Safari (iOS 7): If the canvas has ever been drawn on or transformed in any way, it will not be masked.
Chrome (desktop): Here, we can draw to the canvas and it will still be masked, but if it has been transformed, it will not be masked.
Chrome (iOS 7): If the canvas has been drawn on or transformed at all, it will not be masked.
Firefox: No problems here; it will still be masked even if it has been drawn on and transformed.
IE and others: I haven't checked yet.
You can see this illustrated by applying the following code, which is also hosted here, where I set up an example page: http://kage23.com/masktest.html
<svg width="748" height="421" baseProfile="full" version="1.2">
<defs>
<mask id="myMask" transform="scale(1)">
<image width="100%" height="421" xlink:href="http://i.imgur.com/ORtP2fW.png" />
</mask>
</defs>
<foreignObject id="foreignObject" width="100%" height="421">
<div style="background-color: green; width: 100%; height: 421px;">
<p id="myParagraph" style="padding-top: 100px; padding-left: 200px;">Some text or whatever.</p>
<canvas id="effectCanvas" width="500px" height="100px" style="background-color: red;"></canvas>
</div>
</foreignObject>
</svg>
<button onclick="applyMask()">Apply mask</button>
<button onClick="drawToCanvas()">Draw to canvas</button>
<button onClick="rotateCanvas()">Apply rotation to canvas</button>
<script>
var applyMask = function ()
{
var element = document.getElementById('foreignObject');
element.style.mask = 'url(#myMask)';
};
var drawToCanvas = function ()
{
var canvas = document.getElementById('effectCanvas');
var ctx = canvas.getContext('2d');
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
};
var rotateCanvas = function ()
{
var canvas = document.getElementById('effectCanvas');
canvas.style.webkitTransform = 'rotate3d(1,1,1,45deg)';
canvas.style.MozTransform = 'rotate3d(1,1,1,45deg)';
};
</script>
Is there a better way to do this? I'm not committed to using an SVG mask necessarily; I just need to be able to mask a transforming canvas that is also being actively drawn on. Originally, I was using -webkit-mask-box-image, but that's not supported very widely at all.
I'd like some input on using xml namespaced attributes with angular.
The problem is angular comes with a couple of directives to handle writing attributes such as href and src when angular has parsed the expresssions (otherwise the browser will try to load {{mymodel.myimage}} as a url)
https://github.com/angular/angular.js/blob/master/src/ng/directive/booleanAttrs.js#L329
The problem I'm facing is that I'm using angular to output svg together with D3 and since angular doesn't have a way to output xlink:href I was stuck.
I created a custom directive that outputs xlink:href
app.directive('ngXlinkHref', function () {
return {
priority: 99,
restrict: 'A',
link: function (scope, element, attr) {
var attrName = 'xlink:href';
attr.$observe('ngXlinkHref', function (value) {
if (!value)
return;
attr.$set(attrName, value);
});
}
};
});
Full demo: http://plnkr.co/edit/cMhGRh
But it seems that if I don't manually add xlink:href to the element, the svg image will not render.
Any suggestions on how to best handle xml namespaces / svg together with angular would be greatly appreciated.
You can use ng-attr-<some attribute>
ng-attr-xlink:href="{{xxx}}" works for me.
Note that you also need an empty xlink:href="" as initial value. – Derek Hsu
If, like me, you're looking for a way to add images to svg, you can do so adding:
xlink:href="" ng-href="{{ foo }}"
Example:
http://jsbin.com/sigoleya/1/edit?html,js,output
Where I found the solution:
https://github.com/angular/angular.js/issues/7697
I ran into a similar problem when trying to output a value for xlink:href that's tied to the model. Based on the user's chosen <option> in a <select> control, I was trying to show a dynamic SVG icon via the xlink:href attribute of the <use> element.
I found a thread about this in the GitHub Issues for AngularJS. Based on the discussion there, it appears that because a viable workaround exists, they've effectively tabled a fix by moving it to the Backlog milestone.
What ultimately worked for me was inspired by this JSBin:
http://jsbin.com/sigoleya/1/edit?html,js,output
Here's the code I used in my template:
<svg class="icon" data-ng-class="category.iconName">
<use xlink:href="" data-ng-href="{{'#' + category.iconName}}">
</svg>
Given a category.iconName of icon-music, for example, Angular sets the xlink:href dynamically to #icon-music, which references the <svg id="icon-music"> element further up on the same page.
As others have noted, what's key is setting a blank xlink:href="" attribute on the element where you call the ngHref directive. Attribute order does not seem to matter. Using ng-attr-xlink:href="{{xxx}}" (as mentioned in Derek Hsu's answer) did not work for me.
All of this assumes Angular 1.3.36.
I solved the same problem with the following modules:
Module for SVGs:
var app = angular.module('Svgs', []);
angular.forEach([
{ ngAttrName: 'ngXlinkHref', attrName: 'xlink:href' },
{ ngAttrName: 'ngWidth', attrName: 'width' },
{ ngAttrName: 'ngHeight', attrName: 'height' }
], function (pair) {
var ngAttrName = pair.ngAttrName;
var attrName = pair.attrName;
app.directive(ngAttrName, function (IeHelperSrv) {
return {
priority: 99,
link: function (scope, element, attrs) {
attrs.$observe(ngAttrName, function (value) {
if (!value) return;
attrs.$set(attrName, value);
if (IeHelperSrv.isIE) element.prop(attrName, value);
});
}
};
});
});
Module for IE detection:
angular.module('IeHelper', []).factory('IeHelperSrv', function () {
return {
isIE: checkForIE.isIE,
}
});
var checkForIE = {
init: function () {
this.isIE = (navigator.userAgent.indexOf('MSIE') != -1);
}
};
checkForIE.init();
HTML:
<!-- image has initial fake source, width and height to force it to render -->
<image xlink:href="~/Content/Empty.png" width="1" height="1"
ng-xlink-href="{{item.imageSrc}}"
ng-width="{{item.width}}" ng-height="{{item.height}}"
ng-cloak
/>
For anyone else having this problem due to Angular/Angular UI Router in HTML5 mode, I came up with a straightforward fix to enable svg sprite icons to work with their xlink:href attribute and the tag.
Gist is here: https://gist.github.com/planetflash/4d9d66e924aae95f7618c03f2aabd4a3
app.run(['$rootScope', '$window', function($rootScope, $window){
$rootScope.$on('$locationChangeSuccess', function(event){
$rootScope.absurl = $window.location.href;
});
<svg><use xlink:href="{{absurl+'#svgvID'}}"></use></svg>
I ran into this problem where I was using Ajax to load the svg spritesheet onto the page. If I had a on the page before the spritesheet was loaded, it would fail and would not resolve once the spritesheet was avaialble. Any added to the dom after the spritesheet was loaded were fine. I had to delay putting the items in the dom until after the spritesheet finished loading.
This only affected IOS. All other browsers didn't care about the order.
This took me more time than I would've wanted. Around 20-30 minutes.
If I understand correctly, any failed loading on image element will render that element useless in the future. I believe it's something similiar #GeekyMonkey is saying. If angular binding system has set xlink:href initially to null, Image element wont work anymore, even if we have valid value in the future.
Here is solution, notice how I have wrapped image element inside g element, using ng-if directive. That makes sure we will bind against image only when a correct value is available.
<g ng-if="vm.svgMap.background != null">
<image
ng-attr-xlink:href="{{vm.svgMap.background.image | trusted}}"
ng-attr-width="{{vm.svgMap.background.width}}"
ng-attr-height="{{vm.svgMap.background.width}}"
xlink:href=""
width="1"
height="1"
x="0"
y="0"></image>
</g>
As others said, the order of attributes are important as well. To ensure that angularJS allows us to bind image element, we'll also have to trust that resource, I've done it through filter (it's the one in xlink:href attribute):
(function() {
'use strict';
angular.module('myTool').filter('trusted', TrustedFilter);
function TrustedFilter($sce) {
return function(url) {
return $sce.trustAsResourceUrl(url);
};
};
}());
Objective: Apply CSS Filters to video using html5 and JavaScript.
Contraints: The solution must be compatible with Internet Exporer 10 (for Windows 8). I am really making a Metro app.
So Far:
I have a <video> that I am pumping onto a <canvas>. I thought I would be able to apply CSS filters directly to this (e.g. invert or brightness) but it turns out those are not compatible with IE10.
Thoughts: I am hoping for a way to apply SVG filters to the canvas. Is this possible? Do I need to copy the <canvas> to an <image> and apply the filters to that? Alternatively, should there be a way to wrap the canvas in a <foreignObject>?
Thank you for all your help!
Here is some code for those interested:
filters.svg:
<?xml version="1.0" standalone="no"?>
<svg width="1" height="1" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="blur">
<feGaussianBlur in="SourceGraphic" stdDeviation="2 3" />
</filter>
</defs>
</svg>
style.css:
.a {
filter: url(filter.svg#blur);
-ms-transform: matrix(-1, 0, 0, 1, 0, 0);
}
page.html:
<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
<canvas class="a" style="width: 180px;height:180px;margin-bottom: -5px;" data-win-bind="style.backgroundColor: bc; id: effectId" />
</div>
The Following Code Works, albeit very slowly, to accomplish my goal. Thank you, Anthony!
<html>
<head>
</head>
<body>
<svg id="svgroot" viewbox="0 0 800 800" width="800" height="800" preserveAspectRatio="xMidYMin">
<defs>
<filter id="myHueRotate">
<feColorMatrix type="hueRotate" values="270"/>
</filter>
</defs>
<image id="a" filter="url(#myHueRotate)" x="0" y="0" width="300" height="300" />
<image id="b" filter="url(#myHueRotate)" x="300" y="0" width="300" height="300" />
<image id="c" filter="url(#myHueRotate)" x="0" y="300" width="300" height="300" />
<image id="d" filter="url(#myHueRotate)" x="300" y="300" width="300" height="300" />
</svg>
<canvas id="canvas" height="300" width="300"></canvas>
<video id="vid" src="movie.m4v" height="300" width="300" style="display: none" autoplay/>
<script>
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.src = 'img.jpg';
img.onload = function(){
//ctx.drawImage(img,0,0);
//var canvasImage = canvas.toDataURL("image/png");
//var svgImage = document.getElementById('a');
//svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", canvasImage);
draw();
}
img.load();
function draw(){
var ctx = document.getElementById('canvas').getContext('2d');
var vid = document.getElementById('vid')
ctx.drawImage(vid,0,0,300,300);
var canvasImage = canvas.toDataURL("image/png");
document.getElementById('a').setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", canvasImage);
document.getElementById('b').setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", canvasImage);
document.getElementById('c').setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", canvasImage);
document.getElementById('d').setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", canvasImage);
setTimeout(draw,40);
}
</script>
</body>
</html>
First, articles to read:
moving-to-standards-based-web-graphics-in-ie10
Notice specifically the sections:
Use SVG, not VML
and
Use CSS3, not DX Filters
In that second section, they mention:
DX Filters are not the same as SVG Filter Effects, though both use the CSS property name filter.
Second article:
Introduction to Filters and Transitions
They give a specific example of how to use invert, but, assuming it is the way in IE, I can see why it wasn't easy to find and may or may not work in your case. But the css would be:
#yourTargetElement {
filter: DXImageTransform.Microsoft.BasicImage(invert=1);
}
They don't mention brightness, but they do mention several other filters and transitions, and that first article does mention using SVG. More details (hopefully helpful ones) at:
SVG Filter Effects in IE10
This looks like part 1 of the key:
A filter is applied to an SVG element via the filter attribute, in the form of filter="url(#filterId)", or it can be applied as a CSS property filter:url(#filterId)
And this is part 2:
There are 16 different filter primitives.
Now, I believe the 16 they refer to are the full set for SVG, but knowing MS, it could also mean either:
These are the 16 we support, or
These are the 16 we've invented so as to continue our claim to make IE standards-compliant and SVG/MathML friendly, but making it harder than it would be in any other browser...because we can.
Or, to quote Lily Tomlin: "We don't care, we don't have to...we're the phone company."
But, assuming MS is finally realizing they need to catch up, reading further on the 16 primitive filters, supposedly you just have your embedded SVG, with the filters in the right place (defs) and call them via css. Here is one of their examples (slightly modified and simplified by me):
HTML w/ Embedded SVG:
<div id="svg_wrapper">
<svg xmlns="http://www.w3.org/2000/svg" id="svgroot" viewBox="0 0 800 533" preserveAspectRatio="xMidYMin">
<defs>
<filter id="filtersPicture">
<feComposite operator="arithmetic" k1="0" k2="1" k3="0" k4="0"
in="SourceGraphic" in2="SourceGraphic" result="inputTo_6">
</feComposite>
<feColorMatrix type="saturate" id="filter_6" values="2"
data-filterId="6">
</feColorMatrix>
</filter>
</svg>
</div>
CSS (They use JS to make it dynamic, so beware):
<style type="text/css">
#yourTargetElement{
filter:url(#filtersPicture);
}
</style>
The reason I caution on how "easy" they make it look is because they are adding the style via js and an interactive form (maybe you have the same thing in mind), but I imagine that runs the same risk as calling an element in a script before it is in the DOM, in that it can't find the filter and throws an error. So be sure if you want to keep it simple (non-dynamic) and things still aren't working, to try putting the filter/svg above the style (even if this causes a flicker).