So I have made some waves on a canvas. Now I would also like to add a background to make like a nice sunset with waves. But my problem occurs when I make ctx.fillRect to draw the background. As I need to clear the area around the bottom for the waves to work, the whole screen is being cleared. Therefore also clearing the background
whole code
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
var gradientSky = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientSky.addColorStop(0, "#C1274E");
gradientSky.addColorStop(0.25, "#C344B7");
gradientSky.addColorStop(0.5, "#B2244A");
gradientSky.addColorStop(0.75, "#B2244A");
gradientSky.addColorStop(1, "#B2244A");
ctx.fillStyle = gradientSky;
ctx.fillRect(0, 0, cw, ch);
var gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#7a88d9");
gradientA.addColorStop(1, "#5a6bd0");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#646593");
gradientB.addColorStop(1, "#0f2460");
ctx.lineWidth = 4;
var step = 0;
function drawWaves() {
frames++;
phi = frames / 88;
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 390 + Math.sin(step / 2) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientA;
ctx.fill();
frames++;
phi = frames / 60;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 380 + Math.sin(step) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientB;
ctx.fill();
step += 0.02;
requestId = window.requestAnimationFrame(drawWaves);
}
requestId = window.requestAnimationFrame(drawWaves);
I'm pretty sure that has to do what x and y coordinates I inserted into the ctx.fillRect(); and ctx.clearRect();. But I have tried all variants I could think of but still nothing works. Sometimes I get the Background to appear but then the clear tag wont clear the waves properly. Just for better understanding the ch and cw are window.innerHeight and window.innerWidth although this can also be seen in the code.
The trick here is really simple. As most background images, your sunset also should fill the whole screen and will later be covered by the waves and ultimately the bubbles. This is how a painter would draw a painting. Since the background covers the whole canvas, you even don't need to clear it before because this is essentially being done by filling the canvas with the background.
So all you need to change is remove the clearReact() call and replace it by a fillRect(0,0,cw,ch) after setting the fillStyle to gradientSky.
Here's your modified example:
// Author:
// Name:
// URL: https://jsfiddle.net/7z2yh3pg/
function Blasen() {
const section = document.querySelector('#screen')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
}, 600)
}
const Blaseninterval = setInterval(Blasen, 100)
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
var gradientSky = ctx.createLinearGradient(0, 0, 0, ch);
gradientSky.addColorStop(0, "#C1274E");
gradientSky.addColorStop(0.25, "#C344B7");
gradientSky.addColorStop(0.5, "#B2244A");
gradientSky.addColorStop(0.75, "#B2244A");
gradientSky.addColorStop(1, "#B2244A");
ctx.fillStyle = gradientSky;
ctx.fill();
var gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#7a88d9");
gradientA.addColorStop(1, "#5a6bd0");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#646593");
gradientB.addColorStop(1, "#0f2460");
ctx.lineWidth = 4;
var step = 0;
function drawWaves() {
frames++;
phi = frames / 88;
ctx.fillStyle = gradientSky;
ctx.beginPath();
ctx.rect(0, 0, cw, ch);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 250 + Math.sin(step / 2) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientA;
ctx.fill();
frames++;
phi = frames / 60;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 240 + Math.sin(step) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientB;
ctx.fill();
step += 0.02;
requestId = window.requestAnimationFrame(drawWaves);
}
requestId = window.requestAnimationFrame(drawWaves);
body,
html {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
display: block;
margin: 0 auto;
width: 100%;
height: 100vh;
}
#screen {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#screen spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 3s linear infinite;
}
#screen spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
}
<canvas id="screen2"></canvas>
<div id="screen"></div>
I am now making Customize roulette, and players can input the text what they wanted.
when I click button, the renderRoulette function is work. And this is the inner context of renderRoulette function
rouletteCanvas.style.display = "block"; // the initial state of rouletteCanvas's display is 'none'
// #customize elements are the inputs of the text that users made.
let customize = document.querySelectorAll("#customize");
let len = customize.length;
const canvas = document.querySelector(".roulette-panel"); // I want to draw here.
let width = canvas.width;
let height = canvas.height;
const ctx = canvas.getContext("2d");
// ctx Initialization
ctx.clearRect(0, 0, width, height);
const devide = len;
const degree = 360;
const goalDegree = 270 + degree / devide;
for (let i = 0; i < devide; i++) {
let json = {
first: (degree / devide) * i,
last: (degree / devide) * (i + 1),
text: `${i + 1}`,
};
data.push(json);
}
// Draw a circular sector
data.forEach((item) => {
ctx.save();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "gray";
ctx.moveTo(width / 2, height / 2);
ctx.arc(
width / 2,
height / 2,
width / 2,
degreesToRadians(item.first),
degreesToRadians(item.last),
false
);
ctx.closePath();
ctx.stroke();
ctx.restore();
ctx.save();
let half = Math.abs(item.first - item.last) / 2;
let degg = item.first + half;
let xx = ((Math.cos(degreesToRadians(degg)) * width) / 2) * 0.7 + width / 2;
let yy =
((Math.sin(degreesToRadians(degg)) * width) / 2) * 0.7 + height / 2;
let minus = ctx.measureText(item.text).width / 2;
ctx.font = "bold 15px sans-serif";
ctx.fillText(item.text, xx - minus, yy);
ctx.restore();
});
Every time I click the button, I want to delete all the previously drawn circular sector shapes and redraw them, so I used the code, ctx.clearRect(0, 0, width, height); . However, even if you click the button after, it will be drawn over the existing picture. How do I initialize it?
I get a feeling that the save and restore in your code are causing some problems for you, I tried a simpler version of your code and the ctx.clearRect(0, 0, width, height) works just fine.
Here is the code:
const canvas = document.querySelector(".roulette-panel");
const ctx = canvas.getContext("2d");
let width = height = 100;
const devide = 8;
let data = []
for (let i = 0; i < devide; i++) {
data.push({
first: (360 / devide) * i,
last: (360 / devide) * (i + 1)
});
}
function draw() {
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach((item) => {
ctx.moveTo(width / 2, height / 2);
ctx.arc(50, 50, 45,
item.first * Math.PI / 180,
item.last * Math.PI / 180
);
item.first += 0.5
item.last += 0.5
});
ctx.stroke();
}
setInterval(draw, 40)
<canvas class="roulette-panel"></canvas>
This code uses setInterval to delete all the previously drawn circular sector shapes and draw new one on a new position creating the effect of movement
Here is that same code but with the clearRect commented:
const canvas = document.querySelector(".roulette-panel");
const ctx = canvas.getContext("2d");
let width = height = 100;
const devide = 8;
let data = []
for (let i = 0; i < devide; i++) {
data.push({
first: (360 / devide) * i,
last: (360 / devide) * (i + 1)
});
}
function draw() {
//ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach((item) => {
ctx.moveTo(width / 2, height / 2);
ctx.arc(50, 50, 45,
item.first * Math.PI / 180,
item.last * Math.PI / 180
);
item.first += 0.5
item.last += 0.5
});
ctx.stroke();
}
setInterval(draw, 40)
<canvas class="roulette-panel"></canvas>
We can clearly see a different effect
I just had a quick thought in mind of drawing a chessboard using JS and Canvas, and I have this code that draws the boxes alright with for loops.
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
for (x = 0; x < canvas.width; x += boxWidth) {
for (y = 0; y < canvas.height; y += boxHeight) {
ctx.beginPath();
ctx.rect(x, y, boxWidth, boxHeight);
ctx.stroke();
ctx.closePath();
}
}
<canvas id="canvas" width="240" height="240"></canvas>
Now I'm wondering how I can access each odd box on the axes to change their fill colors (e.g. black, white, black, white, and so on).
I know using global variables isn't the best way, but this is a very small project and I just want to get some logic on how I can alternate the colors of the chessboard. Your help is very much appreciated!
You could also try only incrementing your values by 1 (instead of boxWidth), which would make it simpler to check if they are even or odd. Then you would need to either scale or multiply by boxWidth and boxHeight:
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
var numbRows = Math.floor(canvas.width / boxWidth),
numbCols = Math.floor(canvas.height / boxHeight);
ctx.save();
ctx.scale(boxWidth, boxHeight);
for (x = 0; x < numbRows; x++) {
for (y = 0; y < numbCols; y++) {
if ((x+y) % 2 == 0) ctx.fillStyle = 'white';
else ctx.fillStyle = 'black';
ctx.beginPath();
ctx.rect(x, y, boxWidth, boxHeight);
ctx.stroke();
ctx.closePath();
}
}
ctx.restore();
You can use fillRect to do so like this:
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
for (x = 0; x < canvas.width; x += boxWidth) {
for (y = 0; y < canvas.height; y += boxHeight) {
ctx.fillStyle = (x / boxWidth + y / boxHeight) % 2 === 0? "white": "black"; // determine which color to use depending on the index of x (x / boxWidth) an the index of y (y / boxHeight)
ctx.fillRect(x, y, boxWidth, boxHeight);
}
}
<canvas id="canvas" width="240" height="240"></canvas>
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var x, y,
boxWidth = 30,
boxHeight = 30;
for (x = 0; x < canvas.width; x += boxWidth) {
for (y = 0; y < canvas.height; y += boxHeight) {
ctx.beginPath();
ctx.rect(x, y, boxWidth, boxHeight);
// fill odd boxes
(x/boxWidth + y/boxHeight) % 2 && ctx.fill()
ctx.stroke();
ctx.closePath();
}
}
<canvas id="canvas" width="240" height="240"></canvas>
Alternatives to the for loop
Another way without a loop, draw the pattern 2 by 2 square in top corner then repeat that by copying the canvas onto itself.
First create the top 2 by 2 square then fill rest of board with copies.
First 2 by 2 to 4 by 2
then 4 by 2 to 8 by 2
then 8 by 2 to 8 by 4
then 8 by 4 to 8 by 8
Example
const w= 100;
canvas.height = canvas.width = w * 8;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, w + w, w + w);
ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, w);
ctx.fillRect(w, w, w, w);
ctx.drawImage(canvas, 0, 0, w * 2, w * 2, w * 2, y , w * 2, w * 2);
ctx.drawImage(canvas, 0, 0, w * 4, w * 2, w * 4, y , w * 4, w * 2);
ctx.drawImage(canvas, 0, 0, w * 8, w * 2, 0 , w * 2, w * 8, w * 2);
ctx.drawImage(canvas, 0, 0, w * 8, w * 4, 0 , w * 4, w * 8, w * 4);
Thus it gets drawn in 7 render calls, if the grid was larger then 2 more calls for 16 by 16, and every doubling in size only needs two more calls.
The pattern can be very complex but not create excessive render stress as in the next example that has shadows and different composite calls.
const squareSize = 72;
const boardSize = 8;
const borderSize = 8;
canvas.height = canvas.width = squareSize * boardSize + borderSize * 2;
const ctx = canvas.getContext("2d");
var x = borderSize;
var y = x;
var w = squareSize;
drawSquare(3, 3, canvas.width - 6, "black", "#F97");
drawSquare(x, y, w, "white", "#964");
drawSquare(w + x, y, w, "black", "#745");
ctx.drawImage(canvas, x, y, w, w, x + w, y + w, w, w);
ctx.drawImage(canvas, x + w, y, w, w, x, y + w, w, w);
ctx.drawImage(canvas, x, y, w * 2, w * 2, x + w * 2, y, w * 2, w * 2);
ctx.drawImage(canvas, x, y, w * 4, w * 2, x + w * 4, y, w * 4, w * 2);
ctx.drawImage(canvas, x, y, w * 8, w * 2, x, y + w * 2, w * 8, w * 2);
ctx.drawImage(canvas, x, y, w * 8, w * 4, x, y + w * 4, w * 8, w * 4);
drawSquare(0,0,canvas.width,"rgba(0,0,0,0.0)","rgba(0,0,0,0.05)");
// done.
// this function is only called twice.
function drawSquare(x,y,size,color,color2){
ctx.save();
ctx.shadowColor = color2;
ctx.shadowBlur = size * 0.2;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.beginPath();
ctx.rect(x,y,size,size);
ctx.clip();
ctx.lineWidth = size;
ctx.fillStyle = color;
ctx.fillRect(x,y,size,size);
ctx.globalAlpha = 0.5;
ctx.strokeRect(x - size / 2,y - size / 2, size * 2, size * 2);
ctx.shadowBlur = size * 0.5;
ctx.strokeRect(x - size / 2,y - size / 2, size * 2, size * 2);
ctx.shadowColor = "rgba(0,0,0,0)";
ctx.shadowBlur = 0;
ctx.globalAlpha = 1;
ctx.strokeStyle = color2;
ctx.lineWidth = 2;
ctx.strokeRect(x+1,y+1,size -2,size-2);
ctx.globalAlpha = 0.75;
ctx.fillRect(x+1,y+1,size-2,size-2);
ctx.globalCompositeOperation = "screen";
ctx.fillStyle = "white";
ctx.globalAlpha = 0.1;
ctx.fillRect(x,y,4,size);
ctx.fillRect(x,y,2,size);
ctx.fillRect(x+4,y,size-4,4);
ctx.fillRect(x+2,y,size-2,2);
ctx.restore();
}
canvas { border : 2px solid black; }
<canvas id="canvas" ></canvas>
Use a pattern style.
Create an offscreen canvas to hold the pattern of the top 2 by 2, draw the pattern, then assign the fillStyle of the on screen canvas to a new pattern created from the off screen canvas and fill the whole canvas.
const w = 72;
const patCan = document.createElement("canvas");
patCan.height = patCan.width = w * 2;
var ctx = patCan.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, w + w, w + w);
ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, w);
ctx.fillRect(w, w, w, w);
// setup display canvas
canvas.height = canvas.width = w * 8;
var ctx = canvas.getContext("2d");
ctx.fillStyle = ctx.createPattern(patCan, "repeat");
ctx.fillRect(0, 0, w * 8, w * 8);
canvas { border : 8px solid green; }
<canvas id="canvas" ></canvas>
Rendering with 3 lines of code
Here's a neat little trick you can use to draw a chess board:
var ctx = c.getContext("2d");
for(var x = 0; x < c.width; x += c.width / 4) ctx.fillRect(x, 0, c.width/8, c.height);
ctx.globalCompositeOperation = "xor"; // toggle alpha channel for every 2nd line
for(var y = 0; y < c.height; y += c.height / 4) ctx.fillRect(0, y, c.width, c.height/8);
<canvas id=c width=600 height=600></canvas>
We're using canvas' size to detemine the grid size. You can of course change this and offset to anything you like. You'd still use the divisors 4 (2 cells) and 8 (1 cell) with the actual width and height.
The first step draws vertical black stripes every other column. Then we toggle alpha channel for every other row knowing that the default color is black (rgba(0,0,0,0)) using the "xor" composite mode which toggles the alpha channel.
Just remember to start with a empty canvas (which you probably are anyways due to the need to redraw moves) and to set back composite mode to "source-over" after drawing the board.
If you'd like to change the color itself simply add an extra step at the end:
ctx.globalCompositeOperation = "source-atop"; // will draw on top of what is filled
ctx.fillStyle = "#09a";
ctx.fillRect(0, 0, c.width, c.height);
The fillStyle and fillRect() can be replaced or used with an image, pattern, gradient etc.
To fill the white background simply use composite mode "destination-over" (will draw behind anything filled using the alpha channel), then draw for background.
An alternative is to use a toggle switch when filling each cell one by one:
var ctx = c.getContext("2d");
var toggle = false;
ctx.beginPath();
for(var y=0; y < c.height; y += c.height / 8) {
toggle = !toggle; // toggle for each row so they don't line up
for(var x=0; x < c.width; x += c.width / 8) {
// toggle for each cell and check, only draw if toggle = true
if (toggle = !toggle) ctx.rect(x, y, c.width / 8, c.height / 8);
}
}
ctx.fill(); // remember to use beginPath() for consecutive draw ops
<canvas id=c width=600 height=600></canvas>
Access Logic
To know if you're inside a cell you would simply calculate the mouse position relative to the canvas (see this answer on how to do that) and then quantize (using pseudo variables here, replace with real):
var cellSize = boardWidth / 8; // assumes the board is 1:1 square
var pos = getMousePos(event); // see linked answer above
var cellX = Math.floor(pos.x / cellSize) * cellSize; // start of current cell X
var cellY = Math.floor(pos.y / cellSize) * cellSize; // start of current cell Y
(to get index of cell just drop the * cellSize part).
Example:
var ctx = c.getContext("2d"), x, y, w = c.width, h = c.height, cellSize = w / 8;
render();
ctx.lineWidth = 4; ctx.strokeStyle = "red"; ctx.setLineDash([7, 7]);
// non-optimized - in production only redraw when needed (cellX/Y changes)
c.onmousemove = function(e) {
render();
var cell = getCellPos(getMousePos(e));
if (cell.x >= 0 && cell.x < w && cell.y >=0 && cell.y < h)
ctx.strokeRect(cell.x + 2, cell.y + 2, cellSize - 4, cellSize - 4);
}
function getCellPos(pos) {
return {x: Math.floor(pos.x / cellSize) * cellSize,
y: Math.floor(pos.y / cellSize) * cellSize}
}
function getMousePos(e) {
var rect = c.getBoundingClientRect();
return {x: e.clientX-rect.x, y: e.clientY-rect.y}
}
function render() {
ctx.clearRect(0, 0, w, h);
for(x = 0; x < w; x += w>>2) ctx.fillRect(x, 0, cellSize, c.height);
ctx.globalCompositeOperation = "xor"; // toggle alpha channel for every 2nd line
for(y = 0; y < h; y += h>>2) ctx.fillRect(0, y, w, cellSize);
ctx.globalCompositeOperation = "source-atop"; // fg
ctx.fillStyle = "#3c4168";
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "destination-over"; // bg
ctx.fillStyle = "#eee";
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "source-over"; // reset
}
body {background:#222;margin:20px 0 0 20px;}
<canvas id=c width=600 height=600></canvas>
I'm trying to create a loading circle from an image in HTML5 canvas.
Here's the result I expect when percentage is at 50%:
here's what I've done after a lot of tests: (the blue stroke is just to see the circle, it'll be removed after)
var img = new Image();
img.onload = draw;
img.src = "http://i.imgur.com/HVJBZ1L.png";
var canvas = document.getElementsByTagName("canvas")[0];
canvas.width = 500;
canvas.height = 500;
var ctx = canvas.getContext("2d");
function draw() {
ctx.globalAlpha = 0.5
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1;
var X = 50;
var Y = 50;
var Radius = img.width / 2;
var end = 40;
var start = 0;
var PI2 = Math.PI * 2;
var quart = Math.PI / 2;
var pct = 50 / 100;
var extent = (end - start) * pct;
var current = (end - start) / 100 * PI2 * pct - quart;
var pattern = ctx.createPattern(img, 'no-repeat');
ctx.beginPath();
ctx.arc(X, Y, Radius, -quart, current);
ctx.closePath();
ctx.fillStyle=pattern;
ctx.fill();
ctx.strokeStyle = "blue";
ctx.stroke();
}
<canvas></canvas>
as you can see, the result here isn't as excepted
What is wrong?
First, you need to correctly calculate your center point:
var X = img.width / 2;
var Y = img.height / 2;
Then you need to circle back in counter-clockwise direction tracing the inner radius Radius - 17:
ctx.beginPath();
ctx.arc(X, Y, Radius, -quart, current);
ctx.arc(X, Y, Radius - 17, current, -quart, true);
ctx.closePath();
If you aren't interested in the stroke outline, you could move to the center first, and then arc:
ctx.beginPath();
ctx.moveTo(X, Y);
ctx.arc(X, Y, Radius, -quart, current);
ctx.closePath();
Example:
var img = new Image();
img.onload = draw;
img.src = "http://i.imgur.com/HVJBZ1L.png";
var canvas = document.getElementsByTagName("canvas")[0];
canvas.width = 500;
canvas.height = 500;
var ctx = canvas.getContext("2d");
function draw() {
ctx.globalAlpha = 0.5
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1;
var X = img.width / 2;
var Y = img.height / 2;
var Radius = img.width / 2;
var end = 40;
var start = 0;
var PI2 = Math.PI * 2;
var quart = Math.PI / 2;
var pct = 50 / 100;
var extent = (end - start) * pct;
var current = (end - start) / 100 * PI2 * pct - quart;
var pattern = ctx.createPattern(img, 'no-repeat');
ctx.beginPath();
ctx.moveTo(X, Y);
ctx.arc(X, Y, Radius, -quart, current);
ctx.closePath();
ctx.fillStyle = pattern;
ctx.fill();
}
<canvas></canvas>
const img = new Image();
img.src = "http://i.imgur.com/HVJBZ1L.png";
img.onload = imageLoaded;
var W,H; // width and height when image ready
const ctx = canvas.getContext("2d");
// define the distance of the progress 0- 100
const min = 0;
const max = 100;
var pattern;
var radius;
// get pattern, radius canvas size from image ans start animation
function imageLoaded(){
requestAnimationFrame(mainLoop);
W = this.width;
H = this.height;
pattern = ctx.createPattern(this, 'no-repeat');
radius = W / 2;
canvas.width = W;
canvas.height = H;
}
// draw the background and forground images
// amount is the amount of progress. amount >= min amount <= max
function draw(amount) {
ctx.globalAlpha = 0.5
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1;
ctx.fillStyle=pattern;
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.arc( // draw inside circle CCW
W/2,
H/2,
radius - 17,
((amount - min) / (max-min)) * Math.PI * 2 - Math.PI / 2,
-Math.PI / 2,
true
);
ctx.arc( // draw outer circle CW
W/2,
H/2,
radius,
-Math.PI / 2,
((amount - min) / (max-min)) * Math.PI * 2 - Math.PI / 2
);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// animate the thing.
function mainLoop(time){
ctx.clearRect(0,0,canvas.width,canvas.height);
draw((time / 50) % max);
requestAnimationFrame(mainLoop);
}
canvas { border : 2px solid black;}
<canvas id="canvas"></canvas>