forked from simonsarris/Canvas-tutorials
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathboxes.js
248 lines (202 loc) · 7.04 KB
/
boxes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// Last updated August 2010 by Simon Sarris
// www.simonsarris.com
// sarris@acm.org
//
// Free to use and distribute at will
// So long as you are nice to people, etc
//Box object to hold data for all drawn rects
function Box() {
this.x = 0;
this.y = 0;
this.w = 1; // default width and height?
this.h = 1;
this.fill = '#444444';
}
//Initialize a new Box, add it, and invalidate the canvas
function addRect(x, y, w, h, fill) {
var rect = new Box;
rect.x = x;
rect.y = y;
rect.w = w
rect.h = h;
rect.fill = fill;
boxes.push(rect);
invalidate();
}
// holds all our rectangles
var boxes = [];
var canvas;
var ctx;
var WIDTH;
var HEIGHT;
var INTERVAL = 20; // how often, in milliseconds, we check to see if a redraw is needed
var isDrag = false;
var mx, my; // mouse coordinates
// when set to true, the canvas will redraw everything
// invalidate() just sets this to false right now
// we want to call invalidate() whenever we make a change
var canvasValid = false;
// The node (if any) being selected.
// If in the future we want to select multiple objects, this will get turned into an array
var mySel;
// The selection color and width. Right now we have a red selection with a small width
var mySelColor = '#CC0000';
var mySelWidth = 2;
// we use a fake canvas to draw individual shapes for selection testing
var ghostcanvas;
var gctx; // fake canvas context
// since we can drag from anywhere in a node
// instead of just its x/y corner, we need to save
// the offset of the mouse when we start dragging.
var offsetx, offsety;
// Padding and border style widths for mouse offsets
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
// initialize our canvas, add a ghost canvas, set draw loop
// then add everything we want to intially exist on the canvas
function init() {
canvas = document.getElementById('canvas');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.onselectstart = function () { return false; }
// fixes mouse co-ordinate problems when there's a border or padding
// see getMouse for more detail
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// make draw() fire every INTERVAL milliseconds
setInterval(draw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
// add custom initialization here:
// add an orange rectangle
addRect(200, 200, 40, 40, '#FFC02B');
// add a smaller blue rectangle
addRect(25, 90, 25, 25, '#2BB8FF');
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
function draw() {
if (canvasValid == false) {
clear(ctx);
// Add stuff you want drawn in the background all the time here
// draw all boxes
var l = boxes.length;
for (var i = 0; i < l; i++) {
drawshape(ctx, boxes[i], boxes[i].fill);
}
// draw selection
// right now this is just a stroke along the edge of the selected box
if (mySel != null) {
ctx.strokeStyle = mySelColor;
ctx.lineWidth = mySelWidth;
ctx.strokeRect(mySel.x,mySel.y,mySel.w,mySel.h);
}
// Add stuff you want drawn on top all the time here
canvasValid = true;
}
}
// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
function drawshape(context, shape, fill) {
context.fillStyle = fill;
// We can skip the drawing of elements that have moved off the screen:
if (shape.x > WIDTH || shape.y > HEIGHT) return;
if (shape.x + shape.w < 0 || shape.y + shape.h < 0) return;
context.fillRect(shape.x,shape.y,shape.w,shape.h);
}
// Happens when the mouse is moving inside the canvas
function myMove(e){
if (isDrag){
getMouse(e);
mySel.x = mx - offsetx;
mySel.y = my - offsety;
// something is changing position so we better invalidate the canvas!
invalidate();
}
}
// Happens when the mouse is clicked in the canvas
function myDown(e){
getMouse(e);
clear(gctx);
var l = boxes.length;
for (var i = l-1; i >= 0; i--) {
// draw shape onto ghost context
drawshape(gctx, boxes[i], 'black');
// get image data at the mouse x,y pixel
var imageData = gctx.getImageData(mx, my, 1, 1);
var index = (mx + my * imageData.width) * 4;
// if the mouse pixel exists, select and break
if (imageData.data[3] > 0) {
mySel = boxes[i];
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isDrag = true;
canvas.onmousemove = myMove;
invalidate();
clear(gctx);
return;
}
}
// havent returned means we have selected nothing
mySel = null;
// clear the ghost canvas for next time
clear(gctx);
// invalidate because we might need the selection border to disappear
invalidate();
}
function myUp(){
isDrag = false;
canvas.onmousemove = null;
}
// adds a new node
function myDblClick(e) {
getMouse(e);
// for this method width and height determine the starting X and Y, too.
// so I left them as vars in case someone wanted to make them args for something and copy this code
var width = 20;
var height = 20;
addRect(mx - (width / 2), my - (height / 2), width, height, '#77DD44');
}
function invalidate() {
canvasValid = false;
}
// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
offsetX += stylePaddingLeft;
offsetY += stylePaddingTop;
offsetX += styleBorderLeft;
offsetY += styleBorderTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY
}
// If you dont want to use <body onLoad='init()'>
// You could uncomment this init() reference and place the script reference inside the body tag
//init();