I'm trying to make a drawing app and currently working on the line tool and want to have a preview like exists in Microsoft Paint and other drawing applications.
I'm using HTML5 Canvas and Javascript and am also using typical canvas drawing API like so:
context.beginPath();
context.moveTo(originX, originY);
context.lineTo(mousePos.x, mousePos.y);
context.stroke();
However, it starts to look like this when the user is trying to determine which line he wants because it doesn't erase the previous line:
The point in the middle is the origin from which the user began drawing the line from. I can't clear the canvas every time because there are other things possibly drawn. Only solution I can think of is implementing some sort of undo functionality but that seems like it would be slow and sloppy.
Does anyone have any idea on how to implement this sort of preview functionality?
What I would recommend is using more than one canvas.
Live Demo
So basically they do the preliminary drawing on the top one that continues to clear, and once they release the mouse button it then draws it to the lower "permanent" canvas.
Code from the fiddle, figured Id add it for reference since jsFiddle has been slow lately.
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
drawCanvas = document.getElementById("drawCanvas"),
drawCtx = drawCanvas.getContext("2d"),
painting = false,
lastX = 0,
lastY = 0,
curX = 0,
curY = 0,
startX = 0,
startY = 0,
lineThickness = 1;
canvas.width = canvas.height = 600;
drawCanvas.width = drawCanvas.height = 600;
drawCanvas.onmousedown = function(e) {
startX = e.pageX - this.offsetLeft;
startY = e.pageY - this.offsetTop;
painting = true;
};
drawCanvas.onmouseup = function(e){
painting = false;
ctx.strokeStyle = "#000";
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(lastX, lastY);
ctx.stroke();
drawCtx.clearRect(0, 0, 600, 600);
}
drawCanvas.onmouseclick = function(e) {
startX = e.pageX - this.offsetLeft;
startY = e.pageY - this.offsetTop;
painting = true;
};
drawCanvas.onmousemove = function(e) {
if(painting){
lastX = e.pageX - this.offsetLeft;
lastY = e.pageY - this.offsetTop;
drawCtx.clearRect(0,0,600,600);
drawCtx.beginPath();
drawCtx.moveTo(startX ,startY );
drawCtx.lineTo(lastX, lastY);
drawCtx.stroke();
}
}
Related
I have been struggling with this for a very, very long time (6 months plus). There have been some partial answers to my question but I have been unable to put the available asnswer-bits together to be able to do anything useful with it. This code will be an amazing tool for all new aspiring simple canvas game developers and will greatly benefit the community for sure.
-I need to create a very large scrollable canvas (scrolled by holding right mouse button), similar to this: https://www.desmos.com/calculator , say 50k px by 50k px and this (size)should be amendable in code.
-When we scroll, the background moves and all items, of course, will need to move with this scroll.
-There have to be some measure of scroll rate in code, which is should be amendable.
-Rendering structure need to remain as in my codepen - simple constructor objects generated on screen then rendered using request animation frame. https://codepen.io/alexhermanuk/pen/bGGXLZG
var squares = [];
var Square = function(x,y,w,h,color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
Square.prototype.update = function(){
this.draw();
}
Square.prototype.draw = function(){
context.save();
context.beginPath();
context.fillStyle = this.color;
context.fillRect(this.x,this.y,this.w,this.h)
context.closePath();
context.restore();
}
for(var i=0; i<10; i++){
squares.push(new Square(width*Math.random(),height*Math.random(),100,100,"red"));
}
function animateEverything(){
context.clearRect(0,0,width,height);
context.save()
squares.forEach(Square => {Square.update(squares)})
requestAnimationFrame(animateEverything);
}
animateEverything();
}
-Canvas / scrollable area need to be full screen without overflows
-Canvas background will consist of 4 virtually identical square images (2 main images and 2 mirror images) and these images are rendered/ replicated as a background as we scroll left right up or down and will create an unlimited background image (in our case it is just a wavy ocean). So, these have to be logically rendered like a tile map of some sort... There is no easy way to provide sample images, so please, bring yours along.
I appreciate YOUR effort and thank all in advance.
Here you go. It scrolls with 'right click' drag.
I wasn't sure what you meant by scroll rate. Do you want the scroll rate displayed, or controlled? Anyways, I did both to be safe. scrollRate controls have fast it scrolls, and it defaults to 1. scrollMeter is a number that loosely represents how fast the canvas is being scrolled. It is printed in the top left corner of the canvas.
And about tiling, I made a BackgoundTile class. You can create 4 different tiles with different x, y, and x_gap, y_gap to have what you want. But if you care about performance, instead of making 4 tile objects, use just one with a combined image. Use an image editor to combine the 4 squares to make one big square if possible. Tiling is almost always a performance heavy job, and a bit of optimization goes a long way.
I also added a window/canvas resize handler, so the aspect ratio doesn't go haywire. This also prevents the canvas from going low-res when you enlarge the window/canvas.
I left animateEverything as is, but I'd recommend adding more control to rendering instead of recursively calling it indefinitely.
I hope you like this demo.
CodePen
paste below on in this link https://codepen.io/alexhermanuk/pen/bGGXLZG.
I have added vertical scroll only
you can implement horizontal in same way.
window.onload = function(){
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
maxWidth = 15000,
maxHeight = 15000;
var origin = { x: 0, y: 0},
scrollY = 0,
isMouseDownOn = '',
scrollBarWidth = 25,
scrollThumbHeight = 45,
navButtonSize = 25,
totalScrollHeight = height-navButtonSize*2-scrollThumbHeight;
canvas.removeEventListener('mousedown', onMouseDown);
canvas.removeEventListener('mousemove', onMouseMove);
canvas.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('mouseup', onMouseUp);
canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mousemove', onMouseMove);
canvas.addEventListener('mouseup', onMouseUp);
window.addEventListener('mouseup', onMouseUp);
var squares = [];
var Square = function(x,y,w,h,color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
Square.prototype.update = function(){
this.draw();
}
Square.prototype.draw = function(){
var newY = this.y - origin.y;
context.save();
context.beginPath();
context.fillStyle = this.color;
context.fillRect(this.x,newY,this.w,this.h)
context.closePath();
context.restore();
}
for(var i=0; i<1000; i++){
squares.push(new Square(width*Math.random(),maxHeight*Math.random(),100,100,"red"));
}
function animateEverything(){
context.clearRect(0,0,width,height);
context.save()
console.log(' m here')
squares.forEach(Square => {
Square.update(squares)
})
drawScrollbar();
drawOrigin ();
//requestAnimationFrame(animateEverything);
}
animateEverything();
function drawScrollbar() {
var currentScroll = scrollY+navButtonSize;
context.save();
context.beginPath();
context.fillStyle = 'blue';
context.fillRect((width-navButtonSize ),0,navButtonSize,navButtonSize);
context.closePath();
context.beginPath();
context.fillStyle = 'green';
context.fillRect((width-navButtonSize ),(navButtonSize),scrollBarWidth,(height-navButtonSize*2));
context.closePath();
context.beginPath();
context.fillStyle = 'blue';
context.fillRect((width-navButtonSize ),(height-navButtonSize),navButtonSize,navButtonSize);
context.closePath();
context.beginPath();
context.fillStyle = 'yellow';
context.fillRect((width-navButtonSize),currentScroll,scrollBarWidth,scrollThumbHeight);
context.closePath();
context.restore();
}
function onMouseDown(e) {
//console.log('onMouseDown ' , e);
detectElement(e);
}
function onMouseMove(e) {
if(isMouseDownOn === 'scrollY') {
var posY = e.pageY - canvas.offsetTop;
scrollY = Math.min( (height-navButtonSize*2-scrollThumbHeight), Math.max(posY, 0) ) ;
origin.y = (scrollY/(height-navButtonSize*2-scrollThumbHeight))*maxHeight;
animateEverything();
}
}
function onMouseUp(e) {
//console.log('onMouseUp ' , e);
isMouseDownOn = '';
}
function drawOrigin () {
context.save();
context.beginPath();
context.font = "18px Arial";
context.fillText("Origin X:"+origin.x, 10, 30);
context.fillText("Origin Y:"+origin.y, 10, 50);
context.closePath();
context.restore();
}
function detectElement(e) {
isMouseDownOn = '';
var posX = e.pageX- canvas.offsetLeft;
var posY = e.pageY- canvas.offsetTop;
var currentThumbPos = scrollY+navButtonSize;
if(posX >= width-scrollBarWidth && (posY >= currentThumbPos && posY <= currentThumbPos+scrollThumbHeight) ) {
isMouseDownOn = 'scrollY';
}
}
}
I'm currently working on an HTML canvas example that fades out a line after it is drawn on a canvas, though my question is: how do I attain this while using a background image?
Live Demo (current code in a link at the bottom of this post)
CSS:
canvas {
background-image: url("https://zgab33vy595fw5zq-zippykid.netdna-ssl.com/wp-content/uploads/2018/02/PluralsightandSO.jpg");
background-size: 100% 100%;
}
JavaScript:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
painting = false,
lastX = 0,
lastY = 0;
canvas.width = canvas.height = 600;
canvas.onmousedown = function (e) {
if (!painting) {
painting = true;
} else {
painting = false;
}
lastX = e.pageX - this.offsetLeft;
lastY = e.pageY - this.offsetTop;
};
canvas.onmousemove = function (e) {
if (painting) {
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(mouseX, mouseY);
ctx.stroke();
lastX = mouseX;
lastY = mouseY;
}
}
function fadeOut() {
ctx.fillStyle = "rgba(255,255,255,0.3)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
setTimeout(fadeOut,100);
}
fadeOut();
I realize fillRect is filling the entire canvas with the fillStyle so I assumed I could add the image to the canvas instead, but as you can tell, the animation is not the same by using this code instead:
var image = new Image();
image.src = "https://zgab33vy595fw5zq-zippykid.netdna-ssl.com/wp-content/uploads/2018/02/PluralsightandSO.jpg"
ctx.drawImage(image, 0, 0);
Current Live Demo (after changes described above) - it's too rigid and doesn't have as long of a tail
Any thoughts?
You're drawing a 30% transparent square to get the 'fading' effect:
ctx.fillStyle = "rgba(255,255,255,0.3)";
So you need to draw the PNG transparently as well
canvas.onmousemove = function (e) {
if (painting) {
// set line alpha to 1
ctx.globalAlpha = 1.0;
// your paint code here
}
}
function fadeOut() {
var image = new Image();
// set image alpha to 0.3
ctx.globalAlpha = 0.3;
// your draw image code here
}
By the way, you don't need to create the new image every time!
Example
You need to specify width and height:
ctx.drawImage(image, 0, 0, 600, 600);
I have some questions about using mouse (move event) to draw lines with quintus engine. Can someone help me ?
Here's the thing.
I want to draw lines with mouse movement in the quintus scene,my code is like:
window.addEventListener("load",function(){
var Q = window.Q = new Quintus()
.include("Sprites, Scenes, 2D, Anim, UI")
.setup({maximize:true});
Q.Sprite.extend("Balloon",{
init:function(p){
this._super(p,{
x:100+Math.floor(Math.random()*12)*100,
y:200,
sheet:"balloon",
sprite:"balloon",
frame:Math.floor(Math.random()*12),
points:[[-50,50],[-50,-50],[50,-50],[50,50]]
});
},
});
Q.scene("start",function(stage){
stage.insert(new Q.Balloon());
});
Q.load("balloon.png",function(){
Q.sheet("balloon","balloon.png",{sx:0,sy:1,cols:12,tilew:100,tileh:100,frames:12});
Q.stageScene("start");
});
var canvas = document.getElementById("quintus");
canvas.setAttribute("width", "1920px");
canvas.setAttribute("height", "555px");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
var ctx = canvas.getContext("2d");
var listener_move = function(event){
console.log(ctx);
ctx.lineWidth = 5;
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(ctx.lastX, ctx.lastY);
ctx.lineTo(event.clientX,event.clientY);
ctx.stroke();
ctx.lastX = event.clientX;
ctx.lastY = event.clientY;
};
var listener_down = function(event){
ctx.lineWidth = 5;
ctx.strokeStyle = "#000000";
ctx.lastX = event.clientX;
ctx.lastY = event.clientY;
document.body.addEventListener("mousemove",listener_move,false);
};
var listener_up = function(event){
document.body.removeEventListener("mousemove",listener_move,false);
};
document.body.addEventListener("mousedown",listener_down,false);
document.body.addEventListener("mouseup",listener_up,false);
});
however, I get my ballon sprite ,and my log can capture the event.clientX and event.clientY , but there is no lines appear on the screen. I don't know why.
Can somebody help me ???
I have not used the quintus engine, but your mouse events and drawing code looks OK. So I think that perhaps the quintus engine is erasing and drawing on top of your ctx output, via animation-frame or some such functionality.
(Oh, and also make sure that the html canvas really has the id set to "quintus" )
I am allowing the user to draw rectangles on an image. At the same time , the user should be able to resize or move any of the rectangles at any point of time.
With some help, i have been able to draw the rectangles but i am unable to come up with resizing and moving part of it.
The rectangles that are being drawn do not overlap one another and the same has to be validated while resizing and moving too.
I am using javascript and jquery.
This is a demo of what i have done so far :
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasOffset = $("#canvas").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var startX;
var startY;
var isDown = false;
ctx.strokeStyle = "lightgray";
ctx.lineWidth = 3;
function handleMouseDown(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousedown stuff here
startX = mouseX;
startY = mouseY;
isDown = true;
}
function handleMouseUp(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
$("#uplog").html("Up: " + mouseX + " / " + mouseY);
// Put your mouseup stuff here
isDown = false;
}
function handleMouseMove(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
if (!isDown) {
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRectangle(mouseX, mouseY);
}
function drawRectangle(mouseX, mouseY) {
var width = mouseX - startX;
var height = mouseY - startY;
ctx.beginPath();
ctx.rect(startX, startY, width, height);
ctx.stroke();
}
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
as i am running short of time and i am not able to figure out how this can be done.
AFAK, HTML canvas element is simply an array of pixels.
Then drawing/moving/resizing rectangles is, simply again, to keep redrawing canvas.
So first, drawn objects need to be stored (maybe in array).
Second, corresponding mouse events are necessary.
Last, canvas redrawing is required.
Like:
var boxes = [];
var tmpBox = null;
document.getElementById("canvas").onmousedown = function(e) {...};
document.getElementById("canvas").onmouseup = function(e) {...};
document.getElementById("canvas").onmouseout = function(e) {...};
document.getElementById("canvas").onmousemove = function(e) {...};
Here is JSFiddle for demo: https://jsfiddle.net/wiany11/p7hxjmsj/14/
These 2 tutorials explain what you want:
http://simonsarris.com/blog/510-making-html5-canvas-useful
http://simonsarris.com/blog/225-canvas-selecting-resizing-shape
In short you should store the borders of the rectangles yourself and detect when the user clicks in the rectangle or on the border.
First you create an array to store your rectangles in
var rectangles = [];
Then you make a method to call every time you want to draw all your rectangles
function drawRectangles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < rectangles.length; i++) {
var rect = rectangles[i];
ctx.beginPath();
ctx.rect(rect.startX, rect.startY, rect.endX, rect.endY);
ctx.stroke();
ctx.closePath();
}
}
In your mouseUp you then push the rectangles you have created to the array
function handleMouseUp() {
...
// store the rectangle as an object in your array
var rectangle = {startX: startX, endX: mouseX, startY: startY, endY: mouseY};
rectangles.push(rectangle);
drawRectangles();
}
In your other handlers you can then detect if you click in a rectangle of when your mouse will move in one
You can't just draw objects onto the canvas if you want to move them. You need to create instances of your shape objects and manage those (hit-testing and rendering as required). It is not very complex, but requires a lot more code than you have so far.
Try this tutorial: http://simonsarris.com/blog/510-making-html5-canvas-useful
I have an application where the user has the ability to add captions, my only problem is that I am having problem with dragging and dropping multiple texts. With the usual mousedown, mousemove, mouseup events I am only able to drag and drop one text, I want to have the ability to drag and drop multiple texts however I don't have a clear approach to this problem. Any help would be much appreciated.
UPDATE: My code is messed up when I am trying to drag both of the texts, but I will post it anyways.
thanks
<html>
<body>
<canvas id = 'canvas'></canvas>
<textarea id = 'topCaption'></textarea>
<textarea id = 'bottomCaption'></textarea>
<script type = 'text/javascript'>
window.addEventListener('load',initCanvas,false);
function initCanvas(e)
{
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
canvas.height = 500;
canvas.width = 500;
mouse = {x:0,y:0};
dragging = false;
topCap = document.getElementById('topCaption');
bottomCap = document.getElementById('bottomCaption');
topX = 100; //top x position
topY = 100; //top y position
botX = 300; //bottom x position
botY = 300; //bottom y position
canvas.addEventListener('mousemove',MouseMove,false);
canvas.addEventListener('mouseup',MouseUp,false);
canvas.addEventListener('mousedown',MouseDown,false);
window.addEventListener('keyup',KeyUp,false);
return setInterval(keyup,10)
}
function clear()
{
context.clearRect(0,0,canvas.width,canvas.height);
}
function text(Caption,x,y)
{
context.fillStyle = '#000';
context.font = '45px Impact'; //'bold 45px impact';
context.textAlign = 'center';
context.lineCap = 'round';
context.lineJoin = 'round';
context.fill();
context.stroke();
context.fillText(Caption,x,y);
};
function MouseMove(event){
mouse.x = event.pageX - canvas.offsetLeft;
mouse.y = event.pageY - canvas.offsetTop;
if(dragging)
{
context.lineTo(mouse.x,mouse.y);
}
}
function MouseDown(event)
{
dragging = true;
setInterval(function(){
topX = mouse.x;
topY = mouse.y;
botX = mouse.x;
botY = mouse.y;
},10)
}
function MouseUp(event)
{
if(dragging)
{
dragging = false;
}
}
function KeyUp(event)
{
clear();
text(topCap.value.toUpperCase(),topX,topY);
text(bottomCap.value.toUpperCase(),botX,botY);
}
</script>
</body>
</html>
Sounds like you understand basic dragging by listening to mouse events, so here's the outline of a method to drag multiple items:
Listen for mousedown, mouseup and mousemove.
If you get a mousedown+mouseup inside a text boundingbox with <10px of mousemove in-between, "select" this text (maybe add its reference to a "selected" array)
If you get a mousedown followed by 10+ pixels of mousemove, its a "drag" (move all text in the "selected" array).