canvas-坐标系、圆角矩形、纹理、剪裁

坐标系

画布大小:由canvas标签上设置的width,height决定,默认是 300 x 150,这也是画布坐标系x轴和y轴的最大值,超过这两个值,则意味着超过了画布大小,超过的部分自然不会生效。
canvas标签大小:由css样式的width,height决定,默认是画布的大小。
特殊情况: 画布大小和canvas标签大小不相等的时候,画布会被缩放到跟标签大小一样。缩放不是等比例的,并且缩放完成后,画布的坐标系不变。因此最好把canvas标签的css大小和canvas画布大小设置为一致。

默认画布(300 * 150)

<canvas id="canvas1" style={{ width: '100px', height: '100px' }}></canvas>
复制代码
ctx.moveTo(0, 0);
ctx.lineTo(300, 150);
ctx.stroke();
复制代码


可以看到从(0,0)到(300, 150)这条线是从左上角到右下角的,可见画布被不等比例缩放到跟标签一样大,同时坐标系还是画布的大小:300 * 150。
当画布较小,而canvas标签比较大的时候,图形就会被放大,变形变模糊:

<canvas id="canvas2" width={10} height={20} style={{ width: '100px', height: '100px' }}></canvas>
复制代码
ctx.moveTo(0, 0);
ctx.lineTo(10, 20);
ctx.stroke();
复制代码

在2倍屏和3倍屏上,可把画布大小设置成标签大小的2倍和3倍,这样可以实现1px细线的效果,同时使线条更细腻

<canvas id="canvas3" width={200} height={200} style={{ width: '100px', height: '100px' }}></canvas>
复制代码
ctx.moveTo(0, 0);
ctx.lineTo(200, 200);
ctx.stroke();
复制代码

圆角矩形

要实现圆角矩形,先来了解一下画圆的api。

arc(x, y, r, sAngle, eAngle, counterclockwise)

参数描述
x圆的中心的 x 坐标。
y圆的中心的 y 坐标。
r圆的半径。
sAngle起始角,以弧度计。(弧的圆形的三点钟位置是 0 度)。
eAngle结束角,以弧度计。
counterclockwise可选。规定应该逆时针还是顺时针绘图,默认值false。false = 顺时针,true = 逆时针。

画圆

<canvas id="canvas11" width={100} height={100}></canvas>
复制代码
ctx.arc(50, 50, 20, 0, 2 * Math.PI);
复制代码

圆弧

<canvas id="canvas10" width={100} height={100}></canvas>
复制代码
ctx.arc(20, 20, 20, Math.PI, 1.5 * Math.PI);    // 左上角
ctx.arc(80, 20, 20, 1.5 * Math.PI, 2 * Math.PI);    // 右上角
ctx.arc(20, 80, 20, 0.5 * Math.PI, Math.PI);    // 左下角
ctx.arc(80, 80, 20, 0, 0.5 * Math.PI);    // 右下角
复制代码

有了这四段圆弧,再利用 closePath方法会连接路径的特点,即可画出圆角矩形了。来封装一个画圆角矩形的函数:

const drawRoundedRect = (ctx, x, y, width, height, radius, type) => {
  ctx.moveTo(x, y + radius);
  ctx.beginPath();
  ctx.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI);
  ctx.arc(x + width - radius, y + radius, radius, 1.5 * Math.PI, 2 * Math.PI);
  ctx.arc(x + width - radius, y + height - radius, radius, 0, 0.5 * Math.PI);
  ctx.arc(x + radius, y + height - radius, radius, 0.5 * Math.PI, Math.PI);
  ctx.closePath();
  const method = type || 'stroke';  // 默认描边,传入fill即可填充矩形
  ctx[method]();
};

drawRoundedRect(ctx, 20, 20, 50, 50, 10);
复制代码

纹理

createPattern(img, "repeat|repeat-x|repeat-y|no-repeat")
参数描述
img规定要使用的图片、画布或视频元素。
repeat默认。该模式在水平和垂直方向重复。
repeat-x该模式只在水平方向重复。
repeat-y该模式只在垂直方向重复。
no-repeat该模式只显示一次(不重复)。

原图

尝试如下代码:

<canvas id="canvas13" width={400} height={300}></canvas>
复制代码
const img = new Image();
img.onload = () => {
console.log('catImg', img.width, img.height);
const pat = ctx.createPattern(img, 'no-repeat');
ctx.fillStyle = pat;
drawRoundedRect(ctx, 130, 30, 250, 260, 10, 'fill');
};
img.src = catImgSrc;
复制代码

从结果里发现两个问题:

  1. 图片的左上角和矩形的左上角不在同一点上;
  2. 矩形貌似少了右边和下边的部分。

接下来尝试修改代码里的 no-repeatrepeat:

结合这两个结果和上面的问题,总结出纹理的如下特点:

  1. 纹理是从画布的(0,0)点开始无缩放渲染的;
  2. 设置no-repeat会导致那些没有纹理覆盖的区域是空白。

这意味着我们不能自由的使用纹理来实现圆角图片的效果。 接下来了解一下canvas提供的专门用于图片剪裁的api。

剪裁

剪裁分为画布的剪裁clip和图片的剪裁drawImage,先来介绍一下图片的剪裁。

drawImage(img, sx, sy, swidth, sheight, x, y, width, height)

参数描述
img规定要使用的图像、画布或视频。
sx可选。开始剪切的 x 坐标位置。
sy可选。开始剪切的 y 坐标位置。
swidth可选。被剪切图像的宽度。
sheight可选。被剪切图像的高度。
x在画布上放置图像的 x 坐标位置。
y在画布上放置图像的 y 坐标位置。
width可选。要使用的图像的宽度。(伸展或缩小图像)
height可选。要使用的图像的高度。(伸展或缩小图像)

drawImage可接受3、5、9个参数。 接下来介绍一下分别传这几个参数的表现,先看原图:
大小:1080 * 720

三个参数

会被当做:img、x、y。图片不会缩放和剪裁,直接渲染到画布上,超过画布的区域被隐藏,也可理解成超过画布的区域被剪掉了。

    const img = new Image();
    img.onload = () => {
      console.log('img:', img.width, img.height);
      ctx.drawImage(img, 10, 20);
    };
    img.src = imgSrc;
复制代码

九个参数

按照上面表格的定义进行剪裁和缩放。

const img = new Image();
img.onload = () => {
  ctx.drawImage(img, 0, 0, 500, 500, 30, 30, 150, 150);
};
img.src = imgSrc;
复制代码

五个参数

img、x、y、width、height,此时图片不会被剪裁,而是直接缩放到目标宽高,且是不等比例的。

const img = new Image();
img.onload = () => {
  ctx.drawImage(img, 0, 0, 150, 150);
};
img.src = imgSrc;
复制代码

接下来传入9个参数,剪裁图片的宽高等于图片的宽高,来验证和5个参数是一样的效果:

const img = new Image();
img.onload = () => {
  ctx.drawImage(img, 0, 0, 1080, 720, 0, 0, 150, 150);
};
img.src = imgSrc;
复制代码

另外,从原图上剪裁的时候,不要超过图片的宽高,否则会出现空白。
drawImage这个api能实现把图片剪裁成直角矩形,却不能实现圆角的效果。而上面的纹理方法,能实现圆角图片,但效果不是十分理想,毕竟它是用来做纹理的,而不是图片剪裁。接下来再了解一个api: clip,通过它配合drawImage,能实现把图片剪裁成圆角矩形、圆形、甚至任意形状。

clip()

clip()方法就是把画布中的某个区域临时剪切出来,剪切之前要定义这个区域的路径。剪切以后,所有的绘制只有落在这个区域里才会生效,在这个区域外的不会生效。之所以说“临时”,是因为如果在剪切之前调用了save()方法,则画布状态会被保存下来,之后调用restore()方法即可恢复之前的状态,即 clip 的那个区域的限制不再继续生效,而之前落在区域外的绘制也不会因为 restore 而被绘制出来。 尝试如下代码:

<canvas id="canvas15" width={150} height={150}></canvas>
复制代码
// 定义一个区域
drawRoundedRect(ctx, 20, 20, 100, 100, 10);
    
const img = new Image();
img.onload = () => {
  ctx.save();
  ctx.clip();
  ctx.drawImage(img, 0, 0, 100, 100);
};
img.src = imgSrc;
复制代码

效果:

看到这个效果很兴奋,接下来只要把图片的目标区域先从画布上剪切下来,再调用drawImage去绘制图片,则图片就会变成想要的形状。至于原始图片,则可以通过 drawImage先剪裁一个想要的区域,再进行绘制。
从原图上剪裁出一部分,再绘制成两个圆角的图片:

<canvas id="canvas16" width={300} height={200}></canvas>
复制代码
const img = new Image();
img.onload = () => {
  ctx.save();

  ctx.strokeStyle = '#fff';
  drawRoundedRect(ctx, 20, 20, 100, 100, 10);
  ctx.clip();
  ctx.drawImage(img, 500, 100, 500, 500, 20, 20, 100, 100);
  ctx.restore();

  ctx.strokeStyle = '#fff';
  drawRoundedRect(ctx, 150, 20, 100, 100, 5);
  ctx.clip();
  ctx.drawImage(img, 500, 100, 500, 500, 150, 20, 100, 100);
};
img.src = imgSrc;
复制代码

效果:

接下来封装一个能实现圆角功能的 drawImage:

const drawRoundedImage = (ctx, radius, img, sx, sy, swidth, sheight, x, y, width, height) => {
  ctx.save();
  ctx.moveTo(x, y + radius);
  ctx.beginPath();
  if (width === height && radius >= width / 2) {
    ctx.arc(x + radius, y + radius, radius, 0, 2 * Math.PI);
  } else {
    ctx.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI);
    ctx.arc(x + width - radius, y + radius, radius, 1.5 * Math.PI, 2 * Math.PI);
    ctx.arc(x + width - radius, y + height - radius, radius, 0, 0.5 * Math.PI);
    ctx.arc(x + radius, y + height - radius, radius, 0.5 * Math.PI, Math.PI);
  }
  ctx.closePath();
  ctx.clip();
  ctx.drawImage(img, sx, sy, swidth, sheight, x, y, width, height);
  ctx.restore();
};

const img = new Image();
img.onload = () => {
  drawRoundedImage(ctx, 10, img, (1080 - 720) / 2, 0, 720, 720, 10, 10, 180, 180);
};
img.src = imgSrc;
复制代码

效果:

如果把上面的绘制路径部分提出来当成参数传入,则可实现用户自定义图形然后将图片剪裁成该形状的功能:

const drawImageToWhatYouWant = (ctx, getPath, img, sx, sy, swidth, sheight, x, y, width, height) => {
  ctx.save();
  getPath(ctx);  // 自定义图形的路径
  ctx.clip();
  ctx.drawImage(img, sx, sy, swidth, sheight, x, y, width, height);
  ctx.restore();
};
复制代码

保存图片

toDataURL([type, encoderOptions])

参数描述
type图片格式,默认为image/png
encoderOptions在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
调用:
const imgStr = canvas.toDataURL("image/jpeg", 1.0);
复制代码

注意:当画布中包含图片时,此图片必须是允许跨域的,否则调用toDataURL 会报错!

查看完整代码示例:github.com/vivimee/lea…

转载于:https://juejin.im/post/5c0b6d88f265da6178313ec4

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要利用js绘制圆角矩形,我们可以使用Canvas标签和它的API来实现。 首先,我们需要获得一个指向Canvas元素的引用,这样我们就可以利用其API进行绘图操作。可以通过JavaScript代码来获取该引用,如下所示: ```javascript var canvas = document.getElementById("myCanvas"); ``` 接下来,我们需要获得一个指针来操作Canvas的绘图环境,我们可以使用getContext方法来实现,如下所示: ```javascript var ctx = canvas.getContext("2d"); ``` 然后,我们需要设置圆角矩形的相关属性,包括位置,大小和圆角半径,如下所示: ```javascript var x = 50; // 圆角矩形左上角的横坐标 var y = 50; // 圆角矩形左上角的纵坐标 var width = 200; // 圆角矩形的宽度 var height = 100; // 圆角矩形的高度 var radius = 20; // 圆角的半径 ``` 接下来,我们可以开始绘制圆角矩形了。首先,通过调用beginPath方法启动一个新的路径,然后使用arcTo方法来绘制圆角的边,最后使用closePath方法来闭合路径。代码如下所示: ```javascript ctx.beginPath(); ctx.moveTo(x + radius, y); // 到达右上角的起始点 ctx.arcTo(x + width, y, x + width, y + radius, radius); // 绘制右上角的圆角 ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius); // 绘制右下角的圆角 ctx.arcTo(x, y + height, x, y + height - radius, radius); // 绘制左下角的圆角 ctx.arcTo(x, y, x + radius, y, radius); // 绘制左上角的圆角 ctx.closePath(); // 闭合路径 ``` 最后,我们可以设置圆角矩形的样式和颜色,然后调用fill方法来填充圆角矩形的内部。代码如下所示: ```javascript ctx.fillStyle = "red"; // 设置圆角矩形的填充颜色 ctx.fill(); // 填充圆角矩形的内部 ``` 以上就是利用Canvas和JavaScript绘制圆角矩形的步骤。通过控制圆角矩形的位置、大小、圆角半径以及样式和颜色,我们可以在网页上实现各种个性化的圆角矩形效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值