Interpret box-shadow as canvas shadowXXX options - css

I have to draw a bow object in a canvas (2d). The box has an external shadow specified as css definition
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15)
I don't know how to convert this to the canvas way of defining shadows, using shadowOffsetX/Y, shadowColor and shadowBlur.
If I look at the shadowBlur spec, it's explicitely not related to pixels, but it only says "it's a parameter for a gaussian blur effect" (paraphrasing). Actually, I find this to be under-specified.
Would a better approximation using a gradient to transparent instead ? But then won't it miss the blurring effect ?

Now there are filters which are similar to the CSS filters properties and accepts the same functions. Although it is still a experimental technology.
// create canvas
const canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 500;
// set context
const context = canvas.getContext("2d");
context.fillStyle = '#ff0';
context.rect(50, 50, 100, 100);
// set shadow filter
let offsetX = 20;
let offsetY = 0;
let blurRadius = 5;
let color = 'rgba(255, 0, 0, 0.8)';
context.filter = 'drop-shadow('+ offsetX + 'px ' + offsetY + 'px ' + blurRadius + 'px ' + color + ')';
context.fill();
document.body.appendChild(canvas);

The shadow blur is in pixels, and only supports pixel dimensions. A blur of 0 has a sharp edge, 1 is a blur of one pixel, 2 two pixels and so on. It is not affected by the 2d API transformation. Shadow spread is not supported by the canvas 2D API.
BTW values should be qualified. I have added px where you forgot them in the CSS.
So CSS
box-shadow:0px 1px 2px 0px rgba(0, 0, 0, 0.15);
becomes
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(0, 0, 0, 0.15)";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 1;

Related

Variable font weight on HTML canvas capped at 999?

I'm working on an variable type test for a project with a variable font i created.
The 'wght' axis contains a range of between 0-6600.
I had no problems animating the full range of values above as a DOM element with HTML and CSS.
But while trying out the same thing on HTML canvas, i realised the font weight resets itself to 0 once its passes 1000.
Is there a way to get around this? I tried looking into P5.js but the examples i found still manipulates DOM elements instead of using a canvas.
An excerpt of my code below, but i cant quite share the working demo with the font as the font is meant to be a proprietary font eventually.
#font-face {
font-family: 'TestFont';
src: url('/assets/TestFont_v2.ttf') format('truetype-variations');
font-weight: 0 6600;
}
<canvas id="canvas-text" width="1000" height="500"> </canvas>
/*** js***/
var c = document.getElementById('canvas-text');
var ctx = c.getContext('2d');
function drawCanvas() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = 'white';
ctx.font = (scroll_percentage * 6600) + ' ' + '20px TestFont';
const string_ = 'Lorem ipsum';
ctx.fillText(string_, 20, 20);
}
let scroll_percentage = 0
function updateScrollProgress() {
// get percentage of total scroll
var winScroll = document.body.scrollTop || document.documentElement.scrollTop;
var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
scroll_percentage = winScroll / height;
// update canvas on scroll
drawCanvas()
}

Microsoft Edge Image Scaling

How do i make an image scale with bicubic for MS Edge? Is there some CSS or similar that can change the behavour.
See this page: http://duttongarage.com/Race-Workshop~317
On the right there are two images that have textured background, you can see the weird artifacts quite clearly
Chrome on the Left, MS Edge on the Right. As you can see there is some weird moire effect from the resize being nearest neighbor or linear, not bicubic.
Another example that is more typical:
Microsoft Edge on Top, Chrome on the Bottom. Notice the pixelation, its like what i would expect from browsers from the last decade.
Sorry for stupidness of answer, but, as I can see, Edge doesn't support any image-rendering options, so, please, try to use jQuery to resize picture.
For example, you can use solution from this answer:
just create <canvas id="canvas"></canvas> under your image and see:
screenshot
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
img = new Image();
img.onload = function () {
canvas.height = canvas.width * (img.height / img.width);
/// step 1
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
/// step 2
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
}
img.src = "http://duttongarage.com/img/2167/824";
You can easily adjust oc.width with math. For example, you can use
oc.width = $(".me-wrap-image").width();
oc.height = $(".me-wrap-image").height();
Better, if you adjust your structure by
| .me-wrap-image
| .some-class-to-get-width-and-height
-> img
for img.src you can use
$("div.some-class-to-get img").each(function(){
img.src = $(this).attr('src');
});
But I'm not sure, how to make it work properly.
Hope you fix it :)
Obsolete
This is obsolete solution and does not work on recent versions of MS Edge.
===========
This little css tweak fixed problem for me:
img {
-ms-interpolation-mode: bicubic;
}

CSS Saturate Filter on Canvas

I have a canvas which is currently drawing a grey scale diagram (JSBin example).
It's effectively a radial progress meter, that will be used a lot in the application. However, rather than colouring it with Javascript, I'd prefer to be able to give it a colour based on a class.
I thought it would be an ideal use case for CSS filters. I'd draw the default progress meter in gray, then use CSS filters to add saturation and do a hue rotation, in order to achieve blue, orange and green too.
canvas {
-webkit-filter: saturate(8);
}
The rule was supported and valid in Chrome, but the problem was, it doesn't seem to change the saturation at all.
I'm imagining that #aaa is transformed into it's HSL counterpart hsl(0, 0%, 67%). Then when I increase the saturation with a filter, it should become more saturated, but for the same hue.
I was hoping to end up with something like hsl(0, 50%, 67%) but instead, the filter doesn't seem to change the colour at all, no matter what value I use.
Any ideas?
It turns out if you draw the meter with some saturation initially, you can use the hue-rotate filter and then you can desaturate them to achieve grey scale again.
http://jsbin.com/qohokivobo/2/edit?html,css,output
Conceptually, this isn't an answer. But in the meantime, it's a solution.
What about picking the color from the CSS style ?
canvas {
color: red;
}
function init() {
let canvas = document.getElementById('test'),
context = canvas.getContext('2d');
let style = window.getComputedStyle (canvas);
let color = style.color;
canvas.width = 300;
canvas.height = 300;
let x = canvas.width / 2,
y = canvas.height / 2;
context.beginPath( );
context.arc(x, y, 100, 0, 2 * Math.PI, false);
context.strokeStyle = color;
context.lineWidth = 20;
context.stroke();
context.globalAlpha = 0.85;
context.beginPath();
context.arc(x, y, 100, 0, Math.PI + 0.3, false);
context.strokeStyle = '#eee';
context.stroke();
}
demo

Applying CSS on drawn Canvas Elements

Is it possible to apply CSS Animations (in my case a glowing effect) to a circle that is drawn on a canvas by javascript?
I am using this angularjs directive: https://github.com/angular-directives/angular-round-progress-directive/blob/master/angular-round-progress-directive.js
I use it as a counter, and I want it to glow every second (got the css code for the animation already.
Is that possible?
Another idea would be, to make the canvas itself circular, and apply the glowing effect to the canvas.
You can't apply CSS to elements drawn in the canvas, because they don't exist on the DOM. It's just as if they were a bitmap image.
You could use an SVG circle though, which will allow you to style the circle with CSS and use animations:
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
You can't apply CSS to shapes drawn to canvas, but you can create a glow effect simply by using the shadow.
A demo here
var canvas = document.getElementById('canvas'), // canvas
ctx = canvas.getContext('2d'), // context
w = canvas.width, // cache some values
h = canvas.height,
cx = w * 0.5,
cy = h * 0.5,
glow = 0, // size of glow
dlt = 1, // speed
max = 40; // max glow radius
ctx.shadowColor = 'rgba(100, 100, 255, 1)'; // glow color
ctx.fillStyle = '#fff'; // circle color
function anim() {
ctx.clearRect(0, 0, w, h); // clear frame
ctx.shadowBlur = glow; // set "glow" (shadow)
ctx.beginPath(); // draw circle
ctx.arc(cx, cy, cx * 0.25, 0, 6.28);
ctx.fill(); // fill and draw glow
glow += dlt; // animate glow
if (glow <= 0 || glow >= max) dlt = -dlt;
requestAnimationFrame(anim); // loop
}
anim();
Update
To get a outline with outer glow you can simply "punch out" the center of the circle using a composite operation. Here the example uses save/restore to remove the shadow - you can optimize the code by manually resetting these - but for simplicity, do the following modification:
ctx.fillStyle = '#fff';
// remove shadow from global
function anim() {
ctx.clearRect(0, 0, w, h);
// draw main circle and glow
ctx.save(); // store current state
ctx.shadowColor = 'rgba(100, 100, 255, 1)';
ctx.shadowBlur = glow;
ctx.beginPath();
ctx.arc(cx, cy, cx * 0.25, 0, 6.28);
ctx.fill();
ctx.restore(); //restore -> removes the shadow
// draw inner circle
ctx.globalCompositeOperation = 'destination-out'; // removes what's being drawn
ctx.beginPath();
ctx.arc(cx, cy, cx * 0.23, 0, 6.28); // smaller filled circle
ctx.fill();
ctx.globalCompositeOperation = 'source-over'; // reset
glow += dlt;
if (glow <= 0 || glow >= max) dlt = -dlt;
requestAnimationFrame(anim);
}
The composite operation will remove the pixels from the next draw operation. Simply draw a smaller filled circle on top which will leave an outline of the first circle and its glow.
Modified fiddle here

compute box-shadow width for an element

As you may know that box-shadow is not a part of box-model. so what could be a good way to compute the width of box-shadow that adds to an element?
Update: I need to know the total width of an element, including the shadow width.
well you could simply add a margin equal to the box-shadow. For example:
box-shadow: 0 0 10px #008800;
margin: 10px;
in the case you use the X and Y offsets on the box-shadow use add that value to the length of the shadow. Example:
box-shadow: 5px 5px 10px #080;
margin: 5px 15px 15px 5px;
here the offset is 5px, plus the 10px length. In the case of the spread we can continue to add to the margin values to take this into consideration.
box-shadow: 5px 5px 10px 7px #080;
margin: 12px 21px 21px 12px;
using the margin will keep the shadow from overlapping other objects on the page.
Exact width will differ from browser to browser. Each renders the shadows different. If i have to give a hard calculation for the object I guess it would be the something like this (the css property for reference)
box-shadow: h-shadow v-shadow blur spread color;
The box model offsets would be
top = (spread - v_shadow + 0.5*blur)
right = (spread + h_shadow + 0.5*blur)
bottom = (spread + v_shadow + 0.5*blur)
left = (spread - h_shadow + 0.5*blur)
The coefficient of the blur is a estimate, it may need to be adjusted slightly. Personally I prefer to not use the offset, but is here to show where it would be used
here is a jsfiddle to see it in action http://jsfiddle.net/YvqZV/4/
Just extending #samuel.molinski's answer by creating a complete function that takes a box shadow and returns the widths.
function getBoxShadowWidths(boxShadow) {
// not supporting multiple box shadow declarations for now
if ((boxShadow.match(/(rgb|#)/g) || []).length > 1) {
return false;
}
const regEx = /(\d(?=(px|\s)))/g;
const matches = [];
// box-shadow can have anywhere from 2-4 values, including horizontal offset, vertical offset,
// blur, and spread. Below finds each one and pushes it into an array (regEx.exec when used in succession
// with a global regex will find each match.
let match = regEx.exec(boxShadow);
while (match != null) {
matches.push(match[0]);
match = regEx.exec(boxShadow);
}
// default blur & spread to zero px if not found by the regex
const [hOffset = 0, vOffset = 0, blur = 0, spread = 0] = matches.map(parseFloat);
// calculate approximate widths by the distance taken up by each side of the box shadow after normalizing
// the offsets with the spread and accounting for the added distance resulting from the blur
// See https://msdn.microsoft.com/en-us/hh867550.aspx - "the blurring effect should approximate the
// Gaussian blur with a standard deviation equal to HALF of the blur radius"
const top = spread - vOffset + 0.5 * blur;
const right = spread + hOffset + 0.5 * blur;
const bottom = spread + vOffset + 0.5 * blur;
const left = spread - hOffset + 0.5 * blur;
return { top, right, bottom, left };
}
Thanks #Joey for the function. I added support for multiple values:
function getBoxShadowWidths(boxShadowValues) {
const regEx = /(\d(?=(px|\s)))/g
const widths = { top: 0, right: 0, bottom: 0, left: 0 }
boxShadowValues.split(/\s*,\s*/).forEach(boxShadowValue => {
const matches = []
// box-shadow can have anywhere from 2-4 values, including horizontal offset, vertical offset, blur, and spread.
// Below finds each one and pushes it into an array (regEx.exec when used in succession with a global regex will find each match.
let match = regEx.exec(boxShadowValue)
while (match != null) {
matches.push(match[0])
match = regEx.exec(boxShadowValue)
}
// default blur & spread to zero px if not found by the regex
const [hOffset = 0, vOffset = 0, blur = 0, spread = 0] = matches.map(parseFloat)
// calculate approximate widths by the distance taken up by each side of the box shadow after normalizing
// the offsets with the spread and accounting for the added distance resulting from the blur
// See https://msdn.microsoft.com/en-us/hh867550.aspx - "the blurring effect should approximate the
// Gaussian blur with a standard deviation equal to HALF of the blur radius"
const actualWidths = {
top: spread - vOffset + 0.5 * blur,
right: spread + hOffset + 0.5 * blur,
bottom: spread + vOffset + 0.5 * blur,
left: spread - hOffset + 0.5 * blur,
}
Object.keys(actualWidths).forEach(side => {
widths[side] = Math.max(widths[side], actualWidths[side])
})
})
return widths
}

Resources