Invisible Object Shadow
by Paul Wheeler
HTML
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.7.0/fabric.js"></script>
</head>
<body>
<canvas id="c"></canvas>
</body>
</html>
CSS
html {
height: 100%;
}
body {
height: 100%;
}
JavaScript
fabric.BoxShadow = fabric.util.createClass(fabric.Rect, {
shadowColor: undefined,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 0,
initialize(options) {
this.callSuper('initialize', options);
this._shadow = new fabric.Shadow({
color: this.shadowColor,
blur: this.shadowBlur,
offsetX: this.shadowOffsetX,
offsetY: this.shadowOffsetY
});
},
_render: function(ctx) {
ctx.save();
// set clip path
let [offsetX, offsetY, blur] = [this.shadowOffsetX,
this.shadowOffsetY,
this.shadowBlur
];
let [top, left] = [this.width / -2, this.height / -2];
let region = new Path2D();
// The outer rectangle for our clipping path completely encompases the object and its shadow
let bounds = {
t: Math.min(top, top + offsetY - blur),
l: Math.min(left, left + offsetX - blur),
b: Math.max(top + this.height, top + this.height + offsetY + blur),
r: Math.max(left + this.width, left + this.width + offsetX + blur),
};
region.rect(bounds.l, bounds.t, bounds.r - bounds.l, bounds.b - bounds.t);
// now we subtract the actual object from our clipping path
// Note: we have to add beginPath function because the base class render code is going to treat this likc a CanvasRenderingContext2D instead of a Path2D
region.beginPath = function() { };
this.callSuper('_render', region);
ctx.clip(region, "evenodd");
// Fabric draws shadows, oddly enough, around the entire area rendered within this function. I haven't figured out the correct function to override to get our clip path to work with the normal fabric rendering pipeline
this.shadow = this._shadow;
// leverage the FabricJS shadow sizing logic
this._setShadow(ctx);
this.callSuper('_render', ctx);
this.shadow = undefined;
ctx.restore();
},
_renderPaintInOrder: function(ctx) {
if (ctx instanceof CanvasRenderingContext2D) {
...