What is the correct way to get the size of the 'paper' object with SnapSVG, as soon as it has been created?
My HTML looks something as follows:
<div id="myContainer" style="width: 900px; height: 100px" />
And then the Javascript code:
function initViewer(elementId) {
var element, elementRef, snap;
elementRef = '#' + elementId;
element = $(elementRef);
element.append('<svg style="width: ' + element.width() + 'px; height: ' + element.height() + 'px; border: solid 1px black;"/>');
snap = Snap(elementRef + ' svg');
console.log(snap.getBBox());
}
What I observe here is the bounding box has '0' for all attributes, so I can't rely on the bounding box values here. Are there any ways of doing this, without have to go to a parent element?
What I am essentially wanting form all this is the width and the height of the SVG, so I can draw the shapes of the appropriate size for the view.
JS Fiddle, illustrating the issue: https://jsfiddle.net/ajmas/kdnx2eyf/1/
getBBox() on a canvas returns the bounding box that contains all elements on that canvas. Since there are no elements in your SVG, it returns all 0s.
But the SVG element is just like any other DOM element - you could get its width and height in a number of different ways. You could retrieve the object by ID or whatever and use .offsetWidth or .offsetHeight.
You could also traverse the object itself. I have no idea if this works on all browsers but if you really want to use the snap object, you could do this:
snap=Snap(elementRef + ' svg');
snap.node.clientHeight
snap.node.clientWidth
But you also just set the height and width of it using the div it is contained in. Why can't you just use element.width() and element.height()?
I find that getBBox() doesn't work on a paper (a Snap "drawing surface"), only on elements in a paper. But node.clientWidth works for me for Snap.svg papers. Demo below.
var paper = Snap("#mySVG");
paper.rect(0, 0, 200, 100).attr({fill : "#cde"});
//var tMessage0 = paper.getBBox().width; // throws an error
var tMessage1 = paper.text(4, 24, "paper width = " + paper.node.clientWidth);
var tMessage2 = paper.text(4, 48, "text width = " + tMessage1.getBBox().width);
<script src="https://cdn.jsdelivr.net/snap.svg/0.1.0/snap.svg-min.js"></script>
<body>
<svg id="mySVG" width="200" height="100">
</svg>
</body>
Related
I want to draw two curved arrow lines using SVG to connect two elements to indicate they go back and forth, like this:
I've read a bit about SVG but I'm not totally sure how to create a line that's vertical.
Second, if SVG takes coordinates, do I have to find the coordinate position of the elements before creating the SVG drawing? Does it have to be re-drawn if the window size is adjusted?
Make an svg element that (invisibly) underlies the entire document. This will hold both arrows. Insert two svg path elements (the arrows) whose start and end coordinates are calculated based on the positions of the div's to be connected, and whose curve is created in whatever way you want based on those start and end coordinates.
For the example below, click on "Run code snippet". Then click and drag either of the div's to see how the arrows are dynamically created, i.e. they move with the divs. jQuery and jQueryUI are used in the code snippet simply to allow the easy draggability of the divs and have nothing to do with the creation and use of the arrows.
This example has two arrows starting and ending at the middle of the divs' sides. The details of the curve are, of course, up to you. The arrow lines are constructed using the d attribute of the svg path. In this example, "M" is the "moveTo" coordinates where the path will start and the "C" points are the first and second control points and final coordinate for a cubic bezier curve. You'll have to look those up to understand what they are, but they are a general way of creating smooth curves in an svg element. The arrowheads are added using an svg <marker> element which you can read about here.
A more complex document would need more care to determine the start and end coordinates of the svg path elements, i.e. the arrows, but this example at least gives you a place to begin.
Answers to your specific questions:
If SVG takes coordinates, do I have to find the coordinate position of the elements before creating the SVG drawing? Yes, as I've done in my code.
Does it have to be re-drawn if the window size is adjusted? Probably yes, depending on what happens to the divs themselves when the window is resized.
var divA = document.querySelector("#a");
var divB = document.querySelector("#b");
var arrowLeft = document.querySelector("#arrowLeft");
var arrowRight = document.querySelector("#arrowRight");
var drawConnector = function() {
var posnALeft = {
x: divA.offsetLeft - 8,
y: divA.offsetTop + divA.offsetHeight / 2
};
var posnARight = {
x: divA.offsetLeft + divA.offsetWidth + 8,
y: divA.offsetTop + divA.offsetHeight / 2
};
var posnBLeft = {
x: divB.offsetLeft - 8,
y: divB.offsetTop + divB.offsetHeight / 2
};
var posnBRight = {
x: divB.offsetLeft + divB.offsetWidth + 8,
y: divB.offsetTop + divB.offsetHeight / 2
};
var dStrLeft =
"M" +
(posnALeft.x ) + "," + (posnALeft.y) + " " +
"C" +
(posnALeft.x - 100) + "," + (posnALeft.y) + " " +
(posnBLeft.x - 100) + "," + (posnBLeft.y) + " " +
(posnBLeft.x ) + "," + (posnBLeft.y);
arrowLeft.setAttribute("d", dStrLeft);
var dStrRight =
"M" +
(posnBRight.x ) + "," + (posnBRight.y) + " " +
"C" +
(posnBRight.x + 100) + "," + (posnBRight.y) + " " +
(posnARight.x + 100) + "," + (posnARight.y) + " " +
(posnARight.x ) + "," + (posnARight.y);
arrowRight.setAttribute("d", dStrRight);
};
$("#a, #b").draggable({
drag: function(event, ui) {
drawConnector();
}
});
setTimeout(drawConnector, 250);
/* The setTimeout delay here is only required to prevent
* the initial appearance of the arrows from being
* incorrect due to the animated expansion of the
* Stack Overflow code snippet results after clicking
* "Run Code Snippet." If this was a simpler website,
* a simple command, i.e. `drawConnector();` would suffice.
*/
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#instructions {
position: fixed;
left: 50%;
}
#a, #b {
color: white;
text-align: center;
padding: 10px;
position: fixed;
width: 100px;
height: 20px;
left: 100px;
}
#a {
background-color: blue;
top: 20px;
}
#b {
background-color: red;
top: 150px;
}
<p id="instructions">Click and drag either div to see automatic arrow adjustments.</p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<defs>
<marker id="arrowhead" viewBox="0 0 10 10" refX="3" refY="5"
markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<g fill="none" stroke="black" stroke-width="2" marker-end="url(#arrowhead)">
<path id="arrowLeft"/>
<path id="arrowRight"/>
</g>
</svg>
<div id="a">Div 1</div>
<div id="b">Div 2</div>
I found Andrew Willems's answer very useful. I've modified it to make a library, draw_arrow.js , which exports a function draw_arrow( sel1, locs1, sel2, locs2, arr ). This draws an arrow from the element identified by CSS selector sel1 to that identified by sel2. locs1 and locs2 indicate where the arrow should start or end on the element. arr identifies an SVG path to hold the arrow.
You can download this, and see two demos, from the links at the end of http://www.chromophilia.uk/blog/dress-reform-architecture-and-modernism/ . I needed the arrows to depict the relationships between various topics related to Modernism, as part of an animation. That's what drove me to find and adapt Andrew's code. NOTE: that link isn't currently working, because of some problem with the WordPress database, which I'll have to fix. The arrows library and a library for successively displaying HTML elements, plus demos, can be got via the links in my comment to Henry Mont below.
Here's a suggested improvement. I originally wrote this up as a new, additional, answer, but several commenters have execrated that, so I'll have to put it here and hope it gets noticed. I'm pursuing this because modularity is important. A routine such as draw_arrow should require its user to do as little as possible to the code around it. But at the moment, it needs the user to create one <path> element inside the <svg> for each arrow to be drawn, and to invent IDs for the paths. I suggest it would be better for draw_arrow to do this, by updating the DOM tree. Comments in favour or against?
We finally have it! Take a look at this:
https://www.npmjs.com/package/arrows-svg
there is also a React version:
https://www.npmjs.com/package/react-arrows
So if you have two divs with let's say ids named: from and to according to divs from your example, then you do:
import arrowCreate, { DIRECTION } from 'arrows'
const arrow = arrowCreate({
className: 'arrow',
from: {
direction: DIRECTION.LEFT,
node: document.getElementById('from'),
translation: [-0.5, -1],
},
to: {
direction: DIRECTION.LEFT,
node: document.getElementById('to'),
translation: [0.9, 1],
},
})
/*
- arrow.node is HTMLElement
- arrow.timer is idInterval from setInterval()
REMEMBER about clearInterval(node.timer) after unmount
*/
document.body.appendChild(arrow.node);
and of course some css:
.arrow {
pointer-events: none;
}
.arrow__path {
stroke: #000;
fill: transparent;
stroke-dasharray: 4 2;
}
.arrow__head line {
stroke: #000;
stroke-width: 1px;
}
Tested and it works!
I have some problems using the Trianglify plugin. I would like to use it to set the background of a div. How can I do this? I couldn't find a proper example.
Here's my sample code:
<script>
var pattern = Trianglify({
width: window.innerWidth,
height: window.innerHeight
});
document.body.appendChild(pattern.canvas())
</script>
Also, can I have divs with different backgrounds that come from Trianglify?
One DIV
Here is an example of setting a DIV background to a Trianglify pattern. It cheats a bit and sets the DIV child node to the pattern but it should work for you.
var something = document.getElementById('something');
var dimensions = something.getClientRects()[0];
var pattern = Trianglify({
width: dimensions.width,
height: dimensions.height
});
something.appendChild(pattern.canvas());
The DIV has an id of something and the CSS styles are set on the div for height and width.
Working example JSFiddle: http://jsfiddle.net/u55cn0fh/
Multiple DIVs
We can easily expand this for multiple DIVs like so:
function addTriangleTo(target) {
var dimensions = target.getClientRects()[0];
var pattern = Trianglify({
width: dimensions.width,
height: dimensions.height
});
target.appendChild(pattern.canvas());
}
JSFiddle: http://jsfiddle.net/u55cn0fh/1/
Multiple DIVs as a true background-image
The above are simply appending the pattern to the DIV as a child node instead of setting it as a background. The good news is that we can indeed use the background-image CSS property like so:
function addTriangleTo(target) {
var dimensions = target.getClientRects()[0];
var pattern = Trianglify({
width: dimensions.width,
height: dimensions.height
});
target.style['background-image'] = 'url(' + pattern.png() + ')';
}
JSFiddle: http://jsfiddle.net/abL2kc2q/
I'm pretty late to this answer, but I think it's still valuable:
If you aren't satisfied with using pattern.png() to generate a PNG version (like I wasn't), then you can also use pattern.svg() to set a SVG background with a little more work.
I always tend to lean towards using SVG backgrounds as typically they are crisper. In a test case I ran, using the SVG version also saved bits (although it's relative because sending a Trianglify background is a bit of a burden to begin with).
Characters in the base64 SVG encoding: 137284
Characters in the base64 PNG encoding: 195288
Converting the SVG to a base64 encoding then setting it as the background image can be achieved as follows:
// Create the Trianglify pattern
var pattern = Trianglify({
cell_size: 30,
variance: 0.75,
x_colors: 'random',
y_colors: 'match_x',
palette: Trianglify.colorbrewer,
stroke_width: 1.51,
});
// Serialize the SVG object to a String
var m = new XMLSerializer().serializeToString(pattern.svg());
// Perform the base64 encoding of the String
var k = window.btoa(m);
// Query the element to set the background image property
var element = document.getElementsByTagName('header')[0];
// Set the background image property, including the encoding type header
element.style.backgroundImage = 'url("data:image/svg+xml;base64,' + k + '")';
Hope this helps!
IN my case I had to use a class to use multiple instances of the same background image :
var obj = {
'trianglifiedlightblue': ['#a5cade', '#b7d5e5', '#d5e6f0', '#006ab4', '#e8f2f7', '#cee2ed'],
'trianglifiedbleu': ['#004e83', '#005f9f', '#004879', '#006ab4', '#004777', '#005f9f'],
'trianglifiedviolet': ['#680036', '#830447', '#e62f8e', '#c76c9b'],
'trianglifiedrouge': ['#5f0308', '#851117', '#cf363f', '#e86d74']
};
function addTriangle(classname) {
targets = document.getElementsByClassName(classname);
for (i = 0; i < targets.length; i++) {
target = targets[i];
if (target != null) {
var dimensions = target.getClientRects()[0];
var pattern = Trianglify({
width: dimensions.width,
height: dimensions.height,
x_colors: obj[classname],
cell_size: 100 + Math.random() * 200
});
target.style['background-image'] = 'url(' + pattern.png() + ')';
}
}
}
addTriangle('trianglifiedlightblue');
addTriangle('trianglifiedbleu');
addTriangle('trianglifiedviolet');
addTriangle('trianglifiedrouge');
div {
height: 100px;
width: 500px;
margin:10px;
float:left;
background:#efefef;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/trianglify/0.2.1/trianglify.min.js"></script>
<div class="trianglifiedlightblue"></div>
<div class="trianglifiedbleu"></div>
<div class="trianglifiedviolet"></div>
<div class="trianglifiedrouge"></div>
or if you prefer to keep an ID you can try this :
http://jsfiddle.net/thecpg/Lecdhce6/10/
I would like to know what the best method is to calculate the width and height of an element with Jquery/Javascript (or any other method that might be more accurate). Currently, I am using this Jquery method:
Jquery: See my example: http://jsfiddle.net/AwkF5/1/
var w = $("#wrapper"); //The element dimensions to calculate
$("#displayW").text( "outerWidth:" + w.outerWidth()+ " , outerWidth(true):" + w.outerWidth(true) ); // Displaying the width in the #displayW div
$("#displayH").text( "outerHeight:" + w.outerHeight()+ " , outerHeight(true):" + w.outerHeight(true) ); // Displaying the height in the #displayH div
Now is this the most accurate way to calculate the width/height(including content, padding and border) and TRUE width/height ((including content, padding, border and margin) of an element?
What would be the plain javascript method?
I'm asking because I want to make a background image for the div and I want to know what size it should be...Note that the width and height varies between different browsers...obviously
Thank You
This should work for you:
var realWidth = $("#yourBlockId").outerWidth();
realWidth += parseInt($("#yourBlockId").css("margin-left"), 10);
realWidth += parseInt($("#yourBlockId").css("margin-right"), 10);
The same approach for height.
I have something vaguely like the following:
<div id="body">
surrounding text
<div id="pane" style="overflow: auto; height: 500px; width: 500px;">
lots and lots of text here
<span id="some_bit">tooltip appears below-right of here</span>
</div>
more surrounding text (should be overlapped by tooltip)
</div>
and:
<div id="tooltip" style="width: 100px; height: 100px;">Whee</div>
What I want to do is insert the tooltip such that it is positioned above the pane it's in. If it's attached to an element that's next to the pane boundary (like above), then it should be visible above the pane, and above the text surrounding the pane.
It should NOT a) extend the pane, such that you have to scroll down to see the tooltip (like in http://saizai.com/css_overlap.png), or b) be cut off, so you can't see all of the tooltip.
I'm inserting this with JS, so I can add a wrapper position:relative div or the like if needed, calculate offsets and make it position:absolute, etc. I would prefer to not assume anything about the pane's position property - the tooltip should be insertable with minimal assumptions of possible page layout. (This is just one example case.)
It's for a prototype tooltip library I'm writing that will be open source.
ETA: http://jsfiddle.net/vCb2y/5/ behaves visually like I want (if you keep re-hovering the trigger text), but would require me to update the position of the tooltip on all DOM changes and scrolling behavior. I would rather the tooltip be positioned with pure CSS/HTML so that it has the same visual behavior (i.e. it overlaps all other elements) but stays in its position relative to the target under DOM changes, scrolling, etc.
ETA 2: http://tjkdesign.com/articles/z-index/teach_yourself_how_elements_stack.asp (keep defaults except set cyan div 'a' to position:relative; imagine 'A' is the pane and 'a' the tooltip) seems to more closely behave as I want, but I've not been able to get it to work elsewhere. Note that if you make 'A' overflow: auto, it breaks the overlapping behavior of 'a'.
I can't think of a pure HTML/CSS solution for this.
The overflow declaration is the issue here. If the tooltip is in #pane:
you establish a positioning context within #pane, then the tooltip shows next to #some_bit (regardless of scrolling, etc.) but it gets cut-off.
you do not establish a positioning context, then the tooltip is not clipped but it has no clue where #some_bit is on the page.
I'm afraid you'll need JS to monitor where #some_bit is on the page and position the tooltip accordingly. You'd also need to kill that tooltip as soon as #some_bit is outside of the viewing area (not an issue if the trigger is mouseover).
Actually, if the trigger is mouseover then you may want to use the cursor coordinates to position the tooltip (versus calculating the position of #some_bit).
I would just put the tooltip outside of the #pane div and position it absolutely using JavaScript since you're using JS anyway.
I don't use Prototype so I don't know how it's done in Prototype, but in jQuery, you'd use $(element).position() to get the element position. If you have to do it manually, it's a little more complicated.
And you'll probably want to add a little extra logic to prevent the tooltip from extending outside of the document.
Edit: CSS used
#tooltip {
z-index: 9999;
display: none;
position: absolute;
}
JS used
Note: in jQuery, but it should be easy to change it to Prototype syntax.
$('#some_bit').hover(function() {
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
// hovered element
var offset = $(this).offset();
var top = offset.top + docViewTop;
var left = offset.left;
var width = $(this).width();
var height = $(this).height();
var right = left + width;
var bottom = top + height;
// pane
var poffset = $('#pane').offset();
var ptop = poffset.top + docViewTop;
var pleft = poffset.left;
var pwidth = $('#pane').width();
var pheight = $('#pane').height();
var pright = pleft + pwidth;
var pbottom = ptop + pheight;
// tooltip
var ttop = bottom;
var tleft = right;
var twidth = $('#tooltip').width();
var theight = $('#tooltip').height();
var tright = tleft + twidth;
var tbottom = ttop + theight;
if (tright > pright)
tleft = pright - twidth;
if (tbottom > pbottom)
ttop = pbottom - theight;
if (tbottom > docViewBottom)
ttop = docViewBottom - theight;
$('#tooltip').offset({top: ttop, left: tleft});
$('#tooltip').css('display', 'block');
}, function() {
$('#tooltip').hide();
});
Edit: See it here.
Clicking on the image should show div near it (.show() in jQuery).
But how can i attach div to that image? Is it done with pure css, or javascript?
I tried several "position:absolute", but can't attach it near image.
How it should be done?
It's pretty straightforward, you need to compute the .css({top:___,left:___}) such that the underlines are filled with computations based on the clicked image's .position().top and .position().left.
something like this:
$(document).ready(function() {
$('#someim').click(function() {
showDiv($(this), $('#somediv'));
});
});
function showDiv(sender, object) {
var pos = $(sender).offset();
var width = $(sender).width();
$(object).css({ "left": (pos.left + width) + "px", "top": pos.top + "px" });
$(object).show();
}
<img id="someim" width="250" height="61" alt="Stack Overflow" src="http://sstatic.net/so/img/logo.png">
<div id="somediv" style="display:none; margin-left:10px; color:Red">sd</div>