SVG image at full viewport - javascript

I have this kind of twisted bit of SVG that draws an image of Europe. It's the result of a lot of unsavoury acts. I'm not very good with SVG, either...
p27.eu
In its essentials, the code is this:
<script type="text/javascript">
<!-- compute the image dimensions, proposed code -->
var w = window;
var d = document;
var e = d.documentElement;
var g = e.getElementsByTagName('body')[0];
var x = w.innerWidth || e.clientWidth || g.clientWidth;
var y = w.innerHeight|| e.clientHeight|| g.clientHeight;
var eurlen = Math.min(x, y);
<!-- end proposed code -->
</script>
<svg
id="europe-svg"
display: block
align: xMidYMid
width="300%"
height="300%">
<g
id="layer1"
transform="translate(-107.76319,-174.21562)">
<path>...</path> <!-- one of these for each country -->
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8.37237263;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline"
id="rect10148"
width="452.38409"
height="447.94937"
x="106.52205"
y="174.79648" />
</g>
</svg>
I want the image to display full frame. I can compute the lengths I want (see proposed code, above).
But I'm having trouble getting this nightmarish abomination to scale to the viewport size. First, this is because I'm not clear how to insert the javascript variable value into the svg height/width and the (at the end) rect height/width. But also because when I modify those values manually the image doesn't respond at all in a way that is making sense to me.
Any suggestions, besides avoiding machine-generated SVG that was later modified by hand and inherited?

There are just a couple steps you need to make:
Make the container full width and height, make the svg full width and height too, here's the CSS:
#europe {
width: 100%;
height: 100%;
}
svg {
width: 100%;
height: 100%;
}
Then, remove the width and height attributes from svg element, add a viewBox attribute instead, match it to your contents of svg (I'm not sure about your drawings' size, put something like viewBox="0 0 400 400" to start). That will get you started
Oh, and remove the margin from body:
body {
margin: 0
}

Related

Split SVG into pieces - Javascript

I have a big svg tag, with lots of svg polygons, line, text inside it making a 2D map, which need overflow to see it full size on screen, something like that:
I need a way to print it from broswer when I click "print" or use "ctrl + p", but for that i need to break it into pieces and put then on column layout, so they can fit on A4 size to print the entire content, something like that:
When I try to print i get this:
So, I need a way to break this svg field into pieces to fit the page to print.
Is there any way to do that, using js, css, anything? Thank you!
There is no way to do what you want with pure CSS.
You'll need Javascript to create the split sections of the SVG.
Here's some demonstration code. I've left comments in the code to explain how it works.
The example uses a checkbox to simulate "print mode" but you could run the split and unsplit functions automatically, when printing, by listening to the beforeprint and afterprint events.
https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeprint
function splitSVGs(splitWidth) {
let splittables = document.querySelectorAll(".splittable");
splittables.forEach(function(svgElem) {
// Get starting size of the original SVG now
const computed = getComputedStyle(svgElem);
const width = parseInt(computed.width, 10);
const height = parseInt(computed.height, 10);
const vB = svgElem.viewBox.baseVal;
// Get the viewBox of the SVG also
const bbox = (svgElem.getAttribute("viewBox") !== null) ? {x:vB.x, y:vB.y, width:vB.width, height:vB.height}
: {x:0, y:0, width, height};
// Hide the original SVG
svgElem.classList.add("hide");
// Create a temporary div element to hold our generated sections
const div = document.createElement("div");
div.classList.add("sections");
svgElem.insertAdjacentElement('afterend', div);
let remainingWidth = width;
while (remainingWidth > 0) {
const sectionWidth = Math.min(splitWidth, remainingWidth);
// Convert sectionWidth relative to viewBox
bbox.width = sectionWidth * bbox.height / height;
// Create an SVG element to contain one section of the split
const section = document.createElementNS("http://www.w3.org/2000/svg", "svg");
section.setAttribute("width", sectionWidth);
// Add a viewBox that shows the area of the original that we want to see in this section
section.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(' '));
// Add a <use> element to the section SVG that references the original
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", '#'+svgElem.id);
use.setAttribute("width", vB.width);
use.setAttribute("height", vB.height);
section.appendChild(use);
// Add this section SVG to the sections div
div.appendChild(section);
// How much of the original SVG width is left?
remainingWidth -= splitWidth;
// Update bbox so the next SVG will show the next section of the original
bbox.x += bbox.width;
}
});
}
function unsplitSVGs() {
// Get rid of the generated sections
const sections = document.querySelectorAll(".sections");
sections.forEach(function(div) {
div.remove();
});
// Unhide all the original SVGs
const splittables = document.querySelectorAll(".splittable");
splittables.forEach(function(svgElem) {
svgElem.classList.remove("hide");
});
}
document.getElementById("print-mode").addEventListener("change", function(evt) {
if (evt.target.checked) {
splitSVGs(600);
} else {
unsplitSVGs();
}
});
svg {
background: linen;
}
svg#test {
width: 2960px;
height: 80px;
border: solid 2px black;
}
/* Hide while still keeping the contents visible to our section SVGs */
.hide {
position: absolute;
top: -9999px;
}
.sections svg {
border: solid 2px black;
}
.sections svg:not(:first-child) {
border-left: dashed 2px black;
}
.sections svg:not(:last-child) {
border-right: dashed 2px black;
}
<p>
<input type="checkbox" id="print-mode">
<label for="print-mode"> Simulate print mode (split the SVG)</label>
</p>
<svg viewBox="0 0 1480 40" id="test" class="splittable">
<text x="10" y="30" font-size="30px">We choose to go to the Moon in this decade and do the other things, not because they are easy, but because they are hard.</text>
</svg>

canvas setDimension() breaks drag functionality of figure

I am using draw2dtouch 6.1.66 version (latest version). Initially width and height of canvas was 1000px and 800px respectively. On button click, I am changing the canvas width and height to 2170px and 902px respectively.
Now I cannot drag rectangle figure beyond 1000px width-wise. It gets stuck to 1000px which was initial width.
Note: I check my html, both canvas div and svg shows width:2170px and height:902px.
<div _ngcontent-awi-17="" draw2d-canvas="" id="canvas" style="height: 902px; cursor: default; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); width: 2170px;">
<svg height="902" version="1.1" width="2170" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="overflow: hidden; position: absolute; left: -0.828125px;">
Here is my code:
$("#canvas").css("height",902);
$("#canvas").css("width", 2170);
canvas.setDimension(new draw2d.geo.Rectangle(0, 0, 2170, 902));
It may be and old post, but I had the same issue using same version, and my solution might be useful for some other people coming back.
It seems when the canvas is created Canvas.regionDragDropConstraint is initialized with the original size of the canvas, restrincting the area in which objects can be dragged inside. I had to modify the method Canvas.setDimension, and added the update of the attribute, so when resized, it's updated in all the figures referencing the same object.
/**
* #method
* Tells the canvas to resize. If you do not specific any parameters
* the canvas will attempt to determine the height and width by the enclosing bounding box
* of all elements and set the dimension accordingly. If you would like to set the dimension
* explicitly pass in an draw2d.geo.Rectangle or an object with <b>height</b> and <b>width</b> properties.
*
* #since 4.4.0
* #param {draw2d.geo.Rectangle} [dim] the dimension to set or null for autodetect
*/
setDimension: function(dim, height)
{
if (typeof dim === "undefined"){
var widths = this.getFigures().clone().map(function(f){ return f.getAbsoluteX()+f.getWidth();});
var heights = this.getFigures().clone().map(function(f){ return f.getAbsoluteY()+f.getHeight();});
this.initialHeight = Math.max.apply(Math,heights.asArray());
this.initialWidth = Math.max.apply(Math,widths.asArray());
}
else if(dim instanceof draw2d.geo.Rectangle){
this.initialWidth = dim.w;
this.initialHeight = dim.h;
}
else if(typeof dim.width ==="number" && typeof dim.height ==="number"){
this.initialWidth = dim.width;
this.initialHeight = dim.height;
}
else if(typeof dim ==="number" && typeof height ==="number"){
this.initialWidth = dim;
this.initialHeight = height;
}
this.html.css({"width":this.initialWidth+"px", "height":this.initialHeight+"px"});
this.paper.setSize(this.initialWidth, this.initialHeight);
this.setZoom(this.zoomFactor, false);
// MODIFICATION START TO CORRECT DRAG AND DROP PROBLEM
// Add modification to the Region Drap and Drop Policy, so it does not restricts objects movements on resize.
this.regionDragDropConstraint.constRect.w = this.getWidth();
this.regionDragDropConstraint.constRect.h = this.getHeight();
// MODIFICATION END
return this;
}
Setting the width and height of a canvas using css, will result in scale like effect, because the underlying imageData array is not updated to the new dimensions.
So if you need to scale your canvas, use the attributes of the canvas, like so:
$('#canvas').attr({width: <width>, height: <height>});
Keep in mind that changing those values will clear the canvas and you need to redraw everything.

Getting SVG container size in snapSVG?

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>

Programmatically Resizing Divs

I'm working on an HTML5 browser game that can be divided into 3 parts: two UI panels on the left and right of a center set of square canvases for the playing surface. The three panels need to be horizontally aligned, and the total game needs to keep an aspect ratio of 16:9. The left and right panels should be of equal widths, and all three panels must be of equal height. I have specified a minimum width and height inside a resize() function called when an onresize event is detected.
Currently, each panel is a div, and all three are contained inside a section. Right now, the section isn't necessary, but I want to keep the game separated from extra content at the bottom of the screen that I might choose to add later.
The CSS style is as follows:
* {
vertical-align: baseline;
font-weight: inherit;
font-family: inherit;
font-style: inherit;
font-size: 100%;
border: 0 none;
outline: 0;
padding: 0;
margin: 0;
}
#gameSection {
white-space: nowrap;
overflow-x: hide;
overflow-y: hide;
}
#leftPanel, #centerPanel, #rightPanel {
display: inline-block;
}
#leftPanel {
background-color: #6495ed;
}
#centerPanel {
background-color: #e0ffff;
}
#rightPanel {
background-color: #b0c4de;
Right now, I have set the background color of each div just to show me when I'm correctly setting the size of each div.
The body of my HTML document is as follows:
<body onresize="resize()">
<section id="gameSection">
<div id="leftPanel">Left Panel.</div>
<div id="centerPanel">Center Panel.</div>
<div id="rightPanel">Right Panel.</div>
</section>
</body>
And finally, my resize() function (I created a separate function for resizing the game in case I add more elements below later):
function resize() {
var MIN_GAME_WIDTH = 800;
var MIN_GAME_HEIGHT = 450;
var GAME_ASPECT_RATIO = 16 / 9;
var width = window.innerWidth;
var height = window.innerHeight;
var gWidth, gHeight;
if(width < MIN_GAME_WIDTH || height < MIN_GAME_HEIGHT) {
gWidth = MIN_GAME_WIDTH;
gHeight = MIN_GAME_HEIGHT;
}
else if ((width / height) > GAME_ASPECT_RATIO) {
<!-- width is too large for height -->
gHeight = height;
gWidth = height * GAME_ASPECT_RATIO;
}
else {
<!-- height is too large for width -->
gWidth = width;
gHeight = width / GAME_ASPECT_RATIO;
}
resizeGame(gWidth, gHeight, GAME_ASPECT_RATIO);
}
function resizeGame(var gWidth, var gHeight, var aspectRatio) {
var gSection = document.getElementById("gameSection");
var lPanel = document.getElementById("leftPanel");
var cPanel = document.getElementById("centerPanel");
var rPanel = document.getElementById("rightPanel");
gSection.height = gHeight;
gSection.width = gWidth;
<!-- should the below be taken care of in the CSS? -->
lPanel.height = gHeight;
cPanel.height = gHeight;
rPanel.height = gHeight;
cPanel.width = cPanel.height;
lPanel.width = (gWidth - cPanel.width) / 2;
rPanel.width = lPanel.width;
}
I've tried a number of different commands to resize the divs, but it just isn't working for me. When I try adding test canvases, color appears, but the boxes still aren't the correct size. I have also considered loading an invisible background image to each div and scaling it to the desired size; however, I was able to resize my canvas using the above method before and it seemed to work just fine.
Additional Notes
While I've already had pretty good success resizing a single canvas, I don't want to use just one canvas for the game because not all parts of the UI need to be drawn at the same time.
I'm trying to keep this solely in Javascript.
I suspect that I could just use CSS to handle resizing by fixing the aspect ratio to 16:9 and using width:56.25% for the center panel and width:21.875% for the side panels, but that limits me to one aspect ratio and doesn't explain why my above script isn't working.
I can provide the entire HTML file if needed. This is what it's supposed to look like:
End Goal (without right panel)
Thank you!
UDPATE:
jsfiddle
I got it kind of working here. I made a lot of changes/minor fixes to the code before finding what was wrong (other than various syntax errors):
You were using .width and .height instead of .style.width and .style.height, and you were applying integers to these instead of strings with "px" appended to them. Both of these things are completely understandable to miss.
I also moved the onresize from the body tag into the JS, don't know why it wasn't working on jsfiddle, but this is good practice anyways.
In the future: learn how to debug JS using the console and when you ask questions, use small examples, not your entire codebase. This question could have been simplified to "How do I resize a div?" with one line of JS and one div. You also should consider not doing this specific thing in JS, and using flexbox as redbmk said.

Creating masks across browsers

I see that there are ways to do this in webkit browsers, but I don't see ways to do it in others. Is this simply a feature that hasn't been implemented in all the browsers?
I don't have standard images, so clip won't work. I may have to render everything ahead of time, which will make my work exponential, but you deal with what you have, right?
I'd also want to be able to activate this stuff from javascript. :/
Thanks if you can provide support.
Just off the top of my head - and without an actual problem from you for us to solve - here's a possible way to accomplish what you want...
HTML
<div class="myImage">
<img src="path_to_image" title="Lorem ipsum" alt="Dolar sit amet" />
<div class="myMask">
</div><!-- /myMask -->
</div><!-- /myImage -->
CSS
.myImage {
position: relative;
}
.myMask {
position:absolute;
top: 0;
left: 0;
background-color: transparent;
background-image: url('path_to_masking_image');
}
Alternatively, use an <img /> inside the myMask div, and remove the background-image property from the CSS.
The way it's currently laid out, you would need two images: the image itself in all its glory, and the mask.
The way you would accomplish the 'masking effect' is to have the mask image be a static solid color that matches background of the container its in - ie white, black, whatever.
Kapeesh? This would work in all browsers, which is what you asked for. The background-clip property has -webkit and -moz selectors, but is not supported in browsers like IE or (to my knowledge) Opera.
Here are my 2 cents, if it is indeed CSS Sprites you are after.
<head>
<style type="text/css"><!--
#imagemask {
background-image: url(image.png);
background-repeat: no-repeat;
background-color: transparent;
height: 40px;
width: 40px;
}
.mask1 { background-position: top left; }
.mask2 { background-position: 0 40px; }
.mask3 { background-position: 0 80px; }/* And so on, however your image file is 'layed out' */
--></style>
<script type="text/javascript">
function mask1(){ document.getElementById("imagemask").setAttribute("class", "mask1"); }
function mask2(){ document.getElementById("imagemask").setAttribute("class", "mask1"); }
function mask3(){ document.getElementById("imagemask").setAttribute("class", "mask1"); }
</script>
</head>
<body>
mask 1
mask 2
mask 3
<div id="imagemask" class="mask1"></div>
</body>
We define the div#imagemask to contain 1 image file as a background and set it to not repeat around, as that would sort of defy the point.
We define how to "move around" the image inside the "mask" (div) with fixed width and height.
As a reference, I've then added the javascript you need to switch between the masks on the fly. I wrote that in about 10 seconds, you could probably write something a little more elegant if you want.
Add the links with onclick= events
Finally, add the div#imagemask to the body.
Given that I don't know the width or height of your image file or it's target masking, you'll have to do some substantial edits to this code. But you get the idea :)
I'm just going to skip the CSS variant in favor of this:
Example of a working mask: http://gumonshoe.net/NewCard/MaskTest.html
I acquired a javascript class from another website tutorial:
http://gumonshoe.net/js/canvasMask.js
It reads the image data and applies the alpha pixels from the mask to the target image:
function applyCanvasMask(image, mask, width, height, asBase64) {
// check we have Canvas, and return the unmasked image if not
if (!document.createElement('canvas').getContext) return image;
var bufferCanvas = document.createElement('canvas'),
buffer = bufferCanvas.getContext('2d'),
outputCanvas = document.createElement('canvas'),
output = outputCanvas.getContext('2d'),
contents = null,
imageData = null,
alphaData = null;
// set sizes to ensure all pixels are drawn to Canvas
bufferCanvas.width = width;
bufferCanvas.height = height * 2;
outputCanvas.width = width;
outputCanvas.height = height;
// draw the base image
buffer.drawImage(image, 0, 0);
// draw the mask directly below
buffer.drawImage(mask, 0, height);
// grab the pixel data for base image
contents = buffer.getImageData(0, 0, width, height);
// store pixel data array seperately so we can manipulate
imageData = contents.data;
// store mask data
alphaData = buffer.getImageData(0, height, width, height).data;
// loop through alpha mask and apply alpha values to base image
for (var i = 3, len = imageData.length; i < len; i = i + 4) {
imageData[i] = alphaData[i];
}
// return the pixel data with alpha values applied
if (asBase64) {
output.clearRect(0, 0, width, height);
output.putImageData(contents, 0, 0);
return outputCanvas.toDataURL();
}
else {
return contents;
}
}

Categories