SVG element broke when updating to SVG 2, why? - javascript

I had an SVG element that was used to render lines connecting nodes in a flowchart. At the beginning of October 2018, it suddenly stopped working in Chrome - the SVG element has 0 width and height in the DOM even though it has width and height attributes defined.
After doing some searching I found that Chrome recently updated its standards to SVG 2, however this SVG is fairly simple and I can't figure out exactly what changes caused this to happen.
Details:
The SVG is inside a regular DIV with position:relative. The DIV appears properly and has height and width set.
The SVG has a class and used to have position:absolute. It no longer seems to have any style and I can't edit its style through DevTools. I'm not certain if it needed to have a style to begin with.
The SVG has a bunch of line elements in it, and nothing else. The lines have classes and their styles don't work either.
The parent DIV does have other DIV elements in it (the nodes in the flowchart). These elements all have position:absolute.
Neither the parent DIV nor the SVG exist when the page is opened. They are created using Javascript.
There are no other SVG elements on the page and no use of the "use" keyword anywhere.
What part of this breaks with SVG 2 compliance?
Here is the code:
function appendElement(type,className,to,inner){
if (type === 'svg' || type === 'line'){
var el = document.createElementNS("https://www.w3.org/2000/svg", type);
if (className !== undefined) el.setAttribute("class",className);
} else {
var el = document.createElement(type);
if (className !== undefined) el.className = className;
}
to.appendChild(el);
if (inner !== undefined) el.innerHTML = inner;
return el;
}
The function in the flowchart class
setInner(){
this.flowchart.innerHTML = '';
this.svg = appendElement('svg','bw-flowchart-svg',this.flowchart);
this.svg.setAttribute("width", 800);
this.svg.setAttribute("height", 500);
this.currentSize = [800,500];
this.listitems = [];
this.links = [];
for (var i in this.obj.nodes){
this.listitems.push(new BWBFlowchartNode(this,this.obj.nodes[i]));
}
for (var i in this.listitems){
this.listitems[i].createLinks();
}
this.checkSize();
}
The createLinks function adds all of the lines and sets their X and Y values. The lines are being added to the DOM properly.
And the style that should be applied (but neither the svg nor the lines have any styling at all)
.bw-flowchart-svg{
position:absolute;
}
.bw-flowchart-line{
stroke: rgb(0,0,0);
stroke-width: 1;
}

The following line is incorrect
document.createElementNS("https://www.w3.org/2000/svg", type);
The SVG namespace is actually
document.createElementNS("http://www.w3.org/2000/svg", type);
This is true in SVG 1.1 and is unchanged in SVG 2. A namespace is not actually a URL despite looking like one.

Related

Smooth loading of SVGs using SNAP.SVG

I made a point-and-click-adventure-like website using Snap.SVG. It sort of works! but i'd like to improve it.
www.esad.se
(click on the arrow on the right to go to the next image!)
the biggest problem we encountered (along with the teacher helping me at that time) was to iterate through my collection of SVGs - clicking an SVG causes a new image and a new svg to be loaded into the main page. the solution we used was to point to an array containing the SVG paths and to kill the old SVG by manipulating the DOM with
event.target.parentNode.parentNode.remove()
which we though was probably not the best solution, especially because it doesn't allow for a smooth transition between svgs.
would there be a way of using a loading method to implement smooth transitions between my SVGs (for instance, a cross-fade)?
thanks for your insights.
var s = Snap("#svg");
var first = "A1.JPG"
var data = {
"A1.JPG" : {viens : "A2.JPG", svg : "1.svg"},
"A2.JPG" : {viens : "A3.JPG", svg : "2.svg", scroll : 600}
// [... etc]
}
var currentPath = data[first]
document.images.main.src = first
var mySvg = Snap.load(currentPath.svg, function(loadedFragment){
s.append(loadedFragment)
s.click(getEventElement)
window.scroll(0,0)
});
function getEventElement( event ) {
if( event.target.localName == 'svg' ) { return }
target = event.target.parentNode.id
// if (target == "noclick") {return}
if(currentPath[target] == undefined) {
return
}
document.images.main.src = currentPath[target]
currentPath = data[currentPath[target]]
//this.clear()
event.target.parentNode.parentNode.remove()
if(currentPath.hasOwnProperty("scroll")){
window.scroll(currentPath.scroll,0)
}else{
window.scroll(0,0)
}
mySvg = Snap.load(currentPath.svg, function(loadedFragment){
s.append(loadedFragment)
//s.click(getEventElement)
});
}
I'd just do it with a CSS class. Change the line where you remove the element from the DOM to:
event.target.classList.add('fade-out');
setTimeout (function () {
event.target.parentNode.removeChild(event.target);
}, 2000);
Then in your CSS for the element add:
opacity: 1;
transition: opacity 1.5s;
And add a new style for the fade-out:
.yourSvgClass.fade-out {
opacity: 0;
}

Animate text area to fit the contents of the text area

I want to resize the textarea to fit the contents of the text area.
I am currently using the following code to do that:
var element = document.getElementById(event.target.id);
var content = $(this).val().trim();
if (content == "") {
$(this).animate({
width: element.scrollWidth,
height: element.scrollHeight
}, 100);
}
When I enter nothing. I expect the textarea to become smaller in size and eventually disappear.
But it is expanding instead. :(
JSFiddle: http://jsfiddle.net/7vfjet1g/
I think what you're looking for is an elastic text area. There are a few JavaScript libraries that provide this functionality:
https://github.com/chemerisuk/better-elastic-textarea
https://github.com/chrisgeo/elastic-textarea
http://unwrongest.com/projects/elastic/
It's not much code, but a careful combination of JavaScript and CSS. If for some reason you don't want to add another library to your project, or this doesn't fit your requirement, it may point you in the right direction. Good luck.
I tested out your existing code, and the values that scrollWidth and scrollHeight were giving you were much bigger than the size of the actual text.
One way to get the size of the actual text is to create a span with the same font styling as the text area, copy over the value of the textarea to it, then append the span to the document. You can then use getBoundingRect() to get the dimensions of the span, and thus the dimensions of the text in your textarea.
NOTE: I changed the id of your enclosing div. It had the same id as the textarea, and ids are supposed to be unique. Having two elements with the same id could cause problems with the javascript. Here's the jsfiddle: https://jsfiddle.net/yog8kvx7/7/. And here's the updated blur function:
$('.notesArea').blur(function (event)
{
var content = $(this).val().trim();
var element = document.getElementById(event.target.id);
// Create span to calculate width of text
var span = document.createElement('span');
// Set position to absolute and make it hidden
// so it doesn't affect the position of any other elements
span.style.position = 'absolute';
span.style.visibility = 'hidden';
// Only set to whitespace to pre if there's content
// "pre" preserves whitespace, including newlines
if (element.value.length > 0)
span.style.whiteSpace = 'pre-wrap';
// Copy over font styling
var fontStyle = window.getComputedStyle(element);
span.style.display = 'inline-block';
span.style.padding = fontStyle.padding;
span.style.fontFamily = fontStyle.fontFamily;
span.style.fontSize = fontStyle.fontSize;
span.style.fontWeight = fontStyle.fontWeight;
span.style.lineHeight = fontStyle.lineHeight;
span.innerHTML = element.value;
// Add to document and determine width
document.body.appendChild(span);
var rect = span.getBoundingClientRect();
var width = rect.width;
var height = rect.height;
// Remove span from document
span.parentNode.removeChild(span);
$(this).animate({
width: width,
height: height
}, 100);
});
And if you want to account for the little sizing handle, so it doesn't cover the text, you can just add an offset to the width to account for it.

Detect if element has a background specified?

I'm trying to determine if an element has a background explicitly set. I figured I could just check to see if .css('background')* was set, however, it's inconsistent between browsers. For example, chrome shows an element without a background set as
background: rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box
background-color: rgba(0, 0, 0, 0)
background-image: none
whereas IE8 shows
background: undefined
background-color: transparent
background-image: none
(test case here)
*(shorthand properties of CSS aren't supported for getting rendered styles in jQuery)
Short of handling each separate case is there a better way to detect this?
temporary element approach
It's not ideal, but you could create a temporary element when your js initiates, insert it somewhere hidden in the document (because if you don't you get empty styles for webkit browsers) and then read the default background style set for that element. This would give you your baseline values. Then when you compare against your real element, if they differ you know that the background has been set. Obviously the downside to this method is it can not detect if you specifically set the background to the baseline state.
var baseline = $('<div />').hide().appendTo('body').css('background');
var isBackgroundSet = ( element.css('background') != baseline );
If you wanted to avoid possible global styles on elements, that would break the system i.e:
div { background: red; }
... you could use the following instead, but I doubt if it would work so well with older browsers:
var baseline = $('<fake />').hide().appendTo('body').css('background');
background
I spent some time with a similar issue - attempting to get the original width value from an element when set to a percentage. Which was much trickier than I had assumed, in the end I used a similar temporary element solution. I also expected, as Rene Koch does above, that the getComputedStyle method would work... really annoyingly it doesn't. Trying to detect the difference between the source CSS world and the runtime CSS world is a difficult thing.
This should work:
function isBGDefined(ele){
var img = $(ele).css('backgroundImage'),
col = $(ele).css('backgroundColor');
return img != 'none' || (col != 'rgba(0, 0, 0, 0)' && col != 'transparent');
};
DEMO
I didn't bother to test against the background property because in the end, it will change the computed styles of either backgroundImage and/or backgroundColor.
Here's the code run against your test case (with another added): http://jsfiddle.net/WG9MC/4/
this article explains how:
http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
function getStyle(oElm, strCssRule){
var strValue = "";
if(document.defaultView && document.defaultView.getComputedStyle){
strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
}
else if(oElm.currentStyle){
strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
return p1.toUpperCase();
});
strValue = oElm.currentStyle[strCssRule];
}
return strValue;
}
Using the approach suggested by #pebbl I wrote a small jQuery function, hasBack(), to determine if an element has its background set.
$.fn.hasBack = function()
{
var me = $.fn.hasBack;
if(!me.cache)
{
// get the background color and image transparent/none values
// create a temporary element
var $tmpElem = $('<div />').hide().appendTo('body');
$.fn.hasBack.cache = {
color: $tmpElem.css('background-color'),
image: $tmpElem.css('background-image')
};
$tmpElem.remove();
}
var elem = this.eq(0);
return !(elem.css('background-color') === me.cache.color && elem.css('background-image') === me.cache.image);
}
This was tested in Chrome v22, Firefox v15, Opera 12.1, IE9, IE9 set to browser modes 9 compat, 9, 8, 7 and quirks mode.
Test case here.

Can't get left position with global stylesheet in JavaScript

I need to get the upper left position of an image in JavaScript. I define the location of an image in a global style sheet:
<style type="text/css">
img.movable { position:relative; top:0px; left:375px; }
</style>
When I define the image using the global style
<img id="image11" class="movable" src="testimage.jpg" onclick="jump()" />
The style.left attribute is empty:
<script type="text/javascript">
function jump() {
xpos = document.getElementById("image11").style.left;
alert( "style.left="+xpos );
xpos = document.getElementById("image11").offsetLeft;
alert( "offsetLeft="+xpos );
}
</script>
But when I define the image using an inline style:
<img id="image11" style="position:relative; top:0px; left:375px;" src="logo.jpg" onclick="jump()" />
style.left contains a value.
This behaviour is the same for IE8 and Firefox. Any ideas why that is?
Cheers,
Martin.
el.style is actually a map of all the css properties applied using the style attribute. To get styles defined in stylesheets or default browser styles, you need to use computed style. Quirksmode, as usual, is a first stop.
The DOM element that represents the IMG tag you specified has its own style property, and its values will override any given by globals. Unfortunately in this case, the property of the element you specified will indeed be empty, as the values do not cascade down to the JavaScript object level.
You will need to get the position from either the offsetLeft property or the CSS rule itself.
.style properties only relate to inline css. Styles specified in a stylesheet will thus not be present in the .style list. If you want to get the position of an element on the page (as opposed to viewport) something like this should work:
function getNodePosition(node) {
var top = left = 0;
while (node) {
if (node.tagName) {
top = top + node.offsetTop;
left = left + node.offsetLeft;
node = node.offsetParent;
} else {
node = node.parentNode;
}
}
return [top, left];
}
Adapted from Quirksmode. OffsetTop and OffsetLeft are wrt to the parent, so this iterates up the tree to get the total offsets, and hence page position. If you want to know the position wrt the viewport, you can adjust the values returned here by the scroll distance.
Get Left Element of a given HTMLElement
function getHTMLElementLeft(HTMLElement, left)
{
if (left == undefined)
{
var left = 0;
}
if (HTMLElement.nodeType == 1)
{
left += HTMLElement.offsetLeft;
if (HTMLElement.offsetParent)
{
left = getHTMLElementLeft(HTMLElement.offsetParent, left);
}
}
return left;
}

How to find actual rendered values of elements set to 'auto' using JavaScript

Suppose I have the following html, and no CSS
<div>
here is some content in this div. it stretches it out
<br />and down too!
</div>
Now I want to get the actual pixel width and height that the browser has rendered this div as.
Can that be done with JS?
Thank you.
Try getting a reference to your div and reading the offsetWidth and offsetHeight properties:
var myDiv = document.getElementById('myDiv');
var width = myDiv.offsetWidth; // int
var height = myDiv.offsetHeight;
offsetWidth/Height cumulatively measures the element's borders, horizontal padding, vertical scrollbar (if present, if rendered) and CSS width. It's the pixel values of the entire space that the element uses in the document. I think it's what you want.
If that is not what you meant, and you'd rather only the element's width and height (i.e. excluding padding, margin, etc) try getComputedStyle:
var comStyle = window.getComputedStyle(myDiv, null);
var width = parseInt(comStyle.getPropertyValue("width"), 10);
var height = parseInt(comStyle.getPropertyValue("height"), 10);
The values above will be the final, computed pixel values for the width and height css style properties (including values set by a <style> element or an external stylesheet).
Like all helpful things, this won't work in IE.
You say you are using jQuery. Well it's trivial now, and works cross-browser:
var width = $('div').css('width');
var height = $('div').css('height');
With jQuery you don't need the first part of this answer, it's all taken care of for ya ;)
One of the benefits of using a framework, like Prototype, is that the framework authors have usually sorted out the portability issues. Even if you don't use the framework, it can still be instructive to read. In the case of Prototype, the code for reading the dimensions of an element accounts for a Safari issue and allows you to read the width of an element that is not presently dislayed.
getDimensions: function(element) {
element = $(element);
var display = $(element).getStyle('display');
if (display != 'none' && display != null) // Safari bug
return {width: element.offsetWidth, height: element.offsetHeight};
// All *Width and *Height properties give 0 on elements with display none,
// so enable the element temporarily
var els = element.style;
var originalVisibility = els.visibility;
var originalPosition = els.position;
var originalDisplay = els.display;
els.visibility = 'hidden';
els.position = 'absolute';
els.display = 'block';
var originalWidth = element.clientWidth;
var originalHeight = element.clientHeight;
els.display = originalDisplay;
els.position = originalPosition;
els.visibility = originalVisibility;
return {width: originalWidth, height: originalHeight};
},
For the jQuery framework, .height and .width do the job.

Categories

Resources