Setting the globalCompositeOperation
of the context will cause elements
drawn after its set to be blended with those operations. The options can all be seen
on
MDN.
In this example, a subclass of Polygon is created that will set the context's
globalCompositeOperation
to be 'destination-out'
, which
will "erase" the content underneath it in the canvas.
The argument to the draw
function is the context
, and can
be modified with context.globalCompositeOperation
.
const rightColor = Randomizer.nextColor();
const leftColor = Randomizer.nextColor();
const bgColor = Randomizer.nextColor();
const toRad = angle => {
return (angle * Math.PI) / 180;
};
setSize(400, 600);
setBackgroundColor(bgColor);
const rightVanishingPoint = new Vector(
Randomizer.nextInt(600, 1000),
(3 * getHeight()) / 4
);
const leftVanishingPoint = new Vector(
-Randomizer.nextInt(600, 1000),
(3 * getHeight()) / 4
);
class ClipPolygon extends Polygon {
draw(context) {
context.save();
context.globalCompositeOperation = 'destination-out';
super.draw(context);
context.restore();
}
}
const projectRectangle = (rectangle, vanishingPoint, isLeft = false) => {
// go off the top a bit for subpixel stuff
let top = rectangle.y - 0.001;
let left = rectangle.x;
let right = rectangle.x + rectangle.width;
if (isLeft) {
// swap left and right since the vector will be pointing left
let temp = right;
right = left;
left = temp;
}
const v = vanishingPoint.clone().subtract(new Vector(left, top));
if (isLeft) {
v.multiply(-1, -1);
}
let theta = Math.abs(toRad(v.heading() % 90));
const dy = rectangle.width * Math.atan(theta);
const clip = new ClipPolygon();
clip.setAnchor({ ...rectangle.anchor });
clip.addPoint(left, top);
clip.addPoint(right, top);
clip.addPoint(right, top + dy);
return clip;
};
let buildingLayer = 0;
const makeBuilding = (height, leftWidth, rightWidth, x, lColor, rColor) => {
buildingLayer++;
const building = new Group();
building.layer = buildingLayer;
const rightRect = new Rectangle(rightWidth, height);
rightRect.setAnchor({ vertical: 0, horizontal: 0 });
rightRect.setPosition(x, getHeight() - rightRect.height);
rightRect.setColor(rColor);
building.add(rightRect);
const clipRight = projectRectangle(rightRect, rightVanishingPoint);
clipRight.clip = true;
building.add(clipRight);
const leftRect = new Rectangle(leftWidth, height);
leftRect.setAnchor({ vertical: 0, horizontal: 1.0 });
leftRect.setPosition(x, getHeight() - leftRect.height);
leftRect.setColor(lColor);
building.add(leftRect);
const clipLeft = projectRectangle(leftRect, leftVanishingPoint, true);
building.add(clipLeft);
add(building);
};
for (let i = 0; i < 15; i++) {
const leftWidth = Randomizer.nextInt(50, 60);
const rightWidth = Randomizer.nextInt(50, 60);
const x = Randomizer.nextInt(0, getWidth());
const height = Randomizer.nextInt(150, 450);
makeBuilding(height, leftWidth, rightWidth, x, leftColor, rightColor);
}