Split SVG into pieces - Javascript - 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>

Related

Dynamically adjust a div's css zoom to fit container's size

Suppose such html code:
<editor>
<tree></tree>
</editor>
In my application, the tree is used to store user's input, for example:
'123'
'1111111111111111111111111111111111111111111111111111111'
So overflow is possible if text is too long.
I'd like to apply a css 'zoom' style to tree, to ensure it's size is smaller than editor.
How can I calculate the prefect zoom, using JavaScript?
You can effectively just scale it down step by step until it fits in the container.
Effectively this is:
Styling the elements so they will naturally overflow
Stepping down the scale 5% at a time
Stopping once the child element is smaller than it's parent
function calcSize() {
// The elements we need to use and our current scale
var editor = document.getElementById("editor")
var tree = document.getElementById("tree")
var scale = 1;
// Reset the initial scale and style incase we are resizing the page
tree.classList.add("loading");
tree.style.transform = "scale(1)";
// Loop until the scale is small enough to fit it's container
while (
(editor.getBoundingClientRect().width <
tree.getBoundingClientRect().width) &&
(scale > 0) // This is just incase even at 0.05 scale it doesn't fit, at which point this would cause an infinate loop if we didn't have this check
) {
// Reduce the scale
scale -= 0.05;
// Apply the new scale
tree.style.transform = "scale(" + scale + ")";
}
// Display the final result
tree.classList.remove("loading");
console.log("Final scale: " + Math.round(scale * 100) / 100)
}
// Run on load and on resize
calcSize();
window.addEventListener("resize", calcSize);
#editor {
display: block;
max-width: 50%;
font-size: 20px;
border: 1px solid #000;
overflow: visible;
}
#tree {
display: inline-block;
white-space: nowrap;
/* This is important as the default scale will be relative to the overflowed size */
transform-origin: 0 50%;
}
#tree.loading {
opacity: 0;
}
<editor id="editor">
<tree id="tree" class="loading">This is some overflowing text This is some overflowing text.</tree>
</editor>
(Try viewing the snippet in fullscreen and resizing the window to see it in effect)

SVG image at full viewport

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
}

Zoom inside of a div, changing the inner elements but not the div itself

I am looking for a way to zoom into a div element. Since this question did not provide some code example, I decided to post the following.
This jsfiddle already helped, but as you can see in my adapted jsfiddle, the whole div scales. I just want the image inside to scale. the div should have an overflow if zoomed in (also scroll bars).
Is this possible without including other scripts e.g. panzoom?
HTML
<div id="pane">
<img src="http://i.stack.imgur.com/oURrw.png">
</div>
JavaScript
$('#pane').bind('mousewheel DOMMouseScroll', function(e){
var stage = $(this);
scaleData = getZoom(stage);
if ( e.originalEvent.detail < 0 ){
setZoom( scaleData.curScale * '.9', stage );
}
else{
setZoom( scaleData.curScale * '1.1', stage );
}
});
function setZoom(scale, el){
scale = Math.round(scale*10)/10;
el.attr({
style:
'zoom: '+scale+';'+
'-webkit-transform: scale('+scale+');'+
'-moz-transform: scale('+scale+');'+
'-o-transform: scale('+scale+');'+
'-ms-transform: scale('+scale+');'+
'transform: scale('+scale+');'
});
}
function getZoom(el){
var curZoom = el.css('zoom');
var curScale = el.css('transform') ||
el.css('-webkit-transform') ||
el.css('-moz-transform') ||
el.css('-o-transform') ||
el.css('-ms-transform');
if ( curScale === 'none' ){
curScale = 1;
}else{
//Parse retarded matrix string into array of values
var scaleArray = $.parseJSON(curScale.replace(/^\w+\(/,"[").replace(/\)$/,"]"));
//We only need one of the two scaling components as we are always scaling evenly across both axes
curScale = scaleArray[0];
}
return { curZoom: curZoom, curScale: curScale };
}
CSS
div#pane{
height: 20em;
margin: auto;
background-color: #ffffff;
overflow: scroll;
position: relative;
z-index: 0;
}
Thank you
If I understand correctly the solution is to change the 2nd line of the JS you provided in a JSFiddle to:
var stage = $(this).find('img');
I just included .find('img') to the end of what you had there. Previously you were zooming the entire div, now it zooms just the img tag.

Setting div Background using Trianglify

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/

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