Im drawing lines from the center of a shape on mouse clicks. This works fine, until I perform a rotation on the div element holding the canvas element.
Below is the basic Javascript. rotateWrapper gets called by a button elsewhere on the page
var p;
var rot = 0;
var canvas;
var ctx;
function rotateWrapper() {
if (rot == 0) rot = 180;
else rot = 0;
$("#wCanvas").rotate({ animateTo:rot,duration:2500});
}
function draw() {
ctx.save();
ctx.moveTo(p[0], p[1]);
ctx.lineTo(p[2], p[3]);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
$(document).ready(function () {
canvas = $("#imgCanvas").get(0);
ctx = canvas.getContext("2d");
$("#imgCanvas").bind({
mouseup: function(ev) {
p[2] = ev.pageX;
p[3] = ev.pageY;
},
mousedown: function(ev) {
p = new Array(4);
p[0] = $("#wCanvas").width() / 2;
p[1] = $("#wCanvas").height() / 2;
}
});
}
Im sure Im missing something basic but this is driving me up the wall. Ive tried rotating the context in the draw method, both the amount of rot, as well as the inverse of it. Because Im rotating the container element Im thinking this has something to do with the CSS change interfering with things but not certain on that.
Any insights would be highly appreciated
"rotate" will rotate the canvas for elements drawn after the rotation. Without seeing how rotateWrapper and draw are called, it's difficult to tell how you should structure the code. But essentially, you should redraw after you want to apply the rotation. You probably want to store the rotation value, and call a central redraw routine that will apply the rotation first, then redraw.
Related
I´ve been trying to make a desktop app (javascript, canvas) and draw 413.280 clickable circles in a certain pattern, but I can´t really figure out how to do it. I´m not convinced canvas is the best solution but I dont know how to solve this and get an app with a reasonable performance.
Here´s the layout I´m trying to get:
circle layout
I want 2 rows of circles within each line. the division in the middle is to be left empty.
Every left row has to be 588 circles.
Every right row has to be 560 circles
There are 180 lines on each side which means there's (588*2*180)= 211680 circles on the left side.
There's (560*2*180)=201600 circles on the right side.
can anyone point me in the right direction, maybe have a clue how I can solve this in the most efficient way possible? Thanks in advance.
EDIT: here's the JSFiddle I've got so far jsfiddle.net/cmxLoqej/2/
JavaScript
window.onload = draw;
function draw() {
var canvas = document.getElementById('canvas');
var c = canvas.getContext('2d');
var ycoordinate = 20;
//draw the line 180 times
for (var x = 1; x <= 180; x++) {
// draw the left side
for (var i = 1; i <= 1; i++){
c.strokeStyle = 'black';
c.moveTo(0,ycoordinate);
c.lineTo(6468,ycoordinate);
c.stroke();
ycoordinate = ycoordinate + 40;
}
}
var ycoordinate = 20;
//draw right side
for (var x = 1; x <= 180; x++) {
for (var j = 1; j <= 1; j++){
c.strokeStyle = 'black';
c.moveTo(6776,ycoordinate);
c.lineTo(canvas.width,ycoordinate);
c.stroke();
ycoordinate = ycoordinate + 40;
}
}
}
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var canvasPattern = document.createElement("canvas");
canvasPattern.width=11;
canvasPattern.height=20;
var contextPattern = canvasPattern.getContext("2d");
contextPattern.beginPath();
contextPattern.arc(5, 10, 5, 0, 2 * Math.PI, false);
contextPattern.strokeStyle = '#003300';
contextPattern.stroke();
var pattern = context.createPattern(canvasPattern,"repeat");
context.fillStyle = pattern;
context.fillRect(0, 20, 6468, 7160);
context.fill();
var canvas2 = document.getElementById('canvas');
var context2 = canvas.getContext('2d');
var canvasPattern2 = document.createElement("canvas");
canvasPattern2.width=11;
canvasPattern2.height=20;
var contextPattern2 = canvasPattern.getContext("2d");
contextPattern2.beginPath();
contextPattern2.arc(5, 10, 5, 0, 2 * Math.PI, false);
contextPattern2.strokeStyle = '#003300';
contextPattern2.stroke();
var pattern2 = context2.createPattern(canvasPattern2,"repeat");
context2.fillStyle = pattern;
context2.fillRect(6776, 20, 6160, 7160);
context2.fill();
HTML
<!DOCTYPE html>
<html>
<body>
<canvas {
id="canvas";
width= "12936" ;
height ="7400";
style= "border: 1px solid black;";
padding: 0;
margin: auto;
display: block;
}>
</canvas>
</body>
</html>
Use fill patterns of circles to create rectangular canvas images of
a single row of the left hand side
a single row of the right hand side
a combined row of each side
a single canvas of 180 rows
Use temporary CANVAS objects along the way as necessary to use the context2D.createPattern method. You should not need to add them to the DOM just to manipulate pixels.
Modify the algorithm if needed as you learn. Happy coding!
Update (edit)
Running the code added to the question shows all circles being evenly spaced horizontally and vertically.
A simpler way of drawing the canvas may be to fill two rectangles that exactly cover the left and right areas of the canvas with the circle pattern, and draw the grid lines on the canvas afterwards instead of before.
Finding the circle clicked
A click event listener on the canvas is passed a mouse event object.
The classical way to determine which circle was clicked was to first perform arithmetic on the screenX and screenY event properties for screen position, window.scrollX and window.scrollY for document scroll amounts, and the position of the canvas within the document, to find where the click occured in the canvas.
Although not yet fully standardized, offsetX and offsetY properties of the mouse event object provide the result directly. The MDN reference shows fairly good cross browser support.
Then knowledge of canvas layout can be used to determine which rectangular circle pattern was clicked, and with a bit of algebra if the click is inside the circle.
I'm tinkering with a slowly-rotating geodesic sphere, and I was hoping you guys could help me make some tweaks:
https://codepen.io/anon/pen/QaYQBd
(The code is based on this: https://bl.ocks.org/mbostock/3057239)
var width = 1000,
height = 500;
var velocity = [.002, .002],
t0 = Date.now();
var projection = d3.geo.orthographic()
.scale(height / 2 - 10);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
context.strokeStyle = "#fa0";
context.lineWidth = 0.5;
geodesic(3);
d3.timer(function() {
var time = Date.now() - t0;
projection.rotate([time * velocity[0], time * velocity[1]]);
redraw();
});
function redraw() {
context.clearRect(0, 0, width, height);
faces.forEach(function(d) {
d.polygon[0] = projection(d[0]);
d.polygon[1] = projection(d[1]);
d.polygon[2] = projection(d[2]);
});
context.beginPath();
faces.forEach(function(d) {
drawTriangle(d.polygon);
});
context.stroke();
}
function drawTriangle(triangle) {
context.moveTo(triangle[0][0], triangle[0][1]);
context.lineTo(triangle[1][0], triangle[1][1]);
context.lineTo(triangle[2][0], triangle[2][1]);
context.closePath();
}
function geodesic(n) {
faces = d3.geodesic.polygons(n).map(function(d) {
d = d.coordinates[0];
d.polygon = d3.geom.polygon(d.map(projection));
return d;
});
redraw();
}
I've been able to modify the code to get the design of the sphere closer to what I want. I'm not sure how to progress any further.
My goals:
Add dots to each vertex like this: https://bl.ocks.org/mbostock/3055104
Give the sphere a 3d perspective instead of being flat (Check out this design, which has a slider that lets you adjust the perspective: http://dmccooey.com/polyhedra/GeodesicIcosahedron3.html)
Have the stroke widths and dot sizes change relative to the perspective (this will make the whole thing have a more 3d solid look to it, instead of having the same stroke widths for every line segment)
Move the sphere to an arbitrary position within the canvas and allow the canvas size to change depending on the width of the browser window
Randomize the rotation and starting position of the sphere when the page reloads
If anyone can help me achieve any of these goals, I'd be ecstatic!
How is it / is it possible to draw using the mouse a canvas using 3 axis(x,y,z).
I know that one can draw a canvas on 2 axis and I have done that successfully.
But I have no idea of how I shall draw it on 3 axis (for example a cube).
Following shows some 2d canvas drawing functionallity
$(canvas).on('mousemove', function(e) {
mousex = parseInt(e.clientX-canvasx);
mousey = parseInt(e.clientY-canvasy);
if(mousedown) {
ctx.beginPath();
if(tooltype=='draw') {
ctx.globalCompositeOperation = 'source-over';
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
} else {
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 10;
}
ctx.moveTo(last_mousex,last_mousey);
ctx.lineTo(mousex,mousey);
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
}
last_mousex = mousex;
last_mousey = mousey;
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
The full code https://jsfiddle.net/ArtBIT/kneDX/.
But how can I add a z axis and draw a 3d canvas for instance a cube.
With 2D it is simple, you have the X and Y coordinate of the mouse, and when a mouse button is clicked you can change pixels at that location in the canvas.
3D on the other hand is quite hard. Because of the extra dimension that does not exist on the 2D surface, you need to know how to control the 3D positions. And to make matters worse, with that third dimension comes all kinds of extra's that everyone likes to have: lightning and shadows, effects, focus, etc.
Simple drawing
In its most basic form, (set aside some arithmic) you can flatten the Z axis on the 2D surface with a single division. Suppose that you have a point in 3D which consists of three points on three axis (x3d, y3d, z3d) then you can do:
var x2d = x3d / z3d;
var y2d = y3d / z3d;
If you're new to 3D, you will want to play with this first. Here is a tutorial.
Advanced drawing
For just particles and lines this is rather straightforward, although you might want to use another perspective. But it gets more complicated soon when you use objects and want to rotate them in 3D space. This is why most people rely on an engine like three.js to do the 3D drawing for them.
Control 3D space
When drawing with the mouse, you need to map the 2D mouse movement to 3D for control. For examples, have a look a these 3D GUI's: Microsoft's Paint 3D, Google's Sketchup, and Blender. Note that the more kinds of mappings needs to be implemented (like scaling and other transformations) the more math is required.
Using three.js would help you out. See here: https://jsfiddle.net/bn890dtc/
The core code for drawing the line as your click and drag:
function onMouseMove(evt) {
if (renderer) {
var x = (event.clientX / window.innerWidth) * 2 - 1;
var y = -(event.clientY / window.innerHeight) * 2 + 1;
var z = 0
var vNow = new THREE.Vector3(x, y, z);
vNow.unproject(camera);
splineArray.push(vNow);
}
}
The line
vNow.unproject(camera);
will project your drawing into 3D space.
This function will update the line in 3D space:
function updatePositions() {
var positions = line.geometry.attributes.position.array;
var index = 0;
for ( var i = 0; i < splineArray.length; i ++ ) {
positions[ index ++ ] = splineArray[i].x;
positions[ index ++ ] = splineArray[i].y;
positions[ index ++ ] = splineArray[i].z;
}
}
I am drawing onto an HTML5 canvas with stroke() and regardless of how or when I set globalAlpha, the stroke is being drawn with some measure of transparency. I'd like for the stroke to be completely opaque (globalAlpha=1). Is there somewhere else where the alpha is being set?
In this jsfiddle, I am drawing a grid of solid black lines onto a canvas. For me, the result shows dots at the intersections, confirming that the lines are partially transparent. Here's the gist of it:
context.globalAlpha=1;
context.strokeStyle="#000";
context.beginPath();
/* draw the grid */
context.stroke();
context.closePath;
The especially weird thing (to me) is that this problem was not occurring in my code before my last computer restart, so I'm guessing there was something hanging around in the cache that was keeping the alpha at my desired level.
I'm obviously missing something here... thanks for any help you can provide.
Real answer :
Each point in a canvas has its center in its (+0.5, +0.5) coordinate.
So to avoid artifacts, start by translating the context by (0.5, 0.5) ,
then round the coordinates.
css scaling creates artifact, deal only with canvas width and height, unless
you want to deal with hidpi devices with webGL, or render at a lower resolution
with both webGL and context2D.
-> in your case, your setup code would be (with NO css width/height set ) :
( http://jsfiddle.net/gamealchemist/x9bTX/8/ )
// parameters
var canvasHorizontalRatio = 0.9;
var canvasHeight = 300;
var hCenterCanvas = true;
// setup
var canvasWidth = Math.floor(window.innerWidth * canvasHorizontalRatio);
var cnv = document.getElementById("myCanvas");
cnv.width = canvasWidth;
cnv.height = canvasHeight;
if (hCenterCanvas)
cnv.style['margin-left'] = Math.floor((window.innerWidth - canvasWidth) * 0.5) + 'px';
var ctx = cnv.getContext("2d");
ctx.translate(0.5, 0.5);
gridContext();
The rest of the code is the same as your original code, i just changed the size of you squares to get quite the same visual aspect.
ctx.beginPath();
for (var i=60; i<canvasHeight; i+=60) {
ctx.moveTo(0,i);
ctx.lineTo(canvasWidth,i);
}
for (i=60; i<canvasWidth; i+=60) {
ctx.moveTo(i,0);
ctx.lineTo(i,canvasHeight);
}
ctx.strokeStyle="#000";
ctx.stroke();
ctx.closePath();
With those changes we go from :
to :
Edit : to ensure rounding, in fact i think most convenient is to inject the context and change moveTo, lineTo :
function gridContext() {
var oldMoveTo = CanvasRenderingContext2D.prototype.moveTo;
CanvasRenderingContext2D.prototype.moveTo = function (x,y) {
x |= 0; y |= 0;
oldMoveTo.call(this, x, y);
}
var oldLineTo = CanvasRenderingContext2D.prototype.lineTo;
CanvasRenderingContext2D.prototype.lineTo = function (x,y) {
x |= 0; y |= 0;
oldLineTo.call(this, x, y);
}
}
Obviously, you must do this for all drawing functions you need.
When drawing lines on a canvas, the line itself is exactly on the pixel grid. But because the line is one pixel wide, half of it appears in each of the pixels to either side of the grid, resulting in antialising and a line that is basically 50% transparent over two pixels.
Instead, offset your line by 0.5 pixels. This will cause it to appear exactly within the pixel.
Demo
I have a canvas element that automatically fills the entire browser window of the client when loaded. On it you can draw with the mouse, like in the result of any "make a drawing board"-tutorial out there. What I want to do however is to make it so that if you move the mouse to any extreme of the canvas (or maybe select a certain "move"-tool, you can drag the canvas in any direction you'd like), it scrolls. In particular, I want it to be possible to in theory scroll forever, so I can't really pre-generate, I have to generate "more canvas" on the fly. Does any one have any idea on how to do this?
If it helps, this is the client-side javascript right now: (the html is just a canvas-tag)
$(document).ready(function() {
init();
});
function init() {
var canvas = document.getElementById('canvas')
, ctx = canvas.getContext('2d')
, width = window.innerWidth
, height = window.innerHeight;
// Sets the canvas size to be the same as the browser size
canvas.width = width;
canvas.height = height;
// Binds mouse and touch events to functions
$(canvas).bind({
'mousedown': startDraw,
'mousemove': draw,
'mouseup': stopDraw,
});
};
// Triggered on mousedown, sets draw to true and updates X, Y values.
function startDraw(e) {
this.draw = true;
this.X = e.pageX;
this.Y = e.pageY;
};
// Triggered on mousemove, strokes a line between this.X/Y and e.pageX/Y
function draw(e) {
if(this.draw) {
with(ctx) {
beginPath();
lineWidth = 4;
lineCap = 'round';
moveTo(this.X, this.Y);
lineTo(e.pageX, e.pageY);
stroke();
}
this.X = e.pageX;
this.Y = e.pageY;
}
};
// Triggered on mouseup, sets draw to false
function stopDraw() {
this.draw = false;
};
The canvas element uses real memory of your computer, so there is no infinite canvas which scrolls forever. But, you may simulate this behavior using a virtual canvas. Just record the xy coords captured by draw() into an array and calculate a new center of the virtual canvas if the mouse touches the border. Then filter out the xy coords which fit into center +- screen size and draw them.
However, the array recording the xy coords can not grow infinitely and the filter code will get slower over the size of the array. Are 10,000 points enough?
More optimized code will turn the mouse coords into splines and saves only points needed to redraw the (smoothed) path of the mouse.