I have a function here that's supposed to draw a square and a plus sign in the middle, then reverse colors if the mouse is over it. It works fine if the mouse is not on top, but once it is filled, the plus sign disappears. I assume it's being covered up.
function drawAdd(cx, cy, btnW, btnH, mouse)
{
var getID = document.getElementById("canvas_1");
var color = "black";
var px = cx + btnW/2;
var py = cy + btnH/2;
if (getID.getContext)
{
var ctx = getID.getContext("2d");
ctx.clearRect(cx, cy, btnW, btnH);
ctx.lineWidth = "10";
ctx.fillStyle = "black";
if(mouse)
{
ctx.fillRect(cx, cy, btnW, btnH);
color = "white";
}
else
{
ctx.strokeRect(cx, cy, btnW, btnH);
}
ctx.beginPath();
ctx.lineWidth = "20";
ctx.fillStyle = color;
ctx.moveTo(px - 40, py);
ctx.lineTo(px + 40, py);
ctx.moveTo(px, py - 40);
ctx.lineTo(px, py + 40);
ctx.stroke();
ctx.closePath();
}
}
What am I doing wrong here?
You need to replace ctx.fillStyle = color with ctx.strokeStyle = color as line color is set by strokeStyle, not fillStyle.
Here's a working JSFiddle.
Related
I am drawing five horizontal lines to an HMTL 5 2D canvas:
var canvas_ctx = my_canvas.getContext("2d");
canvas_ctx.lineWidth = 0.5;
canvas_ctx.strokeStyle = "black";
{
let line_x = 0;
let line_length = canvas_ctx.width;
let offset = 5;
let numLines = 5;
let numYincrement = 10;
for (let i=0;i<numLines * numYincrement;i+=numYincrement) {
//canvas_ctx.beginPath();
canvas_ctx.moveTo(line_x,i + offset);
canvas_ctx.lineTo(line_length,i + offset);
canvas_ctx.stroke();
//canvas_ctx.closePath();
}
}
This should, ideally result in 5 black lines. Instead, the color of the lines seems to fade with each new line (as if it's a gradient!), so that line 5 is gray. If I uncomment canvas_ctx.beginPath(); and canvas_ctx.closePath();, all lines become gray. Why is this happening??
Strokes do overlap from both sides of the coordinates.
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// draw big
ctx.scale(30, 30);
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.stroke();
drawPixelGrid();
function drawPixelGrid() {
// simply renders where the pixel bounds are
ctx.beginPath();
// remove the zoom
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2; // avoid the problem we are demonstrating by using a perfect lineWidth ;-)
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
But obviously, a pixel can't be set to two colors at the same time. So browsers apply antialiasing, which will fade your pixel color to an other color, being the result of mixing the background and the foreground color.
So for a black stroke over a white or transparent background, this leads to actual gray pixels being rendered. Here I'll keep using red as an example:
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// first draw as on a 10*10 canvas
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.stroke();
// zoom it
ctx.imageSmoothingEnabled = 0;
ctx.globalCompositeOperation = 'copy';
ctx.drawImage(c, 0,0,9000,9000);
drawPixelGrid();
// this is not red...
function drawPixelGrid() {
ctx.globalCompositeOperation = 'source-over';
ctx.beginPath();
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
One way to avoid it is generally to apply an offset on your coordinates so that the line extends correctly on pixels boundaries. E.g for a 1px lineWidth, you would apply a 0.5 offset:
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// first draw as on a 10*10 canvas
ctx.beginPath();
ctx.moveTo(5.5, 0); // offset +0.5px
ctx.lineTo(5.5, 10);
ctx.stroke();
// zoom it
ctx.imageSmoothingEnabled = 0;
ctx.globalCompositeOperation = 'copy';
ctx.drawImage(c, 0,0,9000,9000);
drawPixelGrid();
// now we've got a real red
function drawPixelGrid() {
ctx.globalCompositeOperation = 'source-over';
ctx.beginPath();
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
But in your case, you are drawing at 0.5px lineWidth, so no offset will be able to get rid of this antialiasing.
So if you want perfect color, choose a correct lineWidth.
//Selectors
canvas = document.querySelector('.canvas');
canvas.setAttribute("tabindex", 0);
canvas.focus();
pointerImg = document.querySelector('.pointer');
//Variables
const pi = Math.PI;
const c = canvas.getContext('2d');
var grid = {
'x':60,
'y':20,
'size':20,
}
var pointerValues = {
37: {'x':-1, 'y':0},
38: {'x':0, 'y':-1},
39: {'x':1, 'y':0},
40: {'x':0, 'y':1},
}
//Event Listeners
window.addEventListener('resize', function() {
//init();
})
var cells = [];
for(let i=0;i<grid.x;i++) {
cells[i] = new Array(grid.y);
}
//Functions
function init() {
resizeGrid();
initCells();
}
init();
function update(){
requestAnimationFrame(update)
}
update();
function Cell(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.color = 'white';
this.update = function() {
this.draw();
}
this.draw = function() {
c.beginPath();
c.rect(this.x*this.size, this.y*this.size, this.size, this.size);
c.strokeStyle = 'black';
c.stroke();
c.fillStyle=this.color; c.fill();
c.closePath();
}
this.resetCell = function() {
c.clearRect(this.x, this.y, this.size, this.size);
this.update();
}
}
function resizeGrid() {
let windowX = window.innerWidth;
windowX -= windowX*0.1;
grid.size = windowX/grid.x;
let canvasX = windowX, canvasY = grid.y * grid.size;
canvas.width = canvasX;
canvas.height = canvasY;
}
function initCells() {
cells = [];
for(let i=0;i<grid.x;i++) {
cells[i] = new Array(grid.y);
}
for(let i=0;i<grid.x;i++) {
for(let j=0;j<grid.y;j++) {
cells[i][j] = new Cell(i, j, grid.size);
cells[i][j].update();
}
}
}
<html>
<head>
<link rel="stylesheet" href="assets/css/main.css">
<div style="display:none;">
<img class='pointer' src="assets/img/pointer.svg">
</div>
<div class='nav'>
</div>
</head>
<body>
<div class="content">
<canvas class='canvas'></canvas>
</div>
<script src='assets/js/canvas.js'></script>
</body>
</html>
function Cell(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.color = 'white';
this.update = function () {
this.draw();
}
this.draw = function () {
c.beginPath();
c.rect(this.x * this.size, this.y * this.size, this.size, this.size);
c.strokeStyle = 'black';
c.stroke();
c.fillStyle = this.color;
c.fill();
c.closePath();
}
this.resetCell = function () {
c.clearRect(this.x, this.y, this.size, this.size);
this.update();
}
This is my code for a single cell in a grid. After I draw it a single time, when I call resetCell(), it draws the cell just a little bit bigger.
In this picture, I call Update for the very first cell and it becomes a tiny bit bigger than the rest.
I've tried pretty much everything but nothing seems to work.
Also can someone recommend better way to draw and control grid.
I need it to demonstrate BFS algorithm.
You were tricked by the thickness of the border
The border has a thickness, which is easy to forget. If you draw a box which is X wide, its left and right border are in addition to the width of X.
So if you do NOT fill the interior, you get this nice appearance (left), but if you FILL, you get this ugly appearance (right).
When you draw a grid of these squares, each one covers over the right-hand and bottom sides of previous squares, so that it is not obvious what is happening.
Unless you redraw one that is not the last of the list, as I have done here (bottom-middle). Then it becomes obvious that something is wrong.
Here is the code to reproduce the above figures, and below I recommend a solution.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// With NO fill, it looks fine
ctx.beginPath();
ctx.rect(40, 40, 40, 40);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.rect(80, 80, 40, 40);
ctx.stroke();
ctx.closePath();
// With white fill, the inner half of each box gets covered up by the white fill.
ctx.beginPath();
ctx.rect(140, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(180, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
// Make Grid. This part looks OK initially.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.rect(240, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(240, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(280, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(280, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(320, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(320, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
// Redraw one: this will look messed up.
ctx.beginPath();
ctx.rect(280, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="400" height="150" ></canvas>
</body>
</html>
Solution 1. Don't fill the interiors
This is the neatest solution, if you can get away with it, and it works regardless of any (non-zero) border thickness.
Solution 2. Shrink the width to allow for border thickness
This is complicated because you need to manually set the border thickness, and realise that it is in addition to the numerical values you specify for the rectangle's size.
Look at this messy business at CSS-tricks, even when they are trying to make borders interesting! https://css-tricks.com/almanac/properties/b/border/
Amateur here :) Sorry if this seems like a dumb question, but why isn't my while loop executing? It should draw a pink rectangle with two black rectangle for paddles. P.S. Yes, I know that i<10 doesn't make sense, but it is a placeholder for later and shouldn't impact the running of the code. Anyways, here's my code-
//set up the javascript canvas
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var i = 1;
//Game loop
while (i < 10) {
//register pressed keys
//var x = event.key;
if (window.event) {
var keynum = event.keyCode;
} else if (event.which) {
var keynum = event.which;
}
//draw the pink background
ctx.beginPath();
ctx.rect(0, 0, 315, 100);
ctx.fillStyle = "#FFC0CB";
ctx.fill();
ctx.closePath();
//Draw the player's paddle
ctx.beginPath();
var playerY = 35;
ctx.rect(15, playerY, 10, 25);
ctx.fillStyle = "#000000";
ctx.fill();
ctx.closePath();
//Draw the bot's paddle
ctx.beginPath();
var botY = 35;
ctx.rect(280, botY, 10, 25);
ctx.fill();
ctx.closePath();
i++;
}
I am drawing a white rectangle on top of a bunch of filled blue circles. This is the code for filling a circle:
function fillCircle(color, radius, x, y){
console.log("New Circle With Color: " + color + " Radius: " + radius + "X: " + x + "Y: " + y);
ctx.save();
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
}
This is the code that calls the above function:
var draw = function(){
//draws map with ships
for(var k = 1; k < 6; k++){
for(var i = 0; i < 24; i++){
var point = polarToReal(k, i * Math.PI / 12);
fillCircle("blue", 4, point[0], point[1]);
}
}
}
Finally, here is the code that draws a rectangle:
function winMessage(color, text){
ctx.fillStyle = "white";
ctx.fillRect(WIDTH/4, HEIGHT/4, WIDTH/2, HEIGHT/2)
ctx.font = WIDTH/20+"px Arial";
ctx.strokeStyle = "black";
ctx.rect(WIDTH/4, HEIGHT/4, WIDTH/2, HEIGHT/2);
ctx.stroke();
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(text, WIDTH/2, HEIGHT/2);
}
When I first call draw() and then winMessage(), the white rectangle has the outline of one of the circles showing through (see image). I am not sure why the stroke queue isn't cleared. Please use the jsbin I provided to see the issue more closely.
JSBIN
The last circle you draw is still saved by the canvas context, and it's being redrawn when you call ctx.stroke(). To clear it, you need to add a ctx.beginPath() before you draw your rectangle.
Alternatively you can use ctx.strokeRect() instead, and skip ctx.stroke().
Updated fiddle: https://jsfiddle.net/r7bcmLpq/4/
I'm currently using HTML5's canvas to render a number of strings using the fillText method. This works fine, but I'd also like to give each string a 1px black outer stroke. Unfortunately the strokeText function seems to apply an inner stroke. To counter this, I've written a drawStrokedText function that achieves the effect I'm after. Unfortunately it's horrible slow (for obvious reasons).
Is there a fast, cross-browser way of achieving a 1px outer stroke using native canvas functionality?
drawStrokedText = function(context, text, x, y)
{
context.fillStyle = "rgb(0,0,0)";
context.fillText(text, x-1, y-1);
context.fillText(text, x+1, y-1);
context.fillText(text, x-1, y);
context.fillText(text, x+1, y);
context.fillText(text, x-1, y+1);
context.fillText(text, x+1, y+1);
context.fillStyle = "rgb(255,255,255)";
context.fillText(text, x, y);
};
Here's an example of the effect at work:
What's wrong with stroke? Since half the stroke will be outside of the shape, you can always draw the stroke first with a line width of double what you want. So if you wanted a 4px outer stroke you could do:
function drawStroked(text, x, y) {
ctx.font = '80px Sans-serif';
ctx.strokeStyle = 'black';
ctx.lineWidth = 8;
ctx.strokeText(text, x, y);
ctx.fillStyle = 'white';
ctx.fillText(text, x, y);
}
drawStroked("37°", 50, 150);
Which makes:
live fiddle here: http://jsfiddle.net/vNWn6/
IF that happens to not look as accurate at smaller text rendering scales, you can always draw it large but scale it down (in the above case you'd do ctx.scale(0.25, 0.25))
Simon's answer is a good solution, yet it may have mitering glitches in some cases, especially with capital 'M', 'V', & 'W':
drawStroked("MVW", 50, 150);
http://jsfiddle.net/hwG42/1/
In this case, it's best to utilize:
ctx.miterLimit=2;
http://jsfiddle.net/hwG42/3/
Best of luck!
The above answers are great, using some of these solutions* and some of my own ideas, I made a quick reference and some creative alternatives in the below fiddle.
*All credits given where due in the fiddle code
drawStrokedText ( text, x, y );
drawShadowedText ( text, x, y, shadowBlur);
drawGlowingText ( text, x, y, glowColorHex, glowDistance);
drawBlurredText ( text, x, y, blurAmount);
drawReflectedText ( text, x, y, reflectionScale, reflectionOpacity);
https://jsfiddle.net/vtmnyea8/
// Author: Aaron Edmistone
// Text effects using HTML5 Canvas with 2D Context.
// https://stackoverflow.com/questions/7814398/a-glow-effect-on-html5-canvas
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// prepare the presentation of the canvas
ctx.fillStyle = 'black';
ctx.fillRect(0,0,250,450);
ctx.fillStyle = 'gray';
ctx.fillRect(250,0,250,450);
ctx.fillStyle = 'white';
ctx.fillRect(500,0,250,450);
ctx.fillStyle = '#0066CC';
ctx.fillRect(750,0,250,450);
// prepare the font and fill
ctx.font = "80px Sans-serif";
ctx.fillStyle = "white";
function drawStrokedText(text, x, y)
{
// using the solutions from #Simon Sarris and #Jackalope from
// https://stackoverflow.com/questions/7814398/a-glow-effect-on-html5-canvas
ctx.save();
ctx.strokeStyle = 'black';
ctx.lineWidth = 8;
ctx.lineJoin="round";
ctx.miterLimit=2;
ctx.strokeText(text, x, y);
ctx.fillText(text, x, y);
ctx.restore();
}
function drawShadowedText(text, x, y, shadowBlur = 3)
{
ctx.save();
ctx.shadowBlur = shadowBlur;
ctx.shadowColor = "#000000";
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.fillText(text, x, y);
ctx.restore();
}
function drawGlowingText(text, x, y, glowColorHexString, glowDistance = 10)
{
ctx.save();
ctx.shadowBlur = glowDistance;
ctx.shadowColor = glowColorHexString;
ctx.strokeText(text, x, y);
for(let i = 0; i < 3; i++)
ctx.fillText(text, x, y); //seems to be washed out without 3 fills
ctx.restore();
}
function drawBlurredText(text, x, y, blur = 5)
{
//using technique from https://www.html5rocks.com/en/tutorials/canvas/texteffects/
ctx.save();
let width = ctx.measureText(text).width + blur * 2;
ctx.shadowColor = ctx.fillStyle;
ctx.shadowOffsetX = width + x + ctx.canvas.width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width + -ctx.canvas.width, y);
ctx.restore();
}
function drawReflectedText(text, x, y, reflectionScale = 0.2, reflectionAlpha = 0.10)
{
ctx.save();
ctx.fillText(text, x, y);
ctx.scale(1, -reflectionScale);
ctx.globalAlpha = reflectionAlpha;
ctx.shadowColor = ctx.fillStyle;
ctx.shadowBlur = 15;
ctx.fillText(text, x, -(y * (1 / reflectionScale)));
ctx.restore();
}
for(let i = 0; i < 4; i++)
{
drawStrokedText ("MVW", 20 + i * 250, 80 * 1);
drawShadowedText ("MVW", 20 + i * 250, 80 * 2, 3);
drawGlowingText ("MVW", 20 + i * 250, 80 * 3, "#FF0000", 10);
drawBlurredText ("MVW", 20 + i * 250, 80 * 4, 5);
drawReflectedText ("MVW", 20 + i * 250, 80 * 5, 0.5, 0.5);
}
<canvas id="myCanvas" width="1000" height="500"></canvas>
Output of the fiddle:
What it supports:
Outline text
Shadow text
Glowing text
Blurred text
Reflected text
Some performance metrics:
Considering using this in a game or at high frame rates?
Check out this jsperf using the above methods.
https://jsperf.com/various-text-effects-html5-2d-context
For a smooth shadow you can try this
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.font = "bold 9pt Tahoma";
ctx.shadowBlur = 3;
ctx.textAlign = "center";
ctx.shadowColor = "#000000";
ctx.shadowOffs = 0;
ctx.fillText('www.ifnotpics.com', 100, 50);
ctx.closePath();