How can I use css variables to set canvas colours?
Example:
<html>
<head>
<style>
:root {
--color-bright:#ffca00;
--color-bg:#40332e;
}
body {
background-color: var(--color-bg);
color: var(--color-bright);
}
</style>
</head>
<body>
<center>
<div>
<canvas id="loadingCanvas" oncontextmenu="event.preventDefault()" width="800" height="600"></canvas>
</div>
</center>
This text is actually yellowish.
<script type='text/javascript'>
var loadingContext = document.getElementById('loadingCanvas').getContext('2d');
var canvas = loadingContext.canvas;
loadingContext.fillStyle = "rgb( 0, 0, 0 )";
// Doesn't work (should be brown instead of black):
loadingContext.fillStyle = "var(--color-bg)";
loadingContext.fillRect(0, 0, canvas.scrollWidth, canvas.scrollHeight);
loadingContext.font = '30px sans-serif';
loadingContext.textAlign = 'center'
loadingContext.fillStyle = "rgb( 200, 200, 200 )";
// Doesn't work (should be yellow instead of white):
loadingContext.fillStyle = "var(--color-bright)";
loadingContext.fillText("This text should be yellowish.", canvas.scrollWidth / 2, canvas.scrollHeight / 2);
</script>
</body>
</html>
No you can't, at least not currently. I must admit that I'm not 100% certain if it should work or not, navigating the CSS parse a grammar is quite complex for me right now, but anyway, no browser supports it, so even if the specs actually were telling that it should work, the specs would be wrong (I'll investigate this further and maybe open an issue there).
Note that it is clear though that currentColor should work, and indeed it does in Firefox, but neither in Blink nor in WebKit, so better consider it unsupported.
What you can do however is to get the parsed value yourself, by calling getComputedStyle(context.canvas).getPropertyValue("--the-property"):
const loadingContext = document.getElementById('loadingCanvas').getContext('2d');
const canvas = loadingContext.canvas;
loadingContext.fillStyle = "rgb( 0, 0, 0 )";
loadingContext.fillStyle = getComputedStyle(canvas).getPropertyValue("--color-bg");
loadingContext.fillRect(0, 0, canvas.scrollWidth, canvas.scrollHeight);
loadingContext.font = '30px sans-serif';
loadingContext.textAlign = 'center'
loadingContext.fillStyle = "rgb( 200, 200, 200 )";
loadingContext.fillStyle = getComputedStyle(canvas).getPropertyValue("--color-bright");
loadingContext.fillText("This text should be yellowish.", canvas.scrollWidth / 2, canvas.scrollHeight / 2);
:root {
--color-bright:#ffca00;
--color-bg:#40332e;
}
body {
background-color: var(--color-bg);
color: var(--color-bright);
}
<canvas id="loadingCanvas" width="800" height="600"></canvas>
This text is actually yellowish.
Note that even if it did work the way you wanted, the value would be parsed only at setting of the property, so doing it this way works exactly the same, also, this obviously doesn't work for canvases that are not connected in the DOM.
Ps: Chrome and Safari actually do support setting the fillStyle to "currentColor", but only when the color is set as a direct color (no currentColor, nor var(--prop)), and when set as the canvas element's style attribute (not through a stylesheet nor inheritance). That's quite bad IMO, and I'll open a few issues to at least get this working.
Related
What the heck is going on here?
Why does the blending abruptly stop at 255,214,214,255? Is this expected behavior?
I thought it would eventually hit 255,255,255,255, but it doesn't. It doesn't even come close. The red rectangle from the beginning never vanishes into white, it remains at color 255,214,214,255 forever.
const ctx = document.querySelector('canvas').getContext('2d');
// draw "red"
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 300, 200);
function loop() {
// log new colors
if (getCurrentColor() !== loop.lastColor) {
console.log(loop.lastColor = getCurrentColor());
}
// draw "transparent white" repeatedly
ctx.fillStyle = 'rgba(255,255,255,0.01)';
ctx.fillRect(0, 0, 300, 200);
// schedule next iteration
requestAnimationFrame(loop);
}
loop(); // start loop
function getCurrentColor() {
return ctx.getImageData(0, 0, 1, 1).data.join(',');
}
<!DOCTYPE html>
<html>
<head></head>
<body>
<canvas width="300" height="200"></canvas>
</body>
</html>
Both explanations and solutions are highly appreciated! :P
The underlying graphic buffers for HTML/JS Canvases are Bitmaps, which only allow for Integer color values from 0 to 255. This means that the output of every render operation has to be rounded!
Say you start at color value 214. If you then mix 214 * 0.99 + 255 * 0.01, you'd theoretically get 214.41. But this is then rounded back to 214 to be stored in the canvas bitmap!!
So, essentially, you're stuck on 214 (or any other value for that matter, as long as your steps are smaller than 0.5) xD
Unfortunately, there is no way to circumvent this with plain JavaScript, as you can't change the graphic buffer architecture or the blending/compositing operations.
But I'm sure you could hack the hell out of it if you really wanted to. By implementing a Float32 graphic buffer via the WebGL API, or something like that.
The reason why this is not completely white at the end is simple: You are adding a non totally transparent overlay on top of something. Regardless of how often you will do that, you will never reach the 100% as it is a asymptote behavior. Just image the following
I have a 100% red paper. And I will cover it with a 50% transparent white glas.
Then you will see 50% of the 100% => 50%.
If I do that again, your logic would say then I have 50% + 50% = 100% but thats not how it works.
You have the 50% red & 50% white glas and overlay it with another 50% white glas. That makes the glas appears 25% red and 75% white.
If you repeat that for several times, the amount of red color goes down but never reaches 0%.
The solution is that you paint the red rect again but with a higher amount of opacity. If you then reach ctx.fillStyle = 'rgba(255,255,255,1.00)'; it will fully cover the red.
Here is the example code:
const ctx = document.querySelector('canvas').getContext('2d');
// draw "red"
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 300, 200);
// draw "transparent white" repeatedly
let lastColor;
let percentDone=0;
render();
function render() {
if (getCurrentColor() !== lastColor)
console.log(lastColor = getCurrentColor());
percentDone += .005;
if (percentDone == 1) {percentDone = 1;}
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 300, 200);
ctx.fillStyle = 'rgba(255,255,255,'+percentDone+')';
ctx.fillRect(0, 0, 300, 200);
requestAnimationFrame(render);
}
function getCurrentColor() {
return ctx.getImageData(0, 0, 1, 1).data.join(',');
}
<canvas width="300" height="200"></canvas>
External: https://jsfiddle.net/Holger50/51h0yLm6/3/
€dit 1:
The color with transparency you want to add and the color on that position are compared and only the color difference is taken in account for calculation and is based on a integer division. If the calculated value is lower than 1, the color will not change and that is what happens at color strength 214 in your example.
I have tried several things out to find out why this stops on a weird step. Maybe the following helps understanding the problem: If you set the initial fill color to ctx.fillStyle = 'black'; and issue the following ctx.fillStyle = 'rgba(255,100,150,0.01)'; the stopping output is 207,43,129,255.
It seems that the new color with tranparency is calculated somehow based on the already existing value. If the difference is to low, the colors do not get added.
As example: If you use ctx.fillStyle = 'rgba(10,100,150,0.01), the red part will never grow cause internally 10*0.01 is less than 1 and therefore not taken in account (it is rounded to the next integer). You can also see that in the color values. Red is raised by 100. The console output raises by 1 each step. Blue is raised by 150 and the console raises by 2 UNTIL it reaches value 44. After that, it will only grow with step 1 until it completely stops at 129.
I have attached a second fiddle where you can see the effect.
const ctx = document.querySelector('canvas').getContext('2d');
// draw "red"
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 300, 200);
// draw "transparent white" repeatedly
let lastColor;
render();
function render() {
if (getCurrentColor() !== lastColor)
console.log(lastColor = getCurrentColor());
ctx.fillStyle = 'rgba(10,100,150,0.01)';
ctx.fillRect(0, 0, 300, 200);
requestAnimationFrame(render);
}
function getCurrentColor() {
return ctx.getImageData(0, 0, 1, 1).data.join(',');
}
<canvas width="300" height="200"></canvas>
I am trying to create many different image repeat pattern. I used the following code for the repeat pattern. The code is working fine for regular patterns but I need many different sets of the pattern as the image is shown below.
OR Like below...
But with the below code I have this pattern only that is here ....
<div style="width: 675px;height:675px;overflow: hidden;"><canvas id="cc" width="300px" height="300px"></canvas></div>
<script>
drawPattern(img, current_size);
function drawPattern(img, size) {
var canvas = document.getElementById('cc');
var tempCanvas = document.createElement("canvas"),
tCtx = tempCanvas.getContext("2d");
tempCanvas.width = size;
tempCanvas.height = size;
tCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, size, size);
// use getContext to use the canvas for drawing
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = ctx.createPattern(tempCanvas, 'repeat');
ctx.beginPath();
ctx.rect(0,0,canvas.width,canvas.height);
ctx.fill();
}
</script>
Please help in this that how I can get that different patterns. Thanks in Advance.
I believe that there are infinite patterns you can accomplish. So it's difficult to tell you all the code that you are looking for. You should research a lot of tecniques and "steal" ideas for yourself.
From what I saw you are using JavaScript to perform this patterns, but there are other ways to accomplish this. You can simply use HTML and CSS for example. Like:
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-image: url("image.png");
background-repeat: repeat-y;
}
</style>
</head>
<body>
<h1>The background-repeat Property</h1>
<p>Here, the background image is repeated only vertically.</p>
</body>
</html>
Also, the background-repeat property can have the following values:
background-repeat: repeat|repeat-x|repeat-y|no-repeat|initial|inherit;
For last there are more CSS properties you can use to perform different background patterns, like:
background-position: center; /* Center the image */
background-size: cover; /* Resize the background image to cover the entire container */
HTML and CSS documentation:
https://www.w3schools.com/cssref/pr_background-repeat.asp
I am new to javascript and need to condense text drawn into a canvas using java script. w3schools suggests this can be done.
JavaScript syntax: object.style.fontStretch="expanded"
I noticed in css syntax however that neither font-stretch: condensed nor font-stretch: expanded appear to work and a comment in the example in w3schools also states no browsers support the font-stretch property. I am also aware this property will not work on just any font.
var can = document.getElementById("textCanvas");
var ctx = can.getContext("2d");
ctx.font = "120px Arial Bold Italic";
ctx.font.fontStretch="ultra-condensed"; //condensed”; // expanded”;
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText("NAME", 100, 100);
<canvas id="textCanvas" width="200" height="200" style="border: 1px solid black"></canvas>
Using font-stretch made no difference. I also tried font-kerning and font-size-adjust. How do I condense the font ?
ctx.font is a string. Setting fontStretch or anything else on it won't change it.
Now, the correct syntax is the same as CSS font property:
ctx.font = "[ [ <'font-style'> || <font-variant-css2> || <'font-weight'> || <font-stretch-css3> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | caption | icon | menu | message-box | small-caption | status-bar"`
So for setting only the font-stretch, the minimal is
ctx.font = "<font-stretch-css3> <'font-size'> <'font-family'>"
For example ctx.font = "ultra-condensed 18px LeagueMonoVariable"; would do.
However you'll face the same limitations as with CSS: browser support is limited, only a few fonts can be used, and in Chrome you need to define the font-stretch in the font-face declaration.
// This example seems to work only on Chrome, as does the MDN's example
const font = '18px "LeagueMonoVariable"';
document.fonts.load(font)
.then(() => {
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d');
['ultra-condensed', 'condensed', 'expanded', 'ultra-expanded'].forEach((stretch, i) => {
ctx.font = stretch + ' ' + font;
ctx.fillText('Hello ' + stretch + ' World', 10, i * 30 + 30);
});
});
/* borrowed from https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch */
/*
This example uses the League Mono Variable font, developed by
Tyler Finck (https://www.tylerfinck.com/) and used here under
the terms of the SIL Open Font License, Version 1.1:
http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web
*/
#font-face {
src: url('https://tylerfinck.com/leaguemonovariable/LeagueMonoVariable.ttf');
font-family: 'LeagueMonoVariable';
font-style: normal;
font-stretch: 1% 500%;
}
<canvas id="canvas" width="500"></canvas>
Chrome output for the ones using an other browser.
Also note that apparently the percentage notation (n% font-size font-family) is not supported in Chrome from context's font.
Given all these limitations, it might be worth considering an alternative solution, and the best alternative is probably to have the variant font as a separate font-face and use it as simply as possible.
Promise.all([
document.fonts.load("18px Roboto"),
document.fonts.load("18px 'Roboto Condensed'")
])
.then(() => {
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d');
ctx.font = "18px Roboto";
ctx.fillText('Hello World', 10, 30);
ctx.font = "18px 'Roboto Condensed'";
ctx.fillText('Hello condensed World', 10, 60);
});
<link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Condensed&display=swap" rel="stylesheet">
<canvas id="canvas" width="500"></canvas>
Started using fabric.js and trying to add a canvas inside another canvas, so that the top canvas stays constant and I'll add objects to inner canvas.
Here is the snippet of adding a canvas to another canvas.
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
canvas.add(innerCanvas);
and my html looks like this
<canvas id="artcanvas" width="500" height="500"></canvas>
<canvas id="innerCanvas" width="200" height="200" ></canvas>
Once adding these successfully, what I am going to do is , add the coordinates to the inner canvas, so that it looks like one on another to the end user.
However, ran into the below error for the tried code
Uncaught TypeError: obj.setCoords is not a function
at klass._onObjectAdded (fabric.js:6894)
at klass.add (fabric.js:231)
at main.js:60
at fabric.js:19435
at HTMLImageElement.fabric.util.loadImage.img.onload (fabric.js:754)
_onObjectAdded # fabric.js:6894
add # fabric.js:231
(anonymous) # main.js:60
(anonymous) # fabric.js:19435
fabric.util.loadImage.img.onload # fabric.js:754
Looking at the error message, just went to the line of error and here is what I found in chrome console
Can someone point the mistake in my codes ?
After going through no.of discussions and internet solutions, for time being I am using Fabric Rectangle as a clipper and setting it's boundaries so user can be able to drop/play with in that particular clipper.
Dotted red(image below) is my clipper and now I can bound the dropping and below is the code to add an image with a clipper.
function addImageToCanvas(imgSrc) {
fabric.Object.prototype.transparentCorners = false;
fabric.Image.fromURL(imgSrc, function(myImg) {
var img1 = myImg.set({
left: 20,
top: 20,
width: 460,
height: 460
});
img1.selectable = false;
canvas.add(img1);
var clipRectangle = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 150,
top: 150,
width: 200,
height: 200,
fill: 'transparent',
/* use transparent for no fill */
strokeDashArray: [10, 10],
stroke: 'red',
selectable: false
});
clipRectangle.set({
clipFor: 'layer'
});
canvas.add(clipRectangle);
});
}
Now while appending any image/layer to the canvas, I bind that image/layer/text to the clipper I created.
function addLayerToCanvas(laImg) {
var height = $(laImg).height();
var width = $(laImg).width();
var clickedImage = new Image();
clickedImage.onload = function(img) {
var pug = new fabric.Image(clickedImage, {
width: width,
height: height,
left: 150,
top: 150,
clipName: 'layer',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
clickedImage.src = $(laImg).attr("src");
}
And the looks like, after restriction of bounds,
Here is the fiddle I have created with some static image url.
https://jsfiddle.net/sureshatta/yxuoav39/
So I am staying with this solution for now and I really feel like this is hacky and dirty. Looking for some other clean solutions.
As far as I know you can't add a canvas to another canvas - you're getting that error as it tries to call setCoords() on the object you've added, but in this case it's another canvas and fabric.Canvas doesn't contain that method (see docs). I think a better approach would be to have two canvases and position them relatively using CSS - see this simple fiddle
HTML
<div class="parent">
<div class="artcanvas">
<canvas id="artcanvas" width="500" height="500"></canvas>
</div>
<div class="innerCanvas">
<canvas id="innerCanvas" width="200" height="200" ></canvas>
</div>
</div>
CSS
.parent {
position: relative;
background: black;
}
.artcanvas {
position: absolute;
left: 0;
top: 0;
}
.innerCanvas {
position: absolute;
left: 150px;
top: 150px;
}
JS
$(document).ready(function() {
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
var rect = new fabric.Rect({
fill: 'grey',
width: 500,
height: 500
});
canvas.add(rect);
var rect2 = new fabric.Rect({
fill: 'green',
width: 200,
height: 200
});
innerCanvas.add(rect2);
})
To handle the object serialization, you can do something like this:
var innerObjs = innerCanvas.toObject();
console.dir(innerObjs);
var outerObjs = canvas.toObject();
innerObjs.objects.forEach(function (obj) {
obj.left += leftOffset; // offset of inner canvas
obj.top += topOffset;
outerObjs.objects.push(obj);
});
var json = JSON.stringify(outerObjs);
This will then give you the JSON for all objects on both canvases
I have no understanding why you want to do this thing, but to put a canvas inside another canvas, you have one simple way:
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
imageContainer = new fabric.Image(innerCanvas.lowerCanvasEl);
canvas.add(imageContainer);
Then depending what you want to do, you may need additional tweaks, but this should work out of the box.
Don't create a canvas
Most objects in fabric (from my limited experience) are at some point converted to a canvas. Creating an additional fabric canvas to manage a group of objects is kind of pointless as you are just adding overhead and mimicking fabrics built in groups object.
Fabric objects are basically DOM canvases wrappers.
The following example shows how fabric uses a canvas to store the content of a group. The demo creates a group and adds it to the fabric canvas, then gets the groups canvas and adds it to the DOM. Clicking on the group's canvas will add a circle. Note how it grows to accommodate the new circles.
const radius = 50;
const fill = "#0F0";
var pos = 60;
const canvas = new fabric.Canvas('fCanvas');
// create a fabric group and add two circles
const group = new fabric.Group([
new fabric.Circle({radius, top : 5, fill, left : 20 }),
new fabric.Circle({radius, top : 5, fill, left : 120 })
], { left: 0, top: 0 });
// add group to the fabric canvas;
canvas.add(group);
// get the groups canvas and add it to the DOM
document.body.appendChild(group._cacheContext.canvas);
// add event listener to add circles to the group
group._cacheContext.canvas.addEventListener("click",(e)=>{
group.addWithUpdate(
new fabric.Circle({radius, top : pos, fill : "blue", left : 60 })
);
canvas.renderAll();
pos += 60;
});
canvas {
border : 2px solid black;
}
div {
margin : 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<div>Fabric's canvas "canvas = new fabric.Canvas('fCanvas');"</div>
<canvas id="fCanvas" width="256" height="140"></canvas>
<div>Fabric group canvas below. Click on it to add a circle.</div>
Use a group rather than a new instance of a fabric canvas.
As you can see a canvas is generated for you. Adding another fabric canvas (Note that a fabric canvas is not the same as a DOM canvas) will only add more work for fabric to do, which already has a lot of work to do.
You are best of to use a group and have that hold the content of the other fabric object you wish to shadow. That would also contain its content in a group.
Just an image
And just a side not, a DOM canvas is an image and can be used by fabric just as any other image. It is sometimes better to do the rendering directly to the canvas rather than via fabric so you can avoid rendering overheads that fabric needs to operate.
To add a DOM canvas to fabric just add it as an image. The border and text are not fabric object, and apart from the code to render them take up no memory, and no additional CPU overhead that would be incurred if you used a fabric canvas and objects.
const canvas = new fabric.Canvas('fCanvas');
// create a standard DOM canvas
const myImage = document.createElement("canvas");
// size it add borders and text
myImage.width = myImage.height = 256;
const ctx = myImage.getContext("2d");
ctx.fillRect(0,0,256,256);
ctx.fillStyle = "white";
ctx.fillRect(4,4,248,248);
ctx.fillStyle = "black";
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("The DOM canvas!",128,128);
// use the canvas to create a fabric image and add it to fabrics canvas.
canvas.add( new fabric.Image(myImage, {
left: (400 - 256) / 2,
top: (400 - 256) / 2,
}));
canvas {
border : 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<canvas id="fCanvas" width="400" height="400"></canvas>
innerCanvas.setCoords(); ìs a function, but you need it only after you set the coordinates. Or more precise, set these four elements:
innerCanvas.scaleX = 1;
innerCanvas.scaleY = 1;
innerCanvas.left = 150;
innerCanvas.top = 150;
innerCanvas.setCoords();
canvas.renderAll();
My code is like this:
(function() {
var c = $('#board')[0].getContext('2d');
$('#board').click(_tst);
var _tst_idx = 0;
function _tst(e) {
c.fillRect(40 + (_tst_idx++) * 50, 40, 40, 40);
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="board" width="500" height="500"></canvas>
http://jsfiddle.net/u063s0s4/
Just click the canvas and draw a box on it.
But, when I focus another window, the 2nd box disappear unexpected.
Like this: http://i2.tietuku.com/8398bd6aacc39e5d.png
I used Chrome 39.0.2171.95/Win 7, I don't know what's wrong with this code.
Thanks.
============ more ==================
I have tried to use the stealth mode of chrome or disable all extensions, the 2nd box was still disappeared.
But the code works all right on the other computer of mine.
That's so strange!
Only the 2nd rendering to canvas will disappear.
So I used this code on init to avoid it:
(function(a,b){
a(function(){
b(0, 0, 1, 1);
a(function(){
b(0, 0, 1, 1);
});
});
})(requestAnimationFrame, c.clearRect.bind(c));
=============== more =============
When I disabled "Use hardware acceleration when available" in the chrome setting, it became fine.
My GPU: NVIDIA 560TI