Detailed Canvas 02-Style and Color Control

In the chapter on drawing graphics, I only use the default line and fill styles. In this chapter, we will explore all the options of canvas to draw more attractive content.

#colorColors _

So far we've only seen methods that draw content. If we want to color graphics, there are two important properties to do it: fillStyle and strokeStyle.

fillStyle = color

Sets the fill color of the shape.

strokeStyle = color

Sets the color of the graph outline.

color can be a string representing a CSS color value, a gradient object or a pattern object. We'll come back to gradient and pattern objects later. By default, both line and fill colors are black (CSS color value #000000).

Note:  Once you set the value of strokeStyle or fillStyle, the new value will become the default value of the newly drawn graphics. If you want to give each shape a different color, you need to reset the value of fillStyle or strokeStyle.

// 这些 fillStyle 的值均为 '橙色'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";

#fillStyleExample _

In this example, I will again use two layers of for loops to draw the array of squares, with each square being a different color. The result is shown on the right, but the code used to implement it is not so gorgeous. I used two variables i and j to generate unique RGB color values ​​for each square, only modifying the red and green channel values ​​while leaving the blue channel value unchanged. You can generate a variety of color palettes by modifying the values ​​of these color channels. By increasing the frequency of gradients, you can also paint palettes like those in Photoshop.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i=0;i<6;i++){
    for (var j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
}

# strokeStyle example

This example is somewhat similar to the above, but this time using the strokeStyle attribute, instead of drawing a square, use the arc method to draw a circle.

  function draw() {
    var ctx = document.getElementById('canvas').getContext('2d');
    for (var i=0;i<6;i++){
      for (var j=0;j<6;j++){
        ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
                         Math.floor(255-42.5*j) + ')';
        ctx.beginPath();
        ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
        ctx.stroke();
      }
    }
  }

#TransparencyTransparency _

In addition to drawing solid color graphics, we can also use canvas to draw translucent graphics. Either set the globalAlpha property or use a translucent color as the outline or fill style.

globalAlpha = transparencyValue

This property affects the transparency of all graphics in the canvas. The valid value range is 0.0 (completely transparent) to 1.0 (completely opaque), and the default is 1.0.

The globalAlpha property is quite efficient when you need to draw a large number of graphics with the same transparency. However, I think the following method is a little more maneuverable.

Since the strokeStyle and fillStyle properties accept color values ​​that conform to the CSS 3 specification, we can use the following writing method to set the color with transparency.

// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";

The rgba() method is similar to the rgb() method, except that there is one more parameter for setting the color transparency. Its valid range is from 0.0 (fully transparent) to 1.0 (fully opaque).

# globalAlpha example

In this example, we will use a four-color grid as the background, set globalAlpha to 0.2, and draw a series of translucent circles with increasing radii on it. The end result is a radial gradient effect. The more circles are superimposed, the less transparent the originally drawn circle will be. By increasing the number of loops and drawing more circles, the background image will gradually disappear from the center to the edge.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  // 画背景
  ctx.fillStyle = '#FD0';
  ctx.fillRect(0,0,75,75);
  ctx.fillStyle = '#6C0';
  ctx.fillRect(75,0,75,75);
  ctx.fillStyle = '#09F';
  ctx.fillRect(0,75,75,75);
  ctx.fillStyle = '#F30';
  ctx.fillRect(75,75,75,75);
  ctx.fillStyle = '#FFF';

  // 设置透明度值
  ctx.globalAlpha = 0.2;

  // 画半透明圆
  for (var i=0;i<7;i++){
      ctx.beginPath();
      ctx.arc(75,75,10+10*i,0,Math.PI*2,true);
      ctx.fill();
  }
}

# rgba() example

The second example is similar to the one above, but instead of drawing a circle, draw a rectangle. It can also be seen here that rgba() can set the outline and fill styles separately, so it has better operability and flexibility.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // 画背景
  ctx.fillStyle = 'rgb(255,221,0)';
  ctx.fillRect(0,0,150,37.5);
  ctx.fillStyle = 'rgb(102,204,0)';
  ctx.fillRect(0,37.5,150,37.5);
  ctx.fillStyle = 'rgb(0,153,255)';
  ctx.fillRect(0,75,150,37.5);
  ctx.fillStyle = 'rgb(255,51,0)';
  ctx.fillRect(0,112.5,150,37.5);

  // 画半透明矩形
  for (var i=0;i<10;i++){
    ctx.fillStyle = 'rgba(255,255,255,'+(i+1)/10+')';
    for (var j=0;j<4;j++){
      ctx.fillRect(5+i*14,5+j*37.5,14,27.5)
    }
  }
}

#Linear Line styles

The style of the line can be set through a series of properties.

lineWidth = value

Sets the line width.

lineCap = type

Sets the line end style.

lineJoin = type

Sets the style of the junction between lines.

miterLimit = value

Limit the maximum length of the junction when two lines intersect; the so-called junction length (miter length) refers to the length from the inner corner vertex to the outer corner vertex where the lines meet.

getLineDash()

Returns an array of non-negative even length containing the current dash style.

setLineDash(segments)

Sets the current dash style.

lineDashOffset = value

Sets the starting offset of the dotted line style.

It may be easier to understand through the following examples.

# Example of lineWidth property

This property sets the thickness of the currently drawn line. Attribute value must be a positive number. The default value is 1.0.

Lineweight refers to the thickness from the center to the sides of a given path. In other words, half the line width is drawn on either side of the path. Because canvas coordinates do not directly correspond to pixels, special care must be taken when obtaining exact horizontal or vertical lines.

In the example below, 10 lines are drawn with increasing widths. The leftmost line is 1.0 units thick. Also, the leftmost and all lines with odd widths are not rendered accurately, because of the positioning of the path.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i = 0; i < 10; i++){
    ctx.lineWidth = 1+i;
    ctx.beginPath();
    ctx.moveTo(5+i*14,5);
    ctx.lineTo(5+i*14,140);
    ctx.stroke();
  }
}

Accurate lines require an understanding of how lines are drawn. As shown in the figure below, a grid is used to represent the coordinate grid of the canvas, and each grid corresponds to a pixel on the screen. In the first figure, the rectangle from (2,1) to (5,5) is filled, and the boundary of the whole area just falls on the edge of the pixel, so that the resulting rectangle has sharp edges.

If you want to draw a line from (3,1) to (3,5) with a width of 1.0, you will get the result like the second image. The actual filled area (dark blue) only extends to half a pixel on either side of the path. And those half pixels are rendered in an approximate way, which means those pixels are only partially colored, and the result is that the entire area (the light and dark blue parts) is filled with a half-tone color of the actual stroke color. This is why a line with a width of 1.0 in the example above is not accurate.

To solve this problem, you must exert more precise control over the path. It is known that a line with a thickness of 1.0 will extend half a pixel on both sides of the path, so draw a line from (3.5,1) to (3.5,5) like the third picture, and its edge just falls on the pixel boundary, and the filling is accurate A line with a width of 1.0.

Note:  In the example of the vertical line, its Y coordinate just falls on the grid line, otherwise half-rendered pixels will also appear on the endpoint (but also note that the performance of this behavior depends on the current lineCap style, It defaults to butt; you may want to get strokes that coincide with half-pixel coordinates of odd-width lines by setting the lineCap style to square, so that the outer borders of the endpoint outlines will be automatically expanded to completely cover the entire pixel grid) .

Note also that only the start and end points of the path are affected by this: if a path is closed via closePath(), it has no start and end; in the opposite case, all endpoints on the path are connected to the previous point , the next path uses the current lineJoin setting (default is miter), if the connected path is horizontal and/or vertical, it will cause the outer contour of the connected path to automatically extend according to the intersection point, so the rendered path outline will cover the entire pixel grid . The next two subsections demonstrate these additional line styles.

For lines with an even width, where the number of pixels on each side is an integer, you want the path to fall between pixels (eg, from (3,1) to (3,5)) rather than between pixels the middle of the point. Also, notice that the vertical lines in that example have their Y coordinates falling exactly on the grid lines, if not, there will also be half-rendered pixels at the endpoints.

While it's a bit of a pain to start working with scalable 2D graphics, early attention to the relationship between the pixel grid and path position can ensure graphics keep looking good after scaling or any other transformations: Lineweight A vertical line of 1.0 will become a clear line width of 2.0 after being enlarged by 2 times, and it will appear where it should appear.

# Example of lineCap property

The value of the property lineCap determines how the end points of the line segments are displayed. It can be one of the following three: butt, round and square. The default is butt.

In this example, I draw three straight lines and give them different lineCap values. There are also two auxiliary lines. In order to see the difference between them more clearly, the starting and ending points of the three lines all fall on the auxiliary line.

The leftmost line uses the default butt. Note that it is flush with the guideline. The middle one is the effect of round, and a semicircle with a radius of half the line width is added to the endpoint. The one on the right is the effect of square, with squares of equal width and half the line width added to the endpoints.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var lineCap = ['butt','round','square'];

  // 创建路径
  ctx.strokeStyle = '#09f';
  ctx.beginPath();
  ctx.moveTo(10,10);
  ctx.lineTo(140,10);
  ctx.moveTo(10,140);
  ctx.lineTo(140,140);
  ctx.stroke();

  // 画线条
  ctx.strokeStyle = 'black';
  for (var i=0;i<lineCap.length;i++){
    ctx.lineWidth = 15;
    ctx.lineCap = lineCap[i];
    ctx.beginPath();
    ctx.moveTo(25+i*50,10);
    ctx.lineTo(25+i*50,140);
    ctx.stroke();
  }
}

# Example of lineJoin attribute

The property value of lineJoin determines how the connection between two line segments is displayed in the graph. It can be one of three: round, bevel and miter. The default is miter.

Here I also use three polylines as an example, and set different lineJoin values ​​respectively. The top one is the effect of round, the corners are rounded, and the radius of the circle is equal to the line width. The middle and bottom ones are the effects of bevel and miter respectively. When the value is miter, the line segment will extend outside the connection until it intersects at a point, and the extension effect is restricted by the miterLimit property described below.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var lineJoin = ['round', 'bevel', 'miter'];
  ctx.lineWidth = 10;
  for (var i = 0; i < lineJoin.length; i++) {
    ctx.lineJoin = lineJoin[i];
    ctx.beginPath();
    ctx.moveTo(-5, 5 + i * 40);
    ctx.lineTo(35, 45 + i * 40);
    ctx.lineTo(75, 5 + i * 40);
    ctx.lineTo(115, 45 + i * 40);
    ctx.lineTo(155, 5 + i * 40);
    ctx.stroke();
  }
}

# Demonstration example of miterLimit property

As seen in the previous example with the effect of miter applied, the outer edges of the line segments will be extended to meet at a point. When the angle between the line segments is relatively large, the intersection point will not be too far away, but as the angle becomes smaller, the distance between the intersection points will increase exponentially.

The miterLimit attribute is used to set the maximum distance between the extension intersection and the connection point. If the intersection distance is greater than this value, the connection effect will become bevel. Note that the maximum miter length (that is, the intersection distance) is the product of the line width measured in the current coordinate system and the value of the miterLimit attribute (HTML canvas defaults to 10.0), so miterLimit can be set independently and is not affected by display scale changes or any affine transformations Affects: It only affects the effectively drawn shape at the edge of the line.

More precisely, the miter limit (miterLimit) is the maximum allowable ratio of the extension length (in HTML Canvas, the distance between the joining point outside the line segment and the point specified in the path) to half the line width. It can also be equivalently defined as the maximum allowable ratio of the distance between the inner and outer connection points (miterLength) of the line and the line width (lineWidth) (because the path point is the midpoint of the inner and outer connection points). This is equivalent to the cosecant of half the smallest inner angle ( θ ) of the intersecting line segments, less than which miter joins will not be rendered, only hypotenuse joins:

  • miterLimit =  max  miterLength / lineWidth = 1 /  sin  (  min  θ  / 2 )
  • The miter limit defaults to 10.0, which will remove all miters smaller than about 11 degrees.
  • A miter limit of √2 ≈ 1.4142136 (rounded up) removes all miters at acute angles, leaving only obtuse or right angles.
  • 1.0 is a legal miter limit, but this strips all miters.
  • Values ​​less than 1.0 are not legal miter limits.

In the small example below, you can dynamically set the value of miterLimit and see its effect on the graphics in the canvas. The blue lines indicate the start and end points of each line in the zigzag pattern (and also the connection points between different line segments).

In this example, when you set the value of miterLimit to be less than 4.2, the corners of the visible part of the graph will not extend and intersect, but will show a beveled edge connection effect at the edge of the blue line; when the value of miterLimit is greater than 10.0, this example Most of the corners in will intersect away from the blue line, and from left to right, the distance decreases as the angle increases; and the effect of values ​​between the above values ​​is also between in between.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // 清空画布
  ctx.clearRect(0, 0, 150, 150);

  // 绘制参考线
  ctx.strokeStyle = '#09f';
  ctx.lineWidth   = 2;
  ctx.strokeRect(-5, 50, 160, 50);

  // 设置线条样式
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 10;

  // 检查输入
  if (document.getElementById('miterLimit').value.match(/\d+(\.\d+)?/)) {
    ctx.miterLimit = parseFloat(document.getElementById('miterLimit').value);
  } else {
    alert('Value must be a positive number');
  }

  // 绘制线条
  ctx.beginPath();
  ctx.moveTo(0, 100);
  for (i = 0; i < 24 ; i++) {
    var dy = i % 2 == 0 ? 25 : -25;
    ctx.lineTo(Math.pow(i, 1.5) * 2, 75 + dy);
  }
  ctx.stroke();
  return false;
}

# use dotted lines

Use the setLineDash method and the lineDashOffset property to specify the dashed line style. The setLineDash method accepts an array to specify the alternation of line segments and gaps; the lineDashOffset property sets the starting offset.

In this example we are going to create an ant line effect. It is often used in the animation of selection tools in computer graphics programs. It helps users to distinguish the selection border from the image background by animating the border. You can learn how to achieve this and other basic animations later in this tutorial.

var ctx = document.getElementById('canvas').getContext('2d');
var offset = 0;

function draw() {
  ctx.clearRect(0,0, canvas.width, canvas.height);
  ctx.setLineDash([4, 2]);
  ctx.lineDashOffset = -offset;
  ctx.strokeRect(10,10, 100, 100);
}

function march() {
  offset++;
  if (offset > 16) {
    offset = 0;
  }
  draw();
  setTimeout(march, 20);
}

march();

#gradient Gradients

Just like in general drawing software, we can fill or stroke with linear or radial gradients. We use the following method to create a canvasGradient object and assign it to the fillStyle or strokeStyle property of the graphics.

createLinearGradient(x1, y1, x2, y2)

The createLinearGradient method accepts 4 parameters, indicating the starting point (x1, y1) and ending point (x2, y2) of the gradient.

createRadialGradient(x1, y1, r1, x2, y2, r2)

The createRadialGradient method accepts 6 parameters, the first three define a circle with (x1, y1) as the origin and radius r1, and the last three parameters define another circle with (x2, y2) as the origin and radius r2.

var lineargradient = ctx.createLinearGradient(0,0,150,150);
var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100);

After creating the canvasGradient object, we can use the addColorStop method to color it.

gradient.addColorStop(position, color)

The addColorStop method accepts 2 parameters, and the position parameter must be a value between 0.0 and 1.0, indicating the relative position of the color in the gradient. For example, 0.5 means the color will appear in the middle. The color parameter must be a valid CSS color value (eg #FFF, rgba(0,0,0,1), etc.).

You can add as many color stops as you want. Below is an example of the simplest linear black and white gradient.

var lineargradient = ctx.createLinearGradient(0,0,150,150);
lineargradient.addColorStop(0,'white');
lineargradient.addColorStop(1,'black');

# createLinearGradient example

In this example, I made two different gradients. The first is the background color gradient. You will find that I set two colors for the same position. You can also use this to achieve a sudden change, like the sudden change from white to green here. In general, the definition of the color scale does not care about the order, but when the position of the color scale is repeated, the order becomes very important. So, keep the color scale definition order consistent with its ideal order, and the result should be no big problem.

For the second gradient, I don't define the color stop from 0.0 because that's not so strict. Set a black color stop at 0.5, and the gradient will default to black from the start point to the color stop.

You'll notice that both the strokeStyle and fillStyle properties accept canvasGradient objects.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // Create gradients
  var lingrad = ctx.createLinearGradient(0,0,0,150);
  lingrad.addColorStop(0, '#00ABEB');
  lingrad.addColorStop(0.5, '#fff');
  lingrad.addColorStop(0.5, '#26C000');
  lingrad.addColorStop(1, '#fff');

  var lingrad2 = ctx.createLinearGradient(0,50,0,95);
  lingrad2.addColorStop(0.5, '#000');
  lingrad2.addColorStop(1, 'rgba(0,0,0,0)');

  // assign gradients to fill and stroke styles
  ctx.fillStyle = lingrad;
  ctx.strokeStyle = lingrad2;

  // draw shapes
  ctx.fillRect(10,10,130,130);
  ctx.strokeRect(50,50,50,50);

}

# createRadialGradient example

For this example, I defined 4 different radial gradients. Because we can control the start and end points of the gradient, we can achieve some more complex effects than the classic radial gradient (as seen in Photoshop). (The classic radial gradient has only one center point, and simply expands from the center point to the outer circle)

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // 创建渐变
  var radgrad = ctx.createRadialGradient(45,45,10,52,50,30);
  radgrad.addColorStop(0, '#A7D30C');
  radgrad.addColorStop(0.9, '#019F62');
  radgrad.addColorStop(1, 'rgba(1,159,98,0)');

  var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50);
  radgrad2.addColorStop(0, '#FF5F98');
  radgrad2.addColorStop(0.75, '#FF0188');
  radgrad2.addColorStop(1, 'rgba(255,1,136,0)');

  var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40);
  radgrad3.addColorStop(0, '#00C9FF');
  radgrad3.addColorStop(0.8, '#00B5E2');
  radgrad3.addColorStop(1, 'rgba(0,201,255,0)');

  var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90);
  radgrad4.addColorStop(0, '#F4F201');
  radgrad4.addColorStop(0.8, '#E4C700');
  radgrad4.addColorStop(1, 'rgba(228,199,0,0)');

  // 画图形
  ctx.fillStyle = radgrad4;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad3;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad2;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad;
  ctx.fillRect(0,0,150,150);
}

Here, I've moved the start point slightly away from the end point to achieve a spherical 3D effect. But it's best not to let the inner and outer circles partially overlap, because it's really unclear what effect it will have.

The last color stops of the 4 radial gradients are all transparent. If you want the direct transition between the two color scales to be softer, as long as the two color values ​​are consistent. I can't see it in the code because I used two different color representation methods, but they are actually the same, #019F62 = rgba(1,159,98,1).

#pattern style Patterns

In an example in the previous section, I used a loop to achieve the pattern effect. In fact, there is a simpler method: createPattern.

createPattern(image, type)

This method accepts two parameters. Image can be a reference to an Image object, or another canvas object. Type must be one of the following string values: repeat, repeat-x, repeat-y, and no-repeat.

Note:  Using a canvas object as the Image parameter is invalid in Firefox 1.5 (Gecko 1.8).

The application of the pattern is very similar to the gradient. After creating a pattern, assign it to the fillStyle or strokeStyle attribute.

var img = new Image();
img.src = 'someimage.png';
var ptrn = ctx.createPattern(img,'repeat');

Note:  It is a bit different from drawImage, you need to make sure that the image object has been loaded, otherwise the pattern may not work correctly.

# createPattern example

In the last example, I created a pattern and assigned it to the fillStyle property. The only thing to note is that you use the Image object's onload handler to make sure the image is loaded before setting the pattern.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // 创建新 image 对象,用作图案
  var img = new Image();
  img.src = 'https://mdn.mozillademos.org/files/222/Canvas_createpattern.png';
  img.onload = function() {

    // 创建图案
    var ptrn = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = ptrn;
    ctx.fillRect(0, 0, 150, 150);

  }
}

#shadowShadows _

shadowOffsetX = float

shadowOffsetX and shadowOffsetY are used to set the extension distance of the shadow on the X and Y axes, which are not affected by the transformation matrix. A negative value means the shadow will extend up or left, a positive value means it will extend down or right, and they are both 0 by default.

shadowOffsetY = float

shadowOffsetX and shadowOffsetY are used to set the extension distance of the shadow on the X and Y axes, which are not affected by the transformation matrix. A negative value means the shadow will extend up or left, a positive value means it will extend down or right, and they are both 0 by default.

shadowBlur = float

shadowBlur is used to set the blurring degree of the shadow. Its value is not linked to the number of pixels, nor is it affected by the transformation matrix. The default is 0.

shadowColor = color

shadowColor is a standard CSS color value, used to set the shadow color effect, the default is fully transparent black.

#Example of text shadow

This example draws text with a shadow effect.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.shadowBlur = 2;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";

  ctx.font = "20px Times New Roman";
  ctx.fillStyle = "Black";
  ctx.fillText("Sample String", 5, 30);
}

We can use the next chapter to understand the content related to the text attribute and the fillText method.

# Canvas filling rules

When we use fill (or clip and isPointinPath), you can choose a fill rule, which determines whether a place is filled according to whether it is outside or inside the path. is useful sometimes.

Two possible values:

  • "nonzero ": non-zero winding rule, the default value.
  • "evenodd": even-odd winding rule.

In this example, we use the padding rule evenodd

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.beginPath();
  ctx.arc(50, 50, 30, 0, Math.PI*2, true);
  ctx.arc(50, 50, 15, 0, Math.PI*2, true);
  ctx.fill("evenodd");
}

Guess you like

Origin blog.csdn.net/qq_59747594/article/details/131350766