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!!

With these three things in mind, the problem of blurring the image without processing is clear

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

  1. When a finger touches the screen, all three properties have the same value and only one data
  2. 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
  3. When two fingers are down at the same time, all three attributes have the same value, and each has two data points
  4. When you swipe your finger, all three values change
  5. With a finger removed from the screen, the corresponding elements in ‘Touches’ and’ targetTouches’ are removed simultaneously, while ‘changedTouches’ elements remain.
  6. ‘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.
  7. 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

  1. www.twle.cn/l/yufei/can…
  2. www.jianshu.com/p/5562ea676…