I'm doing some work with the HTML canvas and I'm finding that when I have drawn a line I'm unable to clear or draw over it?
So what I'm doing: I have a canvas with a grid where each cell draws itself (a filled rectangle, done on page load), when the mouse is on the grid the cell the mouse is over should be outlined (draw four lines on the canvas no other redraws). When the mouse moves to a different cell the new cell should be outlined (as before) and the previous outlined cell should no longer be outlined (the intention was to redraw the cell, drawing over the lines). It's 'un-outlining' the cell that is proving to be the problem.
I assumed it worked with a painter's algorithm, that the last thing drawn on the canvas would be visible but the effects I'm getting suggest that filled shapes and lines are handled separately with lines on a higher layer?
Here's the source for a standalone page to demonstrate it:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
var CANVAS_DIMENSION = 200;
var CANVAS_WIDTH = 10;
var CANVAS_HEIGHT = 10;
var GRID_CELL_WIDTH = CANVAS_DIMENSION/CANVAS_WIDTH;
var GRID_CELL_HEIGHT = CANVAS_DIMENSION/CANVAS_HEIGHT;
var canvas;
var context;
var grid;
var mouseCellColumn = -1;
var mouseCellRow = -1;
function init()
{
canvas = document.getElementById("myCanvas");
context = canvas.getContext("2d");
canvas.addEventListener('mousemove', updateMousePosition, false);
grid = new Array(CANVAS_WIDTH);
for (var i = 0; i < CANVAS_WIDTH; ++i)
{
grid[i] = new Array(CANVAS_HEIGHT);
for (var j = 0; j < CANVAS_HEIGHT; ++j)
{
grid[i][j] = "#0000FF";
}
}
renderScene()
}
function updateMousePosition(event)
{
var initialColumn = mouseCellColumn;
var initialRow = mouseCellRow;
var objectPosition = findPos(this);
var gridPosition = new Point(event.pageX - objectPosition.x, event.pageY - objectPosition.y);
mouseCellColumn = getColumn(gridPosition.x);
mouseCellRow = getRow(gridPosition.y);
var cell_position = getCellPosition(mouseCellColumn, mouseCellRow);
// outline the current cell
drawRectangleOutlineWithColour(cell_position.x,
cell_position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
"#FF0000");
// if mouse has moved cell redraw
if (((initialColumn != mouseCellColumn) ||
(initialRow != mouseCellRow)) &&
(initialColumn > -1) &&
(initialRow > -1))
{
renderGridCell(initialColumn, initialRow);
}
}
function renderScene()
{
for (var i = 0; i < CANVAS_WIDTH; ++i)
{
for (var j = 0; j < CANVAS_HEIGHT; ++j)
{
renderGridCell(i, j);
}
}
}
function renderGridCell(Column, Row)
{
var position = getCellPosition(Column, Row);
drawRectangleWithColour(position.x,
position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
grid[Column][Row]);
}
function drawRectangleWithColour(minX, minY, width, height, colour)
{
context.fillStyle = colour;
context.fillRect(minX,
minY,
width,
height);
}
function drawRectangleOutlineWithColour(minX, minY, width, height, colour)
{
context.strokeStyle = colour;
context.moveTo(minX, minY);
context.lineTo(minX + width, minY);
context.moveTo(minX + width, minY);
context.lineTo(minX + width, minY + height);
context.moveTo(minX + width, minY + height);
context.lineTo(minX, minY + height);
context.moveTo(minX, minY + height);
context.lineTo(minX, minY);
context.stroke();
}
function Point(x, y)
{
this.x = x;
this.y = y;
}
function getColumn(xPosition)
{
if (xPosition < 0)
{
xPosition = 0;
}
if (xPosition > CANVAS_DIMENSION)
{
xPosition = CANVAS_DIMENSION;
}
return Math.floor(xPosition/GRID_CELL_WIDTH);
}
function getRow(yPosition)
{
if (yPosition < 0)
{
yPosition = 0;
}
if (yPosition > CANVAS_DIMENSION)
{
yPosition = CANVAS_DIMENSION;
}
return Math.floor(yPosition/GRID_CELL_HEIGHT);
}
function getCellPosition(column, row)
{
if (row < 0)
{
row = 0;
}
if (row > CANVAS_HEIGHT)
{
row = CANVAS_HEIGHT - 1;
}
if (column < 0)
{
row = 0;
}
if (column > CANVAS_WIDTH)
{
column = CANVAS_WIDTH - 1;
}
var result = new Point(column * GRID_CELL_WIDTH, row * GRID_CELL_HEIGHT);
return result;
}
function findPos(obj)
{
var result = new Point(0, 0);
if (obj.offsetParent)
{
do
{
result.x += obj.offsetLeft;
result.y += obj.offsetTop;
} while (obj = obj.offsetParent);
}
return result;
}
</script>
</head>
<body onload="init()">
<div id="test" style="width: 200px; height:200px; margin: 0px auto;">
<canvas id="myCanvas" width="200" height="200">
Your browser does not support the canvas element.
</canvas>
</div>
</body>
</html>
The offending area is here:
// outline the current cell
drawRectangleOutlineWithColour(cell_position.x,
cell_position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
"#FF0000");
// if mouse has moved cell redraw
if (((initialColumn != mouseCellColumn) ||
(initialRow != mouseCellRow)) &&
(initialColumn > -1) &&
(initialRow > -1))
{
renderGridCell(initialColumn, initialRow);
}
This has no effect, the outlines accumulate.
Some digging into canvas redraws suggests 'clearRect' but this doesn't seem to help, the outlines persist:
// outline the current cell
drawRectangleOutlineWithColour(cell_position.x,
cell_position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
"#FF0000");
// if mouse has moved cell redraw
if (((initialColumn != mouseCellColumn) ||
(initialRow != mouseCellRow)) &&
(initialColumn > -1) &&
(initialRow > -1))
{
context.clearRect(position.x,
position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT);
renderGridCell(initialColumn, initialRow);
}
Let's redraw the outline area specifically with a different colour?
// outline the current cell
drawRectangleOutlineWithColour(cell_position.x,
cell_position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
"#FF0000");
// if mouse has moved cell redraw
if (((initialColumn != mouseCellColumn) ||
(initialRow != mouseCellRow)) &&
(initialColumn > -1) &&
(initialRow > -1))
{
var position = getCellPosition(initialColumn, initialRow);
drawRectangleWithColour(position.x,
position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
"#00FF00");
}
Nope, the grid redraws but it has no effect on the lines. Redraw the whole grid?
renderScene();
// outline the current cell
drawRectangleOutlineWithColour(cell_position.x,
cell_position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
"#FF0000");
/*
// if mouse has moved cell redraw
if (((initialColumn != mouseCellColumn) ||
(initialRow != mouseCellRow)) &&
(initialColumn > -1) &&
(initialRow > -1))
{
var position = getCellPosition(initialColumn, initialRow);
drawRectangleWithColour(position.x,
position.y,
GRID_CELL_WIDTH,
GRID_CELL_HEIGHT,
"#00FF00");
}
*/
I can't seem to find a good explanation of how the canvas is handling this, I'm getting the same behaviour in Safari on MacOSX and Chrome and Firefox on Windows7.