I have an SVG which has a simplified structure like that:
<svg viewbox="0 0 1000 2000">
<image width="1024" height="2048" transform="<matrix A>" … />
</svg>
And I want to replace the image with a html5 <video> element, usign the <foreignObject> like so:
<svg viewbox="0 0 1000 2000">
<g transform="<matrix A>">
<foreignObject x="0" y="0" width="100%" height="100%">
<video style="display:block; width: 100%; height: 100%;">
<source src="…" type="…">
</video>
</foreignObject>
</g>
<!--
<image width="1024" height="2048" transform="<matrix in here>" … />
-->
</svg>
Therefore I have this code:
[...document.querySelectorAll('image')].forEach(img => {
const
g = document.createElementNS('…', 'g'),
fo = g.appendChild(document.createElementNS('…', 'foreignObject')),
video = fo.appendChild(document.createElement('video')),
src = video.appendChild(document.createElement('source'))
;
g.setAttribute('transform', img.getAttribute('transform'));
fo.setAttribute('x', 0);
fo.setAttribute('y', 0);
fo.setAttribute('width', '100%');
fo.setAttribute('height', '100%');
video.style.cssText = `
position: absolute;
top: 100px;
left: 100px;
background-color: #00ff00;
`
video.setAttribute('width', 100);
video.setAttribute('height', 100);
img.parentNode.appendChild(g);
img.parentNode.removeChild(img);
});
Basically everything works fine so far, but I cannot make the <video> appear at the same location an size as the image. The specs say:
(I)
[…] The included foreign graphical content is subject to SVG transformations, filters, clipping, masking and compositing […]
(II)
The HTML parser treats elements inside the ‘foreignObject’ equivalent to elements inside an HTML document fragment. […]
Taking (I) into account: By replacing the image with a <g> which has all the transformations copied over from the image, the video should »live in the coordinate space« than.
Taking (II) into account, the <video> should become the root node of a new Document Fragment, and fit into the given area.
But the result looks like that:
Whereby: The Black Area represents the entire SVG, the white Area the overall HTML document, which includes the the SVG. Both green areas are/seam to be the video, whereby the larger one is the one I would like to have the video.
Why is that/What am I missing?
UPDATE
Since this went too far I created this basic svg:
<svg
width="200"
height="200"
viewBox="0 0 200 200"
preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clip">
<circle cx="100" cy="100" r="100" />
</clipPath>
</defs>
<rect
x="0"
y="0"
width="200"
height="200"
style="fill:#ff0000;stroke:none"
clip-path="url(#clip)" />
<g clip-path="url(#clip)">
<foreignObject x="0" y="0" width="200" height="200" >
<video
autoplay=""
width="100%" height="100%">
<source src="/path/to/a/video.mp4" type="video/mp4" />
</video>
</foreignObject>
</g>
<text x="100" y="100">Hello</text>
</svg>
What works perfectly in Firefox, but not in Chromium. I can manage to get the video cropped by adding style="clip-path: url(#clip)" to the video, but the <text> does not appear in front of the video, in chromium.
Considering the coordinates of the video, this works:
const
{ top,
left,
width,
height } = foreignObject.getBoundingClientRect()
;
Object.entries({ width, height}).forEach(
([k,v]) => video.setAttribute(k,v)
);
video.style.left = `${left}px`;
video.style.top = `${top}px`;
Your viewport is 1000x2000 but the image is 1024x2048. I think that will clip your image. Might be part of the confusion.
Next: Images scale more freely than videos since you usually want to preserve the aspect ration of the video. That's why you get black borders when the video size doesn't fit the container. Make sure that the video size, the image size and the SVG viewport all agree.
I'm also a bit skeptical that a browser can apply all SVG transformations to video. At least in the past, browsers wouldn't render video themselves but reserve a rectangular area in the page and send the coordinates of this area to a video renderer (which could be in another process using an overlay window). If you can't get this to work, try to replace the whole SVG with the video.
Also, instead of doing this in JavaScript, build a small local HTML file that you can quickly tweak until the video looks correct. When it works, then start with code that can transform between the two. Otherwise, you're opening too many cans of worms.
[Update 1]
Welcome to Hell. Stay a while ...
You can try to use the developer tools to find out what Chromium is doing in this case. Maybe create a second local test case where you try to position a <div> with some text over the video. If that doesn't work, then Chromium is using an external renderer for this type of video.
This looks the same on the screen but behind the scenes, Chromium is asking another process to position another window on the screen in such a way that it looks as if it was part of the page. If you have a tool which can show you the coordinates of all application windows on the screen, you can see that there is one which stays on top of your browser window. Clicks go to that Window, Chromium can't see them and it can't render anything in front of that window because it's a window of someone else (just like you wouldn't want Chromium to render over a text editor that happens to be open over the browser window).
Options:
Convert to a format which Chromium can render by itself. See https://www.chromium.org/audio-video for a list. That's probably only VP8, VP9 and Theora (= any "open" video format). H.264 and MPEG-4 might not work because film studios panic that you could steal their precious.
Use text overlay where it works and move the text below the video where it doesn't.
Use a process on the server serving the video files to stamp the text into the video before passing it to the browser. Some video formats have support for subtitles which can be abused for this if you don't need anything too fancy. This would prevent the need to reencode the video.
Display a text and ask visitors to install a browser where it works.
Related
I have a small SVG canvas in my application which houses a button to click. Upon clicking that button, the small circle does some flashy work and opens up to an appropriate sized bubble to fit all the data. This data is just a list of data with custom bullets off to the left; some of this text is clickable (bound to click events) and causes other functions to be fired off. My problem is that the SVG canvas never changes in size. It's really only big enough to contain the original circle that the user clicks on. The rest of SVG elements that make up this control flow outside the bounds of the SVG canvas, but is all viewable due to the SVG having its overflow set to visible. Everything works fine in all browsers, but things change when I open up the application in an iPad or on a Mac (regardless of browser). It seems the overflow pieces of my SVG are completely invisible to the browser. Any click event that fires fine on the overflowed SVG elements on Window's browsers are completely invisible on OSX/iOS browsers (it seems like any click actually falls through to whatever is behind the overflowed SVG). At first I thought it was an issue with the click binding, but after searching and playing around with it, I think OSX/iOS just doesn't handle overflowed SVG very well.
To explain in its simplest form what's going on in my application, I have a very simple JSFiddle here: https://jsfiddle.net/yno8daka/
HTML:
<svg style="overflow: visible !important" class="canvas" width='70px' height='70px' xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle id="circle1" stroke="black" fill="black" stroke-width="1" cx="10" cy="10" r="10"></circle>
<circle id="circle2" stroke="black" fill="black" stroke-width="1" cx="110" cy="110" r="10"></circle>
</svg>
Javascript:
$('#circle1').click(function() {
alert('yeah');
})
$('#circle2').click(function() {
alert('yeah2');
})
Circle1 is in the bounds of the SVG canvas, while circle2 is not. On Windows browsers, you will be able to click on both circles without any issue. On OSX/iOS browsers, the click event will fire for circle1, but not for circle2.
Does anyone know how to get this to work?
I am loading different SVGs dynamically within a web application built in AngularJS, I am also altering the opacity of layers within the SVGs. These SVGs have some paths with the fill pattern property as such
<defs>
<pattern id="glass-floral" patternUnits="userSpaceOnUse" width="184" height="272">
<image xlink:href="../img/glass-floral.png" x="0" y="0" width="184" height="272"/>
</pattern>
</defs>
<rect x="98.3" y="85.5" fill="url(#glass-floral)" width="365" height="318.8"/>
This all works great at first- however under some conditions these # fills simply disappear:
-
Condition 1:
If I were to switch to another SVG and back.
Result::
The # fill is still visible.
-
Condition 2:
If I were to alter the opacity of the element with the # fill.
Result::
The # fill is still visible.
-
Condition 3:
If I were to both switch to another SVG & alter the opacity of the element with the # fill.
Result::
The # fill becomes invisible.
-
This is to mean the styles all appear to still be applied normally in the code- but there is no actual visible fill to be seen. This behaviour exists as far as I can see in Chrome and slightly differently in Safari. Firefox seems to be immune.
I've tried manually flicking the element to another fill and back in the browser to see if perhaps something had cached, no luck. I still think this may somehow be the case, with how the # refers to an inline pattern defined in the <defs> which may not have been loaded yet by the AJAX but the cached CSS rule still floating around.
If it helps matters, both SVGs that I am switching between both have the same <defs> and CSS styling applied. Is perhaps the double case of the defined pattern causing an issue?
After some investigation this appears to be an issue with the browsers (Chrome/Safari possibly others) not being able to keep up with rendering fill: url(#) and opacity for the same element at the same time, at least in cases of multiple/dynamically loaded SVGs.
To solve this, apply your opacity css to a containing element around the element that has the fill: url(#), example below:
<defs>
<pattern id="glass-floral" patternUnits="userSpaceOnUse" width="184" height="272">
<image xlink:href="../img/glass-floral.png" x="0" y="0" width="184" height="272"/>
</pattern>
</defs>
<style>.opacity-class { opacity: 0.33; }</style>
<g class="opacity-class">
<rect x="98.3" y="85.5" fill="url(#glass-floral)" width="365" height="318.8"/>
</g>
This allows the browser to do both independently and not ruin your pretty pictures.
I had multiple svg elements and problem was the same ID of all pattern tags. So, using different id="" for the pattern tag of each svg element solved my problem with disappearing fill="url()" on dynamic reload...
I'm working with animated SVGs / Snap.svg for the first time, so please forgive my lack of knowledge on this subject.
I made a series of 3 animated SVG "frames" (400x300px), each nested within a larger SVG (1200x300px) to contain them all. A div element with a clip style property hides the other two "frames" when they're not ready to be shown.
Using Snap.svg, each frame is supposed to "slide" into view using translate after a certain amount of time, and within each frame are some animated elements.
Long story short: the animation looks perfect in Firefox, but it looks awful in Chrome/Webkit. In Chrome, it looks like each of the frames are just being stacked on top of each other instead of side-by-side.
In addition, two of the elements (the cow circle joules and the graph circle graph) are rendering in the upper-left corner instead of using their translate property to position them in the center-right area.
You can see the animation in Plunker. Please try it out in both browsers to see what I mean.
http://plnkr.co/UhTy83
Firefox GIF screen capture:
Chrome GIF screen capture:
Thanks Ian in the comments to my question! Swapping out the <svg> tags for <g> (group) tags fixed this problem. It's interesting to me that Firefox allows you to transform <svg> elements but Webkit does not.
Before:
<svg class="slides" width="1200" height="300">
<svg class="slide1" width="400" height="300"></svg>
<svg class="slide2" width="400" height="300"></svg>
<svg class="slide3" width="400" height="300"></svg>
</svg>
After:
<svg class="slides" width="1200" height="300">
<g class="slide1"></g>
<g class="slide2"></g>
<g class="slide3"></g>
</svg>
I am trying to dynamically create SVG vectors with Javascript and I'm running into some problems.
I create the svg markup in javascript with document.createElement and append it to a div container. Then I do the same thing and create a rect and add it as a child to the svg id.
When I look at the DOM, I can see the elements being added, and their properties are such:
<svg id="gdc_container" xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="300">
<rect id="gdc_background" width="500" height="300" style="rgb(0,0,255)"></rect>
</svg>
Everything looks fine, however the graphics are not showing up. When I use Chromes inspector, I can see the svg and rect elements, but the chrome tooltip shows their size as [ 0 x 0 ].
Anyone have any ideas?
Did you try using createElementNS? (developer.mozilla.org/en/DOM/document.createElementNS)
For some examples look at http://www.kevlindev.com/tutorials/basics/shapes/js_dom/index.htm.
I'm trying to create (what I thought would be!) a simple re-usable bit of SVG to show three lines of text, with a background colour - to simulate a 'post-it' note.
I have found some useful code here to get the Bounds of the Text http://my.opera.com/MacDev_ed/blog/2009/01/21/getting-boundingbox-of-svg-elements which I am using.
So: I'm creating an group of text elements like this in the 'defs' section of my SVG:
<svg id="canvas" width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="post_it">
<text x="0" y="30" id="heading" class="heading">My Heading</text>
<text x="0" y="45" id="description" class="description">This will contain the description</text>
<text x="0" y="60" id="company" class="company">Very Big Company Ltd.</text>
</g>
And I'm displaying the text with a 'use' element like this:
<use id="12345" class="postit" xlink:href="#post_it" onclick="showId(this);"/>
I'm using the onclick to trigger a call to the following javascript function (defined in 'defs' section):
function showId(elem) {
post_it_rect=getBBoxAsRectElement(elem);
document.getElementById('canvas').appendChild(post_it_rect);
}
(The 'getBBoxAsRectElement(elem)' is from the link I posted).
As this stands; this works just fine - however if I change my 'use' element to position the text in a different place like this:
<use x="100" y="100" id="12345" class="postit" xlink:href="#post_it" onclick="showId(this);"/>
Now, the text displays in the correct place, but the resultant 'background-color' (actually a 'rect' element with opacity of 0.5) still shows on the top-left of the svg canvass - and the function used to calculate the rect is returning '-2' rather than '100' ('-98'?) as I need (I think).
What do I need to do to line up the 'rect' elements and the text elements ?
The author of the (very helpful article btw) script provides a more advanced script to draw a box round any 'bb' in an SVG, but I couldn't get this to work (missing 'transform' functions?).
I'm using Firefox 7.x to render the SVG ; and I'm loading a .svg file (ie, not embedded in html etc) straight from disk to test this).
Yes, you may need to compensate yourself for the x and y attributes on the <use> element for the time being, I'll try to find some time to update the blogpost and script.
Here's a draft SVG 1.1 test that among other things checks that the effect of the x and y attributes are included in the bbox. The line starting [myUse] is the one that tests this case, if it's red then that subtest failed. Chromium and Opera Next both pass that subtest, while Firefox nightly and IE9 doesn't. Note that the test itself has not gone through full review yet, and that it may still change.