I made a hexagonal grid with an SVG pattern.
To zoom in, I used transform's scale function, and to keep it scaling from the center I set transform-origin to the center of the page.
This is perfectly working on Chrome but not on Safari and Firefox.
How can I make it work on all browsers?
Here's what I made so far:
const pattern = document.getElementById('hexagons');
const center = {
x: window.innerWidth / 2,
y: window.innerHeight / 2
};
const transformOrigin = `${center.x}px ${center.y}px`;
let zoom = 1;
const formatPatternTransform = (zoom) => `rotate(90) scale(${zoom})`;
function animate() {
if (zoom > 3)
return;
zoom += .005;
pattern.setAttribute('patternTransform', formatPatternTransform(zoom));
requestAnimationFrame(animate);
}
pattern.setAttribute('transform-origin', transformOrigin);
animate();
* {
margin: 0;
padding: 0;
}
svg {
background: #0a0a0a;
height: 100vh;
width: 100vw;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
why not simply scaling the whole SVG:
* {
margin: 0;
padding: 0;
}
svg {
position:fixed;
top:0;
left:0;
width:100%;
height:100%;
background: #0a0a0a;
animation:zoom 3s ease-out forwards
}
#keyframes zoom {
to {
transform:scale(3);
}
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
To avoid the lag you can keep you JS code:
let s = document.querySelector('svg');
let zoom = 1;
function animate() {
if (zoom > 3)
return;
zoom += .005;
s.style.transform = "scale(" + zoom + ")";
requestAnimationFrame(animate);
}
animate();
* {
margin: 0;
padding: 0;
}
svg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0a0a0a;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
You can easily zoom out too:
let s = document.querySelector('svg');
let zoom = 1;
function animate() {
if (zoom < 0.2)
return;
zoom -= .005;
s.style.transform = "scale(" + zoom + ")";
requestAnimationFrame(animate);
}
animate();
* {
margin: 0;
padding: 0;
}
svg {
position:fixed;
top:-300%;
left:-300%;
width:700%;
height:700%;
background: #0a0a0a;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
This is an alternative solution. Instead of using transform-origin you can center the <rect id="mosaic" around the origin of the svg canvas. In this case you will need a viewBox attribute: viewBox="-250 -250 500 500" and preserveAspectRatio="xMidYMid slice"
"xMidYMid (the default) - Force uniform scaling.
Align the midpoint X value of the element's viewBox with the midpoint X value of the viewport.
Align the midpoint Y value of the element's viewBox with the midpoint Y value of the viewport."
"slice - Scale the graphic such that:
aspect ratio is preserved
the entire viewport is covered by the viewBox
the viewBox is scaled down as much as possible, while still meeting the other criteria"
And this is the rectangle filled with the pattern:
<rect id="mosaic" x="-250" y="-250" width="500" height="500" fill="url(#hexagons)" />
Next comes a working example:
const pattern = document.getElementById('hexagons');
const center = {
x: window.innerWidth / 2,
y: window.innerHeight / 2
};
//const transformOrigin = `${center.x}px ${center.y}px`;
let zoom = 1;
const formatPatternTransform = (zoom) => `rotate(90) scale(${zoom})`;
function animate() {
if (zoom > 3)
return;
zoom += .005;
pattern.setAttribute('patternTransform', formatPatternTransform(zoom));
requestAnimationFrame(animate);
}
//pattern.setAttribute('transform-origin', transformOrigin);
animate();
* {
margin: 0;
padding: 0;
}
svg {
background: #0a0a0a;
height: 100vh;
width: 100vw;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-250 -250 500 500" preserveAspectRatio="xMidYMid slice">
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" x="-250" y="-250" width="500" height="500" fill="url(#hexagons)" />
</svg>
I want exact png of my svg displayed on page as an output, Is it possible?
I have my html code here :
<html>
<head>
<title>Vikas</title>
</head>
<body>
<canvas id="myCanvas" width="578" height="200"></canvas>
<svg width="50" height="50">
<circle cx="25" cy="25" r="25" fill="purple" />
</svg>
<svg width="50" height="50">
<circle cx="25" cy="25" r="25" fill="purple" />
</svg>
<svg width="100" height="100">
<circle cx="25" cy="25" r="25" fill="purple" />
</svg>
<svg width="400" height="110">
<rect width="300" height="100" style="fill:rgb(0,0,255);strokewidth:3;stroke:rgb(0,0,0)">
</svg>
</body>
</html>
There are some libraries to convert some part of the DOM to images.
This is one of them: https://github.com/tsayen/dom-to-image
Maybe you can find it useful
Try something like this example:
window.onload= function() {
domtoimage.toPng(document.getElementById('myDrawing'))
.then(function (dataUrl) {
var img = new Image();
img.src = dataUrl;
document.body.appendChild(img);
})
.catch(function (error) {
console.error('oops, something went wrong!', error);
});
};
//Libraries here:
/*! dom-to-image 24-04-2016 */
!function(a){"use strict";function b(a,b){function c(a){return b.bgcolor&&(a.style.backgroundColor=b.bgcolor),b.width&&(a.style.width=b.width+"px"),b.height&&(a.style.height=b.height+"px"),b.style&&Object.keys(b.style).forEach(function(c){a.style[c]=b.style[c]}),a}return b=b||{},Promise.resolve(a).then(function(a){return g(a,b.filter,!0)}).then(h).then(i).then(c).then(function(c){return j(c,b.width||a.scrollWidth,b.height||a.scrollHeight)})}function c(a,b){return f(a,b||{}).then(function(b){return b.getContext("2d").getImageData(0,0,a.scrollWidth,a.scrollHeight).data})}function d(a,b){return f(a,b||{}).then(function(a){return a.toDataURL()})}function e(a,b){return f(a,b||{}).then(o.canvasToBlob)}function f(a,c){function d(a){var b=document.createElement("canvas");return b.width=c.width||a.scrollWidth,b.height=c.height||a.scrollHeight,b}return b(a,c).then(o.makeImage).then(o.delay(100)).then(function(b){var c=d(a);return c.getContext("2d").drawImage(b,0,0),c})}function g(a,b,c){function d(a){return a instanceof HTMLCanvasElement?o.makeImage(a.toDataURL()):a.cloneNode(!1)}function e(a,b,c){function d(a,b,c){var d=Promise.resolve();return b.forEach(function(b){d=d.then(function(){return g(b,c)}).then(function(b){b&&a.appendChild(b)})}),d}var e=a.childNodes;return 0===e.length?Promise.resolve(b):d(b,o.asArray(e),c).then(function(){return b})}function f(a,b){function c(){function c(a,b){function c(a,b){o.asArray(a).forEach(function(c){b.setProperty(c,a.getPropertyValue(c),a.getPropertyPriority(c))})}a.cssText?b.cssText=a.cssText:c(a,b)}c(window.getComputedStyle(a),b.style)}function d(){function c(c){function d(a,b,c){function d(a){var b=a.getPropertyValue("content");return a.cssText+" content: "+b+";"}function e(a){function b(b){return b+": "+a.getPropertyValue(b)+(a.getPropertyPriority(b)?" !important":"")}return o.asArray(a).map(b).join("; ")+";"}var f="."+a+":"+b,g=c.cssText?d(c):e(c);return document.createTextNode(f+"{"+g+"}")}var e=window.getComputedStyle(a,c),f=e.getPropertyValue("content");if(""!==f&&"none"!==f){var g=o.uid();b.className=b.className+" "+g;var h=document.createElement("style");h.appendChild(d(g,c,e)),b.appendChild(h)}}[":before",":after"].forEach(function(a){c(a)})}function e(){a instanceof HTMLTextAreaElement&&(b.innerHTML=a.value),a instanceof HTMLInputElement&&b.setAttribute("value",a.value)}function f(){b instanceof SVGElement&&(b.setAttribute("xmlns","http://www.w3.org/2000/svg"),b instanceof SVGRectElement&&["width","height"].forEach(function(a){var c=b.getAttribute(a);c&&b.style.setProperty(a,c)}))}return b instanceof Element?Promise.resolve().then(c).then(d).then(e).then(f).then(function(){return b}):b}return c||!b||b(a)?Promise.resolve(a).then(d).then(function(c){return e(a,c,b)}).then(function(b){return f(a,b)}):Promise.resolve()}function h(a){return q.resolveAll().then(function(b){var c=document.createElement("style");return a.appendChild(c),c.appendChild(document.createTextNode(b)),a})}function i(a){return r.inlineAll(a).then(function(){return a})}function j(a,b,c){return Promise.resolve(a).then(function(a){return a.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),(new XMLSerializer).serializeToString(a)}).then(o.escapeXhtml).then(function(a){return'<foreignObject x="0" y="0" width="100%" height="100%">'+a+"</foreignObject>"}).then(function(a){return'<svg xmlns="http://www.w3.org/2000/svg" width="'+b+'" height="'+c+'">'+a+"</svg>"}).then(function(a){return"data:image/svg+xml;charset=utf-8,"+a})}function k(){function a(){var a="application/font-woff",b="image/jpeg";return{woff:a,woff2:a,ttf:"application/font-truetype",eot:"application/vnd.ms-fontobject",png:"image/png",jpg:b,jpeg:b,gif:"image/gif",tiff:"image/tiff",svg:"image/svg+xml"}}function b(a){var b=/\.([^\.\/]*?)$/g.exec(a);return b?b[1]:""}function c(c){var d=b(c).toLowerCase();return a()[d]||""}function d(a){return-1!==a.search(/^(data:)/)}function e(a){return new Promise(function(b){for(var c=window.atob(a.toDataURL().split(",")[1]),d=c.length,e=new Uint8Array(d),f=0;d>f;f++)e[f]=c.charCodeAt(f);b(new Blob([e],{type:"image/png"}))})}function f(a){return a.toBlob?new Promise(function(b){a.toBlob(b)}):e(a)}function g(a,b){var c=document.implementation.createHTMLDocument(),d=c.createElement("base");c.head.appendChild(d);var e=c.createElement("a");return c.body.appendChild(e),d.href=b,e.href=a,e.href}function h(){var a=0;return function(){function b(){return("0000"+(Math.random()*Math.pow(36,4)<<0).toString(36)).slice(-4)}return"u"+b()+a++}}function i(a){return new Promise(function(b,c){var d=new Image;d.onload=function(){b(d)},d.onerror=c,d.src=a})}function j(a){var b=3e4;return new Promise(function(c){function d(){if(4===g.readyState){if(200!==g.status)return void f("cannot fetch resource: "+a+", status: "+g.status);var b=new FileReader;b.onloadend=function(){var a=b.result.split(/,/)[1];c(a)},b.readAsDataURL(g.response)}}function e(){f("timeout of "+b+"ms occured while fetching resource: "+a)}function f(a){console.error(a),c("")}var g=new XMLHttpRequest;g.onreadystatechange=d,g.ontimeout=e,g.responseType="blob",g.timeout=b,g.open("GET",a,!0),g.send()})}function k(a,b){return"data:"+b+";base64,"+a}function l(a){return a.replace(/([.*+?^${}()|\[\]\/\\])/g,"\\$1")}function m(a){return function(b){return new Promise(function(c){setTimeout(function(){c(b)},a)})}}function n(a){for(var b=[],c=a.length,d=0;c>d;d++)b.push(a[d]);return b}function o(a){return a.replace(/#/g,"%23").replace(/\n/g,"%0A")}return{escape:l,parseExtension:b,mimeType:c,dataAsUrl:k,isDataUrl:d,canvasToBlob:f,resolveUrl:g,getAndEncode:j,uid:h(),delay:m,asArray:n,escapeXhtml:o,makeImage:i}}function l(){function a(a){return-1!==a.search(e)}function b(a){for(var b,c=[];null!==(b=e.exec(a));)c.push(b[1]);return c.filter(function(a){return!o.isDataUrl(a)})}function c(a,b,c,d){function e(a){return new RegExp("(url\\(['\"]?)("+o.escape(a)+")(['\"]?\\))","g")}return Promise.resolve(b).then(function(a){return c?o.resolveUrl(a,c):a}).then(d||o.getAndEncode).then(function(a){return o.dataAsUrl(a,o.mimeType(b))}).then(function(c){return a.replace(e(b),"$1"+c+"$3")})}function d(d,e,f){function g(){return!a(d)}return g()?Promise.resolve(d):Promise.resolve(d).then(b).then(function(a){var b=Promise.resolve(d);return a.forEach(function(a){b=b.then(function(b){return c(b,a,e,f)})}),b})}var e=/url\(['"]?([^'"]+?)['"]?\)/g;return{inlineAll:d,shouldProcess:a,impl:{readUrls:b,inline:c}}}function m(){function a(){return b(document).then(function(a){return Promise.all(a.map(function(a){return a.resolve()}))}).then(function(a){return a.join("\n")})}function b(){function a(a){return a.filter(function(a){return a.type===CSSRule.FONT_FACE_RULE}).filter(function(a){return p.shouldProcess(a.style.getPropertyValue("src"))})}function b(a){var b=[];return a.forEach(function(a){try{o.asArray(a.cssRules||[]).forEach(b.push.bind(b))}catch(c){console.log("Error while reading CSS rules from "+a.href,c.toString())}}),b}function c(a){return{resolve:function(){var b=(a.parentStyleSheet||{}).href;return p.inlineAll(a.cssText,b)},src:function(){return a.style.getPropertyValue("src")}}}return Promise.resolve(o.asArray(document.styleSheets)).then(b).then(a).then(function(a){return a.map(c)})}return{resolveAll:a,impl:{readAll:b}}}function n(){function a(a){function b(b){return o.isDataUrl(a.src)?Promise.resolve():Promise.resolve(a.src).then(b||o.getAndEncode).then(function(b){return o.dataAsUrl(b,o.mimeType(a.src))}).then(function(b){return new Promise(function(c,d){a.onload=c,a.onerror=d,a.src=b})})}return{inline:b}}function b(c){function d(a){var b=a.style.getPropertyValue("background");return b?p.inlineAll(b).then(function(b){a.style.setProperty("background",b,a.style.getPropertyPriority("background"))}).then(function(){return a}):Promise.resolve(a)}return c instanceof Element?d(c).then(function(){return c instanceof HTMLImageElement?a(c).inline():Promise.all(o.asArray(c.childNodes).map(function(a){return b(a)}))}):Promise.resolve(c)}return{inlineAll:b,impl:{newImage:a}}}var o=k(),p=l(),q=m(),r=n(),s={toSvg:b,toPng:d,toBlob:e,toPixelData:c,impl:{fontFaces:q,images:r,util:o,inliner:p}};"undefined"!=typeof module?module.exports=s:a.domtoimage=s}(this);
/*! #source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),c=e.webkitRequestFileSystem,f=e.requestFileSystem||c||e.mozRequestFileSystem,u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},d="application/octet-stream",s=0,l=4e4,v=function(e){var t=function(){"string"==typeof e?n().revokeObjectURL(e):e.remove()};setTimeout(t,l)},p=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(i){u(i)}}},w=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob(["\ufeff",e],{type:e.type}):e},y=function(t,u,l){l||(t=w(t));var y,m,S,h=this,R=t.type,O=!1,g=function(){p(h,"writestart progress write writeend".split(" "))},b=function(){if(m&&a&&"undefined"!=typeof FileReader){var o=new FileReader;return o.onloadend=function(){var e=o.result;m.location.href="data:attachment/file"+e.slice(e.search(/[,;]/)),h.readyState=h.DONE,g()},o.readAsDataURL(t),void(h.readyState=h.INIT)}if((O||!y)&&(y=n().createObjectURL(t)),m)m.location.href=y;else{var r=e.open(y,"_blank");void 0===r&&a&&(e.location.href=y)}h.readyState=h.DONE,g(),v(y)},E=function(e){return function(){return h.readyState!==h.DONE?e.apply(this,arguments):void 0}},N={create:!0,exclusive:!1};return h.readyState=h.INIT,u||(u="download"),r?(y=n().createObjectURL(t),void setTimeout(function(){o.href=y,o.download=u,i(o),g(),v(y),h.readyState=h.DONE})):(e.chrome&&R&&R!==d&&(S=t.slice||t.webkitSlice,t=S.call(t,0,t.size,d),O=!0),c&&"download"!==u&&(u+=".download"),(R===d||c)&&(m=e),f?(s+=t.size,void f(e.TEMPORARY,s,E(function(e){e.root.getDirectory("saved",N,E(function(e){var n=function(){e.getFile(u,N,E(function(e){e.createWriter(E(function(n){n.onwriteend=function(t){m.location.href=e.toURL(),h.readyState=h.DONE,p(h,"writeend",t),v(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&b()},"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=h["on"+e]}),n.write(t),h.abort=function(){n.abort(),h.readyState=h.DONE},h.readyState=h.WRITING}),b)}),b)};e.getFile(u,{create:!1},E(function(e){e.remove(),n()}),E(function(e){e.code===e.NOT_FOUND_ERR?n():b()}))}),b)}),b)):void b())},m=y.prototype,S=function(e,t,n){return new y(e,t,n)};return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t,n){return n||(e=w(e)),navigator.msSaveOrOpenBlob(e,t||"download")}:(m.abort=function(){var e=this;e.readyState=e.DONE,p(e,"abort")},m.readyState=m.INIT=0,m.WRITING=1,m.DONE=2,m.error=m.onwritestart=m.onprogress=m.onwrite=m.onabort=m.onerror=m.onwriteend=null,S)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!==define.amd&&define([],function(){return saveAs});
<script src="https://raw.githubusercontent.com/tsayen/dom-to-image/master/dist/dom-to-image.min.js"></script>
<html>
<head>
<title>Vikas</title>
</head>
<body>
<div id="myDrawing">
<canvas id="myCanvas" width="578" height="200"></canvas>
<svg width="50" height="50">
<circle cx="25" cy="25" r="25" fill="purple" />
</svg>
<svg width="50" height="50">
<circle cx="25" cy="25" r="25" fill="purple" />
</svg>
<svg width="100" height="100">
<circle cx="25" cy="25" r="25" fill="purple" />
</svg>
<svg width="400" height="110">
<rect width="300" height="100" style="fill:rgb(0,0,255);strokewidth:3;stroke:rgb(0,0,0)">
</svg>
</div>
</body>
</html>
(Please note that I pasted both libraries dom-to-image and FileSaver, in my javascript. You should include sepparated js files instead)
Also, I included a wrapping element with id="myDrawing"