Is there a way to streamline canvas code - javascript

This is a bit of a strange issue but I have a world map that is produced from lines of canvas code.
The canvas code is derived from an SVG file that was automatically converted by a website but it produced 47000 lines of code & a 1.5mb file size.
This obviously takes some time to load and occasionally it doesn't appear (it currently resides in a .js file that is remote loaded).
Is there a way to streamline this code to reduce the file size.
I have thought about transferring all the line coordinates into a sql table and producing it that way but I'm not sure if that would be any better.
Example code:
function world(scale) {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.scale(scale,scale);
ctx.save();
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(39960,0);
ctx.lineTo(39960,19980);
ctx.lineTo(0,19980);
ctx.closePath();
ctx.clip();
ctx.strokeStyle = "#ffffff";
ctx.lineCap = "butt";
ctx.lineJoin = "miter";
ctx.miterLimit = 4;
ctx.save();
ctx.restore();
ctx.save();
ctx.restore();
ctx.save();
ctx.save();
ctx.fillStyle = "#bcbcbc";
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 5.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.miterLimit = 4;
ctx.beginPath();
ctx.moveTo(14553,0);
ctx.lineTo(14528,2);
ctx.lineTo(14502,2);
ctx.lineTo(14476,2);
ctx.lineTo(14448,4);
ctx.lineTo(14464,17);
ctx.lineTo(14436,15);
ctx.lineTo(14413,11);
ctx.lineTo(14395,4);

function CanvasProxy(target){
var Host=new Object;
for (var v in target)
switch (typeof target[v]) {
case "function":Host[v]=addMethod.bind(v);break;
case "number":setProperty(v);break;
case "string":setProperty(v);break;
case "object":Host[v]=target[v];break;
default:setSimpleProperty(v)
};
return Host;
function addMethod(){
var ret=target[this].apply(target,arguments);
if (typeof ret==="undefined") return Host;
Host[this]=target[this];
return ret;
}
function setProperty(p){
function property(v){target[p]=v;return Host;}
property.toString=property.valueOf=function(){return target[p]}
Object.defineProperty(Host, p , {get : function(){return property}, set : function(v){ target[p] = v}});
}
function setSimpleProperty(p){
Object.defineProperty(Host, p , {get : function(){return target[p]}, set : function(v){ target[p] = v}});
}
}
var ctx;
function _init(){
var canvas = document.getElementById("myCanvas");
ctx = CanvasProxy(canvas.getContext("2d"));
}
function world(scale) {
ctx.scale(scale,scale)
.save()
.beginPath()
.moveTo(0,0)
.lineTo(39960,0)
.lineTo(39960,19980)
.lineTo(0,19980)
.closePath()
.clip()
.strokeStyle("#ffffff")
.lineCap("butt")
.lineJoin("miter")
.miterLimit(4)
.save().restore().save().restore().save().save().fillStyle("#bcbcbc").....

Related

Erasing only paticular element of canvas in JS

I want to create something like scratch card.
I created a canvas and added text to it.I than added a box over the text to hide it.Finally write down the code to erase(scratch) that box.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "30px Arial";
ctx.fillText("Hello World",10,50);
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle='red';
ctx.fillRect(0,0,500,500);
function myFunction(event) {
var x = event.touches[0].clientX;
var y = event.touches[0].clientY;
document.getElementById("demo").innerHTML = x + ", " + y;
ctx.globalCompositeOperation = 'destination-out';
ctx.arc(x,y,30,0,2*Math.PI);
ctx.fill();
}
But the problem is it delete the text also.
How could I only delete that box not the text?
Canvas context keeps only one drawing state, which is the one rendered. If you modify a pixel, it won't remember how it was before, and since it has no built-in concept of layers, when you clear a pixel, it's just a transparent pixel.
So to achieve what you want, the easiest is to build this layering logic yourself, e.g by creating two "off-screen" canvases, as in "not appended in the DOM", one for the scratchable area, and one for the background that should be revealed.
Then on a third canvas, you'll draw both canvases every time. It is this third canvas that will be presented to your user:
var canvas = document.getElementById("myCanvas");
// the context that will be presented to the user
var main = canvas.getContext("2d");
// an offscreen one that will hold the background
var background = canvas.cloneNode().getContext("2d");
// and the one we will scratch
var scratch = canvas.cloneNode().getContext("2d");
generateBackground();
generateScratch();
drawAll();
// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;
function drawAll() {
main.clearRect(0,0,canvas.width,canvas.height);
main.drawImage(background.canvas, 0,0);
main.drawImage(scratch.canvas, 0,0);
}
function generateBackground(){
background.font = "30px Arial";
background.fillText("Hello World",10,50);
}
function generateScratch() {
scratch.fillStyle='red';
scratch.fillRect(0,0,500,500);
scratch.globalCompositeOperation = 'destination-out';
}
function handlemousedown(evt) {
down = true;
handlemousemove(evt);
}
function handlemouseup(evt) {
down = false;
}
function handlemousemove(evt) {
if(!down) return;
var x = evt.clientX - canvas.offsetLeft;
var y = evt.clientY - canvas.offsetTop;
scratch.beginPath();
scratch.arc(x, y, 30, 0, 2*Math.PI);
scratch.fill();
drawAll();
}
<canvas id="myCanvas"></canvas>
Now, it could all have been done on the same canvas, but performance wise, it's probably not the best, since it implies generating an overly complex sub-path that should get re-rendered at every draw, also, it is not much easier to implement:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
ctx.font = '30px Arial';
drawAll();
// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;
function drawAll() {
ctx.globalCompositeOperation = 'source-over';
// first draw the scratch pad, intact
ctx.fillStyle = 'red';
ctx.fillRect(0,0,500,500);
// then erase with the currently being defined path
// see 'handlemousemove's note
ctx.globalCompositeOperation = 'destination-out';
ctx.fill();
// finally draw the text behind
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = 'black';
ctx.fillText("Hello World",10,50);
}
function handlemousedown(evt) {
down = true;
handlemousemove(evt);
}
function handlemouseup(evt) {
down = false;
}
function handlemousemove(evt) {
if(!down) return;
var x = evt.clientX - canvas.offsetLeft;
var y = evt.clientY - canvas.offsetTop;
// note how here we don't create a new Path,
// meaning that all the arcs are being added to the single one being rendered
ctx.moveTo(x, y);
ctx.arc(x, y, 30, 0, 2*Math.PI);
drawAll();
}
<canvas id="myCanvas"></canvas>
How could I only delete that box not the text?
You can't, you'll have to redraw the text. Once you've drawn the box over the text, you've obliterated it, it doesn't exist anymore. Canvas is pixel-based, not shape-based like SVG.

Clip canvas strokes to image

I'm trying to animate a pen filling in a variable width shape, currently working with html5 canvas. Ideally, I want to be able to have the sample below both start light and get colored in dark, as well as not appear at all and get colored in as though being drawn from nothing.
source-in doesn't seem to work, at least in firefox.
The image in question is a simple-ish SVG path, so if there's a reasonable way to generate canvas clip paths from SVG bezier paths, that would work as well.
var img = new Image();
img.src = "data:image/svg+xml;base64,...";
var xRecords = [...];
var yRecords = [...];
var canvas = document.getElementById("topCanvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
ctx.strokeStyle = "#0000ff";
ctx.lineWidth = 90;
ctx.beginPath();
ctx.lineCap = "round";
ctx.moveTo(xRecords[0], yRecords[0]);
for(var i = 0; i < xRecords.length; i++) {
ctx.lineTo(xRecords[i], yRecords[i]);
ctx.stroke();
}
http://codepen.io/matelich/pen/gpLmOW
Generating alternate versions of the image is not a big deal if that would help. Oh and the animation is just a sample.
Update This works for the most part on Chrome and IE, but not on Firefox: http://codepen.io/matelich/pen/pJNeRq
FF doesn't like your SVG dataURL.
Option#1:
You could use a .png image instead.
Option#2:
You can create a cubic Bezier curve (like SVG's "C") in canvas using context.moveTo and context.bezierCurveTo.
Then your compositing will work fine even in FF:
var canvas = document.getElementById("topCanvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload=start;
img.src = 'https://dl.dropboxusercontent.com/u/139992952/multple/svg2png.png';
var xRecords = [117.6970666666671, 137.5037866666671, 139.6247579166671, 138.2627966666671, 134.75555041666712, 130.4406666666671, 126.65579291666711, 124.7385766666671, 126.0266654166671, 131.8577066666671, 155.5330366666671, 191.76562666666712, 224.94953666666714, 239.47882666666712];
var yRecords = [143.95648000000128, 200.21077333333463, 232.21213000000128, 264.735546666668, 296.24260333333467, 325.19488000000126, 350.05395666666794, 369.2814133333347, 381.33883000000134, 384.687786666668, 371.9640133333346, 346.7872000000013, 322.15182666666794, 311.05237333333463];
function start(){
canvas.width=img.width;
canvas.height=img.height;
ctx.beginPath();
ctx.rect(0, 0, 640, 640);
ctx.fillStyle = 'white';
ctx.fill();
ctx.globalCompositeOperation = "darker";
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = "xor";
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = "destination-over";
ctx.strokeStyle = "#0000ff";
ctx.lineWidth = 90;
ctx.beginPath();
ctx.lineCap = "round";
ctx.moveTo(xRecords[0], yRecords[0]);
var i = 0;
function drawNext()
{
i++;
console.log(i+"!");
if(i >= xRecords.length) { return; }
ctx.lineTo(xRecords[i], yRecords[i]);
ctx.stroke();
setTimeout(drawNext, 500);
}
drawNext();
}
body{ background-color: white; }
#canvas{border:1px solid red;}
<canvas id="topCanvas" width=300 height=300></canvas>

CanvasRenderingContext2D.fill() does not seem to work

I'm making a simply polygon draw method in HTML5 canvas. It will successfully outline the shape, but it will not fill it, even though it has been instructed to:
function PhysicsObj(Position /*Vector*/,Vertices /*Vector Array*/)
{
this.Position = Position
this.Vertices = Vertices
this.Velocity = new Vector(0,0);
this.Colour = "rgb(100,100,100)";
this.draw = function()
{
ctx.beginPath();
for(var point=0; point<Vertices.length-1; point++)
{
ctx.moveTo(Vertices[point].X+Position.X,Vertices[point].Y+Position.Y);
ctx.lineTo(Vertices[point+1].X+Position.X,Vertices[point+1].Y+Position.Y);
}
ctx.moveTo(Vertices[point].X+Position.X,Vertices[point].Y+Position.Y);
ctx.lineTo(Vertices[0].X+Position.X,Vertices[0].Y+Position.Y);
ctx.closePath();
ctx.fillStyle = this.Colour;
ctx.fill();
ctx.strokeStyle = this.Colour;
ctx.stroke();
}
}
var Polygon = new PhysicsObj(new Vector(100,100),[new Vector(50,50),new Vector(-50,50), new Vector(0,125)]);
Polygon.draw();
The method simply takes several vertices, and connects them into a path. It simply will not fill; I cannot figure out how to use the fill method.
Eliminate the extra moveTo commands:
var PositionX=50;
var PositionY=50;
var Vertices=[
{X:10,Y:10},
{X:100,Y:10},
{X:50,Y:50}
];
ctx.beginPath();
ctx.moveTo(Vertices[0].X+PositionX,Vertices[0].Y+PositionY);
for(var point=1; point<Vertices.length; point++)
{
ctx.lineTo(Vertices[point].X+PositionX,Vertices[point].Y+PositionY);
}
ctx.lineTo(Vertices[0].X+PositionX,Vertices[0].Y+PositionY);
ctx.closePath();
ctx.fillStyle = "red";
ctx.fill();
ctx.strokeStyle = "black";
ctx.stroke();

Canvas - clipping multiple images

I want to clip a bunch of images into hexagon shapes.
I have it sort of working, but the clipping is across all the hexes instead of each image clipping to only one hex. What am I doing wrong?
Here's a live demo:
http://codepen.io/tev/pen/iJaHB
Here's the js in question:
function polygon(ctx, x, y, radius, sides, startAngle, anticlockwise, img, imgX, imgY) {
if (sides < 3) return;
var a = (Math.PI * 2)/sides;
a = anticlockwise?-a:a;
ctx.save();
ctx.translate(x,y);
ctx.rotate(startAngle);
ctx.moveTo(radius,0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius*Math.cos(a*i),radius*Math.sin(a*i));
}
ctx.closePath();
// add stroke
ctx.lineWidth = 5;
ctx.strokeStyle = '#056e96';
ctx.stroke();
// add stroke
ctx.lineWidth = 4;
ctx.strokeStyle = '#47b6c8';
ctx.stroke();
// add stroke
ctx.lineWidth = 2;
ctx.strokeStyle = '#056e96';
ctx.stroke();
// Clip to the current path
ctx.clip();
ctx.drawImage(img, imgX, imgY);
ctx.restore();
}
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// Create an image element
var img = document.createElement('IMG');
var img2 = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
polygon(ctx, 120,120,100,6, 0,0,img, -120,-170);
}
img2.onload = function () {
polygon(ctx, 280,212,100,6, 0,0,img2, -150,-120);
}
// Specify the src to load the image
img.src = "http://farm8.staticflickr.com/7381/9601443923_051d985646_n.jpg";
img2.src = "http://farm6.staticflickr.com/5496/9585303170_d005d2aaa9_n.jpg";
You need to add this to your polygon() method:
ctx.beginPath();
See modified pen here
function polygon(ctx, x, y, radius, sides, startAngle, anticlockwise, img, ...
if (sides < 3) return;
var a = (Math.PI * 2)/sides;
a = anticlockwise?-a:a;
ctx.save();
ctx.translate(x,y);
ctx.rotate(startAngle);
ctx.beginPath(); /// for example here, before moveTo/lineTo
ctx.moveTo(radius,0);
...
If not the lines will accumulate so the second time you call polygon the previous polygon will still exist. That's why you see the image partly inside the first hexagon as well.

setTimeout with canvas

I am trying to run an animate background with canvas. Right now the setTimeout shows an error in chrome Uncaught SyntaxError: Unexpected identifier. I must be animating it wrong.
When I remove setTimeout and just have the tiles(), everything works fine (i.e. not animated, but show the correct background that I want). So I am sure, it has something to do with setTimeout.
Anyone got clues for me?
function createBackground(){
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
background = $('#game .background')[0],
rect = background.getBoundingClientRect(), //returns the dimension of background
gradient,
m = monster.settings.monsterSize;
canvas.width = rect.width;
canvas.height = rect.height;
/* create checker */
tile_cols = canvas.width / m;
tile_rows = canvas.height / m;
setTimeout(tiles(ctx, m, tile_cols, tile_rows), 300);
/* add canvas to html element */
background.appendChild(canvas);
}
function tiles(ctx, m, tile_cols, tile_rows){
for (var i=0; i<tile_cols; i++){
for (var j=0; j<tile_rows; j++){
var x = Math.ceil(Math.random()*3);
switch(x){
case 1:
ctx.fillStyle = 'black';
break;
....
case 3:
ctx.fillStyle = '#00080E';
break;
}
ctx.strokeStyle = 'black';
ctx.beginPath();
...
ctx.closePath();
ctx.fill();
ctx.stroke();
};
};
return this;
}
You're assigning the result of tiles(ctx, m, tile_cols, tile_rows) to the first parameter of setTimeout
Change it to
setTimeout(function() {
tiles(ctx, m, tile_cols, tile_rows)
}, 300);
You should have a look at requestAnimationFrame for this task. Paul Irish wrote a good article about it.

Categories

Resources