HTML5 Canvas - How to render a transparent line with colored border - colors

I'm currently using Canvas to draw some curved lines
I know how to draw colored lines, but I don't find a way to render them with only a colored border..
For example :
const canvas = document.getElementsByTagName('canvas')[0],
ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.quadraticCurveTo(100, 10, 150, 75);
ctx.quadraticCurveTo(200, 140, 300, 100);
// this draws a 20px thick red line
ctx.lineCap = 'round';
ctx.lineWidth = 20;
ctx.strokeStyle = '#f00';
ctx.stroke();
<canvas width="350" height="150" style="border: 1px #303030 solid"></canvas>
I tried with clip(), but it's not built to cut a line...
Is there a way to draw a real border around a thick line, to avoid filling it ?
I don't want to re-draw a thinner colored line over it, because I'm using a transparent canvas within a parent DOM element filled with an image (and don't want to move this background into the canvas)

I found a not perfect solution in a another post, using globalCompositeOperation
const canvas = document.getElementsByTagName('canvas')[0],
ctx = canvas.getContext('2d'),
borderWidth = 1;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.quadraticCurveTo(100, 10, 150, 75);
ctx.quadraticCurveTo(200, 140, 300, 100);
// this draws a 20px thick red line
ctx.lineCap = 'round';
ctx.lineWidth = 20;
ctx.strokeStyle = '#f00';
ctx.stroke();
// just add this
ctx.globalCompositeOperation = "destination-out";
ctx.lineWidth = 20 - borderWidth * 2;
ctx.stroke();
ctx.globalCompositeOperation = "source-over";
<canvas width="350" height="150" style="border: 1px #303030 solid"></canvas>
In my case, it cut a double-hole into the canvas ; and it doesn't work if you have another background into the canvas...

You can do this too!
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.quadraticCurveTo(100, 10, 150, 75);
ctx.quadraticCurveTo(200, 140, 275, 100);
ctx.quadraticCurveTo(285, 110,275, 120);
ctx.quadraticCurveTo(200, 160, 150, 95);
ctx.quadraticCurveTo(100, 30, 20, 40);
ctx.quadraticCurveTo(10, 30, 20, 20);
ctx.lineWidth = 2;
//ctx.fillStyle = '#f00';
ctx.stroke();
//ctx.fill();
</script>
</body>
</html>

Related

How can I vertically orient my text using SVG.js?

I have a vertical rectangle and I want to place a text inside it.
let draw = SVG('drawing').size('100%', '100%');
let rect;
let text;
rect = draw.rect(50, '100%').attr({
fill: color,
stroke: '#d9d9d9',
'stroke-width': 1,
});
rect.move(50, 0);
text = draw.text('Some important text').font({ fill: '#ffffff' });
text.move(50, 0);
I tried to use .rotate(90) but it didn't work. Can anyone help me?
Set the text-orientation style to upright and the writing-mode style to vertical-lr to get vertical text.
In version 3 of svg.js the .style method becomes .css instead
let draw = SVG('drawing').size('100%', '100%');
let rect;
let text;
rect = draw.rect(50, '100%').attr({
fill: 'red',
stroke: '#d9d9d9',
'stroke-width': 1,
});
rect.move(50, 0);
text = draw.text('Some important text').font({ fill: '#ffffff' });
text.move(70, 0);
text.style({'text-orientation': 'upright', 'writing-mode': 'vertical-lr'})
html, body, div {
width: 100%;
height: 100%;
}
<script src=" https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.7.1/svg.min.js"></script>
<div id="drawing"></div>

Fabric.js: How to remove padding and format selection box? (padding still exists after zoom)

I am trying to format selection border style to look like in inkscape(image left side).
I have tried to set the object padding to zero with this:
fabric.Object.prototype.set({
padding: 0,
});
If I zoom in this is more apparent and padding grows.
Example: Left side inkscape selection box style, right side actual fabric.js selection box
How can i remove the padding?
Here is jsFiddle.
That's because of stroke width, set strokeWidth:0 to object.
DEMO
var canvas = window.__canvas = new fabric.Canvas('canvas');
var rect = new fabric.Rect({left: 10, top: 10,strokeWidth:0, width: 60, height: 60, fill: 'blue'});
rect.editable = true;
rect.customType = 'shape';
rect.describtion = 'fabric.js rect object';
rect.cornerColor = 'red';
rect.borderColor = 'red';
rect.cornerSize = 8;
rect.padding = 0;
canvas.add(rect);
canvas.setActiveObject(rect);
canvas.setZoom(20);
canvas{
border:2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="canvas" width="400" height="400"></canvas>

why does canvas object keep blinking during animation?

I made an animation in javascript for a house with rising smoke. the smoke is 3 functions for each part of the smoke that flow upwards from the chimney. they are controlled by a slider that toggles the speed at which the smoke exists the chimney. Everything works except when the slider is toggled left to right, the smoke blinks while rising. Could anyone tell me why that is?
Thanks
html:
<!DOCTYPE html>
<html>
<head>
<title>Carrey, Justin, Myshkin, Rost</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
<canvas id="canvas" width="500" height="500">Get a new Browser!</canvas>
<script src="script.js" ></script>
<form>
<input type="range" min="10" max="250" value="100" id="speedCont"/>
<p>
Rostislav Myshkin A00787633 rmyshkin#my.bcit.ca
<br />
Completed:3-D house, smoke, animation for smoke, slider for speed.
<br />
Challanges: animating the smoke.
</p>
</form>
</body>
</html>
javascript:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.lineWidth = 4;
ctx.strokeLinecap = 'round';
var a = 1;
var speed = 100;
var posY = 100,
posY2 = 120,
posY3 = 140,
posX = 100,
vx = 5,
vy = 5;
function foundation() {
//grass
ctx.fillStyle = "green";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(25, 375);
ctx.lineTo(125, 325);
ctx.lineTo(471, 325);
ctx.lineTo(400, 375);
ctx.lineTo(25, 375);
ctx.fill();
ctx.stroke();
//front face ground
ctx.fillStyle = "#873600";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(25, 375); //top left
ctx.lineTo(25, 425); //bottom left
ctx.lineTo(400, 425); //bottom right
ctx.lineTo(400, 375); //top right
ctx.lineTo(25, 375); //top line
ctx.fill();
ctx.stroke();
//east face ground
ctx.fillStyle = "#872000";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(475, 325); //top right
ctx.lineTo(475, 375); //bottom right
ctx.lineTo(400, 425); //bottom line
ctx.lineTo(400, 375); //top left
ctx.lineTo(475, 325); //top right
ctx.fill();
ctx.stroke();
}
function house() {
//front face
ctx.fillStyle = "#2980B9";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(110, 365);
ctx.lineTo(110, 200);
ctx.lineTo(375, 200);
ctx.lineTo(375, 365);
ctx.lineTo(110, 365);
ctx.fill();
ctx.stroke();
//east face
ctx.fillStyle = "#1760B4";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(375, 200); //lower left
ctx.lineTo(415, 180); //
ctx.lineTo(415, 340);
ctx.lineTo(375, 365);
ctx.lineTo(375, 200);
ctx.fill();
ctx.stroke();
//roof front face
ctx.fillStyle = "#B41717";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(95, 210);
ctx.lineTo(160, 140);
ctx.lineTo(395, 140);
ctx.lineTo(365, 210);
ctx.lineTo(365, 210);
ctx.lineTo(95, 210);
ctx.fill();
ctx.stroke();
//roof east face
ctx.fillStyle = "darkred";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(365, 210);
ctx.lineTo(425, 190);
ctx.lineTo(395, 140);
ctx.lineTo(365, 210);
ctx.fill();
ctx.stroke();
//door
ctx.fillStyle = "darkred";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(300, 365);
ctx.lineTo(300, 295);
ctx.lineTo(250, 295);
ctx.lineTo(250, 365);
ctx.lineTo(300, 365);
ctx.fill();
ctx.stroke();
//doorknob
ctx.fillStyle = "yellow";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.arc(290, 335, 5, 0, 2 * Math.PI, false);
ctx.fill();
ctx.stroke();
//walkway
ctx.fillStyle = "gray";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(250, 365); //left point
ctx.lineTo(240, 375); //left side
ctx.lineTo(290, 375);
ctx.lineTo(300, 365);
ctx.fill();
ctx.stroke();
//window living room
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(143, 347);
ctx.lineTo(143, 295);
ctx.lineTo(212, 295);
ctx.lineTo(212, 347);
ctx.lineTo(143, 347);
ctx.fill();
ctx.stroke();
//window top left
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(143, 275);
ctx.lineTo(143, 225);
ctx.lineTo(212, 225);
ctx.lineTo(212, 275);
ctx.lineTo(143, 275);
ctx.fill();
ctx.stroke();
//window top right
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(263, 275);
ctx.lineTo(263, 225);
ctx.lineTo(332, 225);
ctx.lineTo(332, 275);
ctx.lineTo(263, 275);
ctx.fill();
ctx.stroke();
//chimney front
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(170, 130); //top left
ctx.lineTo(170, 180); //left side line
ctx.lineTo(200, 180); //bottom line
ctx.lineTo(200, 130); //right side line
ctx.lineTo(170, 130); //top side line
ctx.fill();
ctx.stroke();
//chimney east
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(200, 130); //top left
ctx.lineTo(215, 123); //top side line
ctx.lineTo(215, 170); //right side line
ctx.lineTo(200, 180); //
ctx.fill();
ctx.stroke();
//chimney top
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(170, 130); //top left
ctx.lineTo(185, 122); //left side
ctx.lineTo(210, 122); //top side
ctx.lineTo(200, 130);
ctx.fill();
ctx.stroke();
}
function smoke1(){
posY += -vy;
posX += vx;
if (posY < -15) posY = 100;
ctx.fillStyle = "aqua";
ctx.fillRect(0,0, 220, 127);
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.beginPath();
ctx.arc(200, posY, 15, 0, Math.PI*2, true);
ctx.fill();
}
function smoke2(){
posY2 += -vy;
posX += vx;
if (posY2 < -13) posY2 = 110;
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.beginPath();
ctx.arc(185, posY2, 10, 0, Math.PI*2, true);
ctx.fill();
}
function smoke3(){
posY3 += -vy;
posX += vx;
if (posY3 < -13) posY3 = 110;
ctx.fillStyle = "rgba(0,0,0,0.5s)";
ctx.beginPath();
ctx.arc(210, posY3, 6, 0, Math.PI*2, true);
ctx.fill();
}
function animate() {
smoke1();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate, speed);
}
function animate2() {
smoke2();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate2, speed);
}
function animate3() {
smoke3();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate3, speed);
}
/** if (a == 1) {
ctx.clearRect(0, 0, 260, 105);
smoke();
a++;
} else if (a == 2) {
ctx.clearRect(0, 0, 260, 105);
smokeMed();
a++;
} else if (a == 3) {
ctx.clearRect(0, 0, 260, 105);
smokeBig();
a = 1;
} else {
ctx.clearRect(0, 0, 260, 105);
}
window.setTimeout(animate2, speed);
}
**/
window.onload = function all() {
foundation();
house();
animate();
animate2();
animate3();
}
window.addEventListener("load", all, false);
//window.setInterval(animate2, 1000);
//window.setTimeout(animate2, speed);
css:
#canvas {
background-color: aqua;
border: 1px solid black;
margin-bottom: 10px ;
}
body {
background-color: gray;
}
input[type=range] {
-webkit-appearance: none;
border: 3px solid black;
width: 500px;
border-radius: 20px;
}
input[type=range]::-webkit-slider-runnable-track {
width: 500px;
height: 10px;
background: #ddd;
border: none;
border-radius: 20px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: 3px solid black;
height: 30px;
width: 30px;
border-radius: 50%;
background: red;
margin-top: -8px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #ccc;
}
The flickr has to do with having multiple setTimeout functions. Since you delete the previous state of the smoke on smoke1(), once you change the speed there is going to be a discrepancy. If you use only one setTimeout it should work fine in your particular case. Here a JSFIDDLE as an example.
Backbuffers & requestAnimationFrame
All rendering to the DOM is double buffered. Rendering is always done to the backBuffer, it is not drawn to the screen. Presenting to the screen should be done when the scene is fully rendered.
When a function exits (returns to idle) the DOM assumes that you have completed all the rendering and what to display the results. This is when the backbuffer is moved to where the screen can see it.
If you have only rendered part of the scene and exit the DOM does not know you have not yet finished rendering and will present the backbuffer as is. The screen refreshes at a much slower rate than you can write to the backbuffer. When the DOM presents the backbuffer to the screen the display hardware may be at any point of scanning out the pixels onto the display hardware. The result is inconsistent flickering and shearing.
You can fix the problem by using only one function to do all the rendering. The problem still will be that when the function exits it may be at any point of the display scan. You will still get some shearing.
To keep in sync with the display hardware and prevent any flickering and shearing use requestAnimationFrame (there are endless answers about it in SO) to do all your rendering.
The DOM treats all callback functions called by requestAnimationFrame as special and will delay any DOM visual changes being moved from the backbuffer to the screen until the display hardware is in its vertical refresh phase. At that point all changes since the last vertical refresh are moved from back buffers to the screen. (This applies to all visual FX not just the canvas).
Fixing your code
As you want a fixed update speed.
var speed = 100;
var nextUpdateTime;
function updateAll(time){
if(nextUpdateTime=== undefined){
nextUpdateTime= time - speed; // first update now
}
if(time >= nextUpdateTime){
nextUpdateTime= time + (speed - (time - nextUpdateTime)); // get time of next update
smoke1();
smoke2();
smoke3()
}
requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);
If you want each FX to have its own speed you will have to create a array of speed and nextUpdateTime values. Requested animation frames generally run at 60fps. Time is always the first argument of the requested callback function.
Better yet run at 60fps
I would suggest that rather than use a slow update speed (100ms is 10fps) you modify your code to slow the animation down so that it runs smoothly when sped up to 60fps
The following will modify your animation to play the same but at a higher frame rate.
var speed = 100; // the old frame update delay
var frameRate = 60; // requestAnimationFrame frame rate
// vx and vy where the update delta move vectors for the smoke
// that need to be adjusted for the new frame rate.
vx = (vx * (1000 / speed)) / frameRate;
vy = (vy * (1000 / speed)) / frameRate;
function updateAll(time){
smoke1();
smoke2();
smoke3()
requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);
NOTE if you present a lot of rendering work the browser may not be able to keep up. If it cant do it in 1/60th of a second and it will not display anything until the next vertical refresh the frame rate will drop from 60fps to 30fps.
If you suspect this will happen then you should monitor the time between frames and use that to calculate the new smoke position every frame.

set Min and Max resize limits for fabric object

I am using fabric js for resizing object i want user to resize the object with in min&max limits. How to do this with fabric js.
I tried properties like
lockScalingX,lockScalingY,lockMomentX,lockMomentY but no luck.
Any help will be grateful.
Thanks,
There is no way to do it natively in fabric but you can hook in to the scaling event and make any modification to the object you should like. In this code I stop the scaling as well as correct fabric from shifting the top/left when I am over riding the scaling.
window.canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
});
canvas.add(rect);
var maxScaleX = 2;
var maxScaleY = 2;
rect.on('scaling', function() {
if(this.scaleX > maxScaleX) {
this.scaleX = maxScaleX;
this.left = this.lastGoodLeft;
this.top = this.lastGoodTop;
}
if(this.scaleY > maxScaleY) {
this.scaleY = maxScaleY;
this.left = this.lastGoodLeft;
this.top = this.lastGoodTop;
}
this.lastGoodTop = this.top;
this.lastGoodLeft = this.left;
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Might come in handy for people searching for similar answers.
You can natively set the minimum limit for scaling by setting minScaleLimit. However, there isn't an out-of-the-box solution available for setting maximum limit.
i addapted the drag limit algorithm that i had. In my specific case, i have a canvas element with a background image and i had to limit the other objects resizing with the background image limits so i added extra margins to do that. But if you need to limit the objects resizing only with the canvas size you can set the extra margins to 0.
//Limit the draggable zone
this.canvas.on("object:scaling", function (e) {
let obj = e.target;
let canvas = obj.canvas;
let zoom = canvas.getZoom();
let pan_x = canvas.viewportTransform[4];
let pan_y = canvas.viewportTransform[5];
// width & height we are constraining to must be calculated by applying the inverse of the current viewportTransform
let canvas_height = canvas.height / zoom;
let canvas_width = canvas.width / zoom;
let totalWidth = obj.width * obj.scaleX;
let totalHeight = obj.height * obj.scaleY;
// if you need margins set them here
let top_margin = marginYTop;
let bottom_margin = marginYBottom;
let left_margin = marginXLeft;
let right_margin = marginXRight;
let top_bound = top_margin - pan_y;
let bottom_bound = canvas_height - bottom_margin - pan_y;
let left_bound = left_margin - pan_x;
let right_bound = canvas_width - right_margin - pan_x;
if (obj.top < top_bound || (obj.top + totalHeight) > bottom_bound) {
obj.scaleY = obj.canvas.lastScaleY;
obj.set("top", top_bound);
}
if (obj.left < left_bound || (obj.left + totalWidth) > right_bound) {
obj.scaleX = obj.canvas.lastScaleX;
obj.set("left", left_bound);
}
obj.canvas.lastScaleY = obj.scaleY;
obj.canvas.lastScaleX = obj.scaleX;
});
As this is top 1 topic in google:
Better answer (based on StefanHayden snipped ^^ and http://jsfiddle.net/fabricjs/58y8b/):
window.canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
});
//Gradient to see pattern on passing 0 in scaling
// horizontal linear gradient
rect.setGradient('fill', {
type: 'linear',
x1: -rect.width / 2,
y1: 50,
x2: rect.width / 2,
y2: 50,
colorStops: {
0: '#ffe47b',
1: 'rgb(111,154,211)'
}
});
canvas.add(rect);
var maxScaleX = 2;
var maxScaleY = 2;
//Set starting center point:
var centerPoint = rect.getCenterPoint();
//Save center point on center point changing events (moved, maybe some cases of: rotated (not center rotation), added and drop (just assuming for last two))
rect.on('moved rotated added drop', function() {
centerPoint = rect.getCenterPoint();
});
rect.on('scaling', function() {
if(this.scaleX > maxScaleX) {
this.scaleX = maxScaleX;
}
if(this.scaleY > maxScaleY) {
this.scaleY = maxScaleY;
}
rect.setPositionByOrigin(centerPoint, 'center', 'center');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>

Fabricjs background with transparent parts

I have a Fabric.js canvas with a background image that has some transparent parts. I want the transparent parts to show the color of the div behind the canvas (e.g. red). So this works:
canvas.setBackgroundColor(null);
canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas), {
opacity: 1.0
});
Now say I want to to change the opacity of the image to 0.3. In this case the non-transparent parts of the image show the red underneath. But I would like the transparent parts to show red, and the non-transparent parts to be faded against a white background. Is this possible?
I'm not sure if I'm understanding you correctly, but you can just load in a .png with transparency in it and it'll show the canvas background in the transparent parts.
For example:
var canvas = new fabric.Canvas('fabric');
canvas.backgroundColor = 'green';
canvas.renderAll();
fabric.Image.fromURL("https://i.imgur.com/aSEcfuz.png", function(img){
img.left = 0;
img.top = 0;
img.width = 200;
img.height = 200;
canvas.add(img);
});
<canvas id="fabric" width="500" height="500"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.2/fabric.min.js"></script>
Workaround which places a white background behind the image and moves it along with the image:
var canvas = new fabric.Canvas('fabric');
canvas.backgroundColor = 'green';
canvas.renderAll();
var sharedProperties = {
left: 0,
top: 0,
width: 200,
height: 200
};
var image;
var rect = new fabric.Rect({
left: sharedProperties.left,
top: sharedProperties.top,
fill: 'white',
width: sharedProperties.width,
height: sharedProperties.height
});
canvas.add(rect);
fabric.Image.fromURL("https://i.imgur.com/aSEcfuz.png", function(img) {
image = img;
img.left = sharedProperties.left;
img.top = sharedProperties.top;
img.width = sharedProperties.width;
img.height = sharedProperties.height;
canvas.add(img);
image.on('moving', function(event) {
rect.set({
left: image.left,
top: image.top
});
rect.setCoords();
});
});
<canvas id="fabric" width="500" height="500"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.2/fabric.min.js"></script>

Resources