Recently, CANVAS has been used a lot, so I have sorted it out. Online examples are attached, and the level is limited. If there are any mistakes, please comment😁
content |
---|
Canvas draws complex graphics |
Canvas canvas understanding |
High DPI screen rendering blur problem |
Canvas implements picture magnifier |
Canvas draws complex graphics
The commonly used Canvas APIS include moveTo, lineTo, Arc, etc., which will not be discussed in detail. See the following examples for how to use them. Pay attention to the position of the brush and moveTo it to the specified position. If you want to selectively fill, use the non-zero surround or even surround principle.
Non-zero wrap (default)
ctx.fill('nonzero')
Copy the code
Non-zero circles are divided into directions. Draw a ray outward from an area, +1 for each clockwise line and -1 for counterclockwise line, all the way to the outermost part, and leave it empty if the sum is equal to 0, as shown in the figure
Parity around
ctx.fill('evenodd')
Copy the code
There is no direction difference between odd and even circles, from a region to draw a ray, all the way to the outermost, the number of lines through, if it is odd, fill; If it is even, it is not filled.
Complex graph drawing example
To draw the following figure, stroke and color it
Ctx. arc is used to draw the basic outline of the image, as shown in the figure
According to the color requirements, it is necessary to pay attention to the drawing direction when drawing the circle, using the non-zero surround principle, in which the innermost circle uses counterclockwise, the other five circles use clockwise (or the innermost circle uses clockwise, the other five circles use counterclockwise), that is, the target effect. ➡ ️ stackblitz.com/edit/js-ngu…
The following code
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.strokeStyle="red"
ctx.fillStyle="blue"
ctx.moveTo(55.40);
ctx.arc(40.40.15.0.2*Math.PI, false);
ctx.moveTo(70.40);
ctx.arc(40.40.30.0.2*Math.PI, true);
ctx.moveTo(45.10);
ctx.arc(40.10.5.0.2*Math.PI, true);
ctx.moveTo(75.40)
ctx.arc(70.40.5.0.2*Math.PI, true);
ctx.moveTo(15.40)
ctx.arc(10.40.5.0.2*Math.PI, true);
ctx.moveTo(45.70)
ctx.arc(40.70.5.0.2*Math.PI, true);
// Here we have all the paths set up, and then stroke and fill together. Be careful to stroke before filling, otherwise the line will be drawn on top
ctx.stroke();
ctx.fill();
Copy the code
Note ⚠ ️
-
Using beginPath clears out the previous path, so using fill or stroke you don’t have to redraw something that already exists, otherwise you’ll find the same trajectory getting deeper and deeper every time you stroke
-
ClosePath is used to connect the start and end points
Canvas logical size, physical size, coordinate size
Here is my personal understanding
The logical size
Canvas. width, canvas.height
The physical size
Width, canvas.style.height, which is the size displayed in the browser
1. When the logical size range > physical size range, it is equivalent to the canvas canvas is compressed, so it is clearer
2. When the logical size range is less than the physical size range, it is equivalent to stretching the canvas, so it will become blurred
Coordinate system size
Set by scale transformation, scale magnifies the range of unit length is larger, scale shrinks the range of unit length is smaller;
At the beginning, the coordinates of the canvas are compatible with the logical size of the canvas. As shown in the figure, blue is assumed to be the canvas, and the coordinate position is assumed to be placed in the middle
When the canvas logic size is changed without scaling the coordinates, the range of unit length representation remains unchanged
When the canvas logic size is changed and the scaling coordinate is the same, it becomes consistent again, but the range of unit length is larger
Note the transformation of the coordinate system, the canvas will be invalid after clearing!!
High power screen blur
why
For devices with high DPI, there are more pixels in the same visual area. It is understandable that the logical size of canvas is the same as the physical size. However, for devices with high DPI, because of more pixels, the scope of canvas’s physical size is larger. Therefore, when the logical size range is less than the physical size range, it is equivalent to stretching the canvas, so it will become blurred
plan
Therefore, in order to draw clearly on a high-power screen, the key is to know the pixel ratio of the device on the current screen, and then enlarge the logical size of canvas by ratio times, and then the physical size (area) of canvas remains unchanged, so that in the view of the browser, area*ratio is pixels (physical size). The canvas logical size also has area*ratio pixels, which is exactly the same.
code
After the canvasDPI function obtains the pixel ratio of the screen, it expands and shrinks the logical size of the canvas by ratio times, as well as the size of the coordinate system by ratio times, so that we can accurately locate the drawing position on the canvas after the subsequent drawing without considering whether to expand and shrink the coordinates
➡ ️ stackblitz.com/edit/js-gfk…
/** * Handle canvas blur at high magnification screen *@return * Const ratio = canvasDPI(canvas) */
function canvasDPI(canvas, customWidth, customHeight) {
if(! canvas)return 1;
const width = customWidth ||
canvas.width ||
canvas.clientWidth;
const height = customHeight ||
canvas.height ||
canvas.clientHeight;
const context = canvas.getContext('2d')
const deviceRatio = window.devicePixelRatio || 1;
const bsRatio = context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
const ratio = deviceRatio / bsRatio;
if(deviceRatio ! == bsRatio) { canvas.width =Math.round(width * ratio);
canvas.height = Math.round(height * ratio);
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
context.scale(ratio, ratio);
}
return ratio;
}
canvasDPI(canvas);
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = "anonymous";
img.src="Https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1100304840, & FM = 26 & gp = 0. 1265248222 JPG";
img.onload = function() {
ctx.drawImage(img, 0.0.200.100)}Copy the code
Canvas implements picture magnifier
The full code and effects can be found at ➡️ : stackblitz.com/edit/js-seb…
The overall idea is to draw a complete picture with one canvas, monitor the mouse event to calculate the position in the canvas coordinate, extract the picture data within the specified range of the point and put it into another canvas for magnification display.
When drawing a complete picture to canvas, the traditional rule is to apply high DPI to canvas to avoid blurring
const canvasBg = document.getElementById('bg');
const ratio = canvasDPI(canvasBg, 200.100);
const ctxBg = canvasBg.getContext('2d');
const img = new Image();
img.crossOrigin = "anonymous";// Implement an image using a cross-domain element in the canvas
img.src="Https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1100304840, & FM = 26 & gp = 0. 1265248222 JPG";
img.onload = function() {
ctxBg.drawImage(img, 0.0.200.100)}Copy the code
Listen for mouse events and convert the mouse position to the position in the canvas
let pickX, pickY, ifEmit, radius=30, rect=canvasBg.getBoundingClientRect();
function attachListener(canvas) {
canvas.addEventListener('mousedown', onMousedown);
canvas.addEventListener('touchstart', onMousedown);
document.addEventListener('mousemove',
onMousemove);
document.addEventListener('touchmove',
onMousemove);
document.addEventListener('mouseup',
onMouseup);
document.addEventListener('touchend',
onMouseup);
}
function onMousedown(e) {
ifEmit = +new Date(a); xy2canvas(e); startLoupe(); }function onMousemove(e) {
ifEmit && xy2canvas(e);
}
function onMouseup(e) {
pickX = null;
pickY = null;
ifEmit = null;
stopLoupe();
}
function xy2canvas(e) {
const {x, y} = getEventXY(e);
pickX = x - rect.left;
pickY = y - rect.top;
}
function getEventXY(e) {
return {
x: e.clientX || (e.touches && e.touches[0] && e.touches[0].clientX) || (e.changedTouches && e.changedTouches[0] && e.changedTouches[0].clientX),
y: e.clientY || (e.touches && e.touches[0] && e.touches[0].clientY) || (e.changedTouches && e.changedTouches[0] && e.changedTouches[0].clientX)
}
}
Copy the code
- Here, we listen for events on both canvas and document, so that we can continue to update the position when the mouse moves off the canvas, and continue to draw when the mouse moves back to the canvas, as shown in the figure. At the same time, doucument does not listen for the start event, so that the touch event starts from the canvas. Another way to use ifEmit to control the complete sequence of one touch is to start–>move–> Up in the canvas
- Event of the Mouse class on the PC
OffsetX is similar to clientX, but contains browser borders such as toolbars, navigation bars, screenX based on screen size, and pageX based on page sizeCopy the code
- Touch class events on the mobile end
Touches: A list of all touches on the current screen; TargetTouches: a list of all touch points on the current element; ChangedTouches: List of touches that trigger the eventCopy the code
The difference between analysis
- When a finger touches the screen, all three properties have the same value and only one data
- The second finger touches the screen, ‘Touches’ has two data points, one for each finger; TargetTouches will have two data only if the second finger is placed in the same element as the first finger; ChangedTouches only has data on the second finger, which is the cause of the incident
- When two fingers are down at the same time, all three attributes have the same value, and each has two data points
- When you swipe your finger, all three values change
- With a finger removed from the screen, the corresponding elements in ‘Touches’ and’ targetTouches’ are removed simultaneously, while ‘changedTouches’ elements remain.
- ‘Touches’ and’ targetTouches’ will no longer have a value when all fingers are removed from the screen, and ‘changedTouches’ will have a value that’ touches’ the last finger to leave the screen.
- You can use touches or targetTouches in ‘TouchStart’ and ‘TouchMove’. Use changedTouches in TouchEnd
I have compatible touch events on both ends in the getEventXY function
Extract the local data of the image at the specified position and draw it to the magnifying glass canvas
const canvasLoupe = document.getElementById('loupe');
const ctxLoupe = canvasLoupe.getContext('2d');
let timeId, colorInfo;
function startLoupe() {
canvasLoupe.style.width = `The ${2*radius}px`;
canvasLoupe.style.height = `The ${2*radius}px`;
timeId = setInterval(() = >{
if(typeof pickX === 'number') { colorInfo = getImageData(pickX, pickY) drawLoupe(colorInfo); }},60)}function stopLoupe() {
clearInterval(timeId);
colorInfo = null;
canvasLoupe.style.width = 0;
canvasLoupe.style.height = 0;
}
function getImageData(x, y) {
const imageData = ctxBg.getImageData(x*ratio-radius, y*ratio-radius, 2*radius, 2*radius);
return {
x,
y,
data: imageData.data,
width: imageData.width,
height: imageData.height
}
}
function drawLoupe(colorInfo) {
if(! colorInfo)return;
const {x, y, width, height, data} = colorInfo;
canvasLoupe.width = width;
canvasLoupe.height= height;
const tmpCanvas = document.createElement('canvas');
tmpCanvas.width = width;
tmpCanvas.height = height;
const tmpCtx = tmpCanvas.getContext('2d');
const imageData = tmpCtx.createImageData(width, height);
imageData.data.set(data);
tmpCtx.putImageData(imageData, 0.0);
ctxLoupe.scale(zoomScale, zoomScale);
ctxLoupe.drawImage(tmpCanvas,0.0,width, height,0.0, width, height);
canvasLoupe.style.left = `${x-width/2}px`;
canvasLoupe.style.top = `${y-height/2}px`;
}
Copy the code
-
In drawLoupe, the ImageData is first constructed into ImageData object, which is placed on the temporary canvas, and then drawn to the magnifying glass canvas using the drawImage
-
Here we talk about the frequency and timing of drawing. In general, the start event starts drawing, then the move event triggers several times to draw, and the up event stops drawing. I’ve stripped the draw operation out of the high-frequency touch event and put it in a custom setInterval to lower the frequency and improve performance.
Execute startLoupe at start, then start setInterval every 60ms (same as the screen refresh rate), then move updates pickX and Elliot, Finally, execute stopLoupe on an UP event to stop setInterval.
Test, the current scheme three move events will have a startLoupe, magnifying glass real-time effect will not be stuck
Refer to the link
- www.twle.cn/l/yufei/can…
- www.jianshu.com/p/5562ea676…