Canvas

1. HTML CanvasElement(IE9+)

HTML CanvasElement 可以看成是所有canvas元素的代称,是一个在浏览器可视窗口可以全局访问的接口。例如运行window.HTMLCanvasElement

ƒ HTMLCanvasElement() { [native code] }

这些接口都是DOM Level 2中出现的,因此,IE9+浏览器才支持,IE8浏览器并不支持。考虑到<canvas>元素也是IE9+开始支持的,因此,如果你有需要使用HTMLCanvasElement的场景,就无需担心兼容性问题。

类似的还有 HTMLDivElement(对应<div>元素),HTMLImageElement(对应<img>元素), HTMLParagraphElement(对应<p>元素)等一些列接口,几乎所有的标准HTML标签都有一个对应的接口名称,然后全部都继承于HTMLElement,完整继承关系见下图。

通常这些接口会内置一些属性和方法,方便用户使用。我们也可以根据自己需求,自定义一些方法,方便调用。例如,下面这个检测canvas画布上是否有透明或半透明像素点的方法:

// 检测canvas画布上是否有透明或半透明像素点
HTMLCanvasElement.prototype.isSomeAlphaPixel = function () {
    var context = this.getContext('2d');
    // 获取图片像素信息
    var imageData = context.getImageData(0, 0, this.width, this.height).data;
    // 检测有没有透明数据
    var isAlphaBackground = false;
    for (var index = 3; index < imageData.length; index += 4) {
        if (imageData[index] != 255) {
            isAlphaBackground = true;
            break;    
        }
    }
    return isAlphaBackground;
};

此时,页面上任意<canvas>元素DOM对象都可以调用isSomeAlphaPixel()方法。例如:

var canvas = document.querySelector('canvas');
// 输出true或false
console.log(canvas.isSomeAlphaPixel());

1.1. height 和 width属性

  • height 获取、设置画布高度。
  • width获取、设置画布宽度。
// 获取高度
var pixel = canvas.height;

// 设置高度
canvas.height = pixel;

// 获取宽度
var pixel = canvas.width;
// 设置宽度
canvas.width = pixel;

虽然height标准属性值只能是整数,但是实际上:

  • 如果设置小数,浏览器不会认为是不合法的。例如:

    <canvas height="88.888" style="background:green;"></canvas>

  • 经测试,各个主流浏览器下都会渲染为88像素高度。

    width="188em"
  • 如果宽度值后面是个完全不合法的字符,则按照前面的数值进行宽度解析,例如 

    width="188abc"
  • 宽度高度负数会展示为150像素,IE浏览器会展示为0

1.2. getContext()返回上下文

var context = canvas.getContext(contextType);
var context = canvas.getContext(contextType, contextAttributes);
  1. contextType:String
  • "2d"

    会创建并返回一个CanvasRenderingContext2D对象,主要用来进行2d绘制,也就是二维绘制,平面绘制。

  • "webgl"或"experimental-webgl"

此参数可以返回一个WebGLRenderingContext(WebGL渲染上下文)对象,WebGL(全写Web Graphics Library)是一种3D绘图协议,可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型,无需安装任何其他插件。此参数对应的WebGL版本1(OpenGL ES 2.0

  • "webgl2"

    此参数可以返回一个WebGL2RenderingContext对象,可以用来绘制三维3D效果。此参数对应的WebGL版本2(OpenGL ES 3.0)。不过目前这个还处于试验阶段,我们实际Kaufman都是使用'webgl'这个参数。

  • "bitmaprenderer"

    创建一个ImageBitmapRenderingContext(位图渲染上下文),可以借助给定的ImageBitmap替换Canavs的内容。

2. contextAttribute(可选):Object

contextAttributes为一个纯对象参数,下面是一个使用示意:

var gl = canvas.getContext('webgl', {
    antialias: false,
    depth: false
});

 该参数对象支持的属性值具体如下:

如果contextType参数值是'2d',则contextAttributes支持的标准属性值为:

  • alphaBoolean

表示Canavs是否包含alpha透明通道,如果设置为false,则表示Canvas不支持全透明或者半透明,在绘制带有透明效果的图形或者图像时候速度会更快一些。

如果contextType参数值是'webgl',则contextAttributes支持的标准属性值为:

  • alphaBoolean

表示Canavs是否包含透明缓冲区。

  • antialiasBoolean

表示是否需要抗边缘锯齿。如果设置为true,图像呈现质量会好一些,但是速度会拖慢。

  • depthBoolean

表示绘制缓冲区的缓冲深度至少16位。

  • failIfMajorPerformanceCaveatBoolean

表示如果用户的系统性能比较差,是否继续常见绘制上下文。

  • powerPreferenceString

高速用户使用的客户端(如浏览器)我们现在这个WebGL上下文最合适的GPU配置是什么。支持下面关键字值:

  • 'default' 

让用户的客户端设备自己觉得那个GPU配置是最合适的。这个是此参数的默认值。

  • 'high-performance' 

渲染性能优先,通常更耗掉(如手机,平板等移动设备)。

  • 'low-power' 

省电优先,渲染性能权重可以低一些。

  • premultipliedAlphaBoolean

表示页面合成器将假定绘图缓冲区包含具有alpha预乘(pre-multiplied alpha)颜色。

  • preserveDrawingBufferBoolean

如果值为true,则不会清除缓冲区并保留其值,直到作者清除或覆盖。

  • stencilBoolean

表示绘图缓冲区具有至少8位的模板缓冲区。

返回值

无论getContext()方法中的参数是什么,其返回值都可以称之为RenderingContext,再细分可以包括下面这些:

  • '2d'参数值对应的CanvasRenderingContext2D
  • 'webgl'或experimental-webgl参数值对应的WebGLRenderingContext;
  • 'webgl2'参数值对应的WebGL2RenderingContext;
  • 'bitmaprenderer'参数值对应的ImageBitmapRenderingContext。

基本上,只要我们使用canvas实现功能,getContext()方法调用100%需要使用。甚至可以这么说,只要你想用canvas实现任何平面效果,首先不管三七二十一,先把下面2行写上:

var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');

当然如果页面存在多个<canvas>元素,我们可以自己加个id或者class类名在配合选择器获取。也可以直接create创建,然后append到页面中,例如:

var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var context = canvas.getContext('2d');

如果希望<canvas>元素放在<body>元素最前面,则可以:

var canvas = document.createElement('canvas');
document.body.insertBefore(canvas, document.body.firstElementChild);
var context = canvas.getContext('2d');

1.3. toBlob()将Canvas图像转为Blob对象(IE10+)

toBlob()方法可以Canvas图像对应的Blob对象(binary large object)。此方法可以把Canvas图像缓存在磁盘上,或者存储在内存中,这个往往由浏览器决定

void canvas.toBlob(callback, mimeType, quality);

这里的void表示无返回值。

参数说明
  • CallbackFunction (回调函数)

toBlob()方法执行成功后的回调方法,支持一个参数,表示当前转换的Blob对象。

  • mimeType:(可选)String  (图片类型)

mimeType表示需要转换的图像的mimeType类型。默认值是image/png,还可以是image/jpeg,甚至image/webp(前提浏览器支持)等。

  • quality:(可选)Number (图片质量(0-1)

quality表示转换的图片质量。范围是01。由于Canvas的toBlob()方法转PNG是无损的,因此,此参数默认是没有效的,除非,指定图片mimeTypeimage/jpeg或者image/webp,此时默认压缩值是0.92

案例1:Blob图像上传

这个案例演示的是Canvas图像转换成Blob二进制对象并使用HTML5 FormData进行Ajax上传

<canvas></canvas>

假设上面<canvas>是已经绘制好的图形,我们需要Ajax提交到后台进行保存,则JavaScript代码可以这样:

var canvas = document.querySelector('canvas');
// canvas转为blob并上传
canvas.toBlob(function (blob) {
    var data = new FormData();
    // 装载图片数据
    data.append('image', blob);
    // 图片ajax上传,字段名是image
    var xhr = new XMLHttpRequest();
    // 文件上传成功
    xhr.onload = function() {
        // xhr.responseText就是返回的数据
    };
    // 开始上传
    xhr.open('POST', 'upload.php', true);
    xhr.send(data);    
});

如果我们希望把<canvas>元素图像使用<img>元素显示,toBlob()toDataURL()方法都是可以的,但个人推荐使用toBlob()方法(如果不用顾及兼容性)。

blob数据对象是无法直接作为<img>的src属性值呈现的,需要URL.createObjectURL()方法处理下。

具体使用可参见下面一个图片水印合成的例子,点击下面的按钮选择合适的图片,会得到一个Blob形式的合成图片,当我们打开开发者工具查看上图(假设你已经选择了图片),会发现其src是一个blob地址,如下截图示意:

Blob数据转URL地址关键JavaScript代码如下:

canvas.toBlob(function(blob) {
    var url = URL.createObjectURL(blob);
    p.innerHTML = '<img src="'+ url +'">';
}, 'image/jpeg');

另外,如果图片转换交互频繁,性能开销比较大,且图片仅展示无其它数据层面的交互,我们可以使用URL.revokeObjectURL(url)释放资源。不过实际开发页面通常都不复杂,不释放也没关系。​

首先,toBlob()方法IE9浏览器不支持,因为Blob数据格式IE10+才支持。

然后,对于IE浏览器,toBlob()的兼容性有些奇怪,IE10浏览器支持ms私有前缀的toBlob()方法,完整方法名称是msToBlob()。而IE11+,toBlob()方法却不支持。

但是,我们可以基于toDataURL()方法进行polyfill,性能相对会差一些,JavaScript代码如下,参考自MDN:

if (!HTMLCanvasElement.prototype.toBlob) {
  Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
    value: function (callback, type, quality) {
      var canvas = this;
      setTimeout(function() {
        var binStr = atob( canvas.toDataURL(type, quality).split(',')[1] );
        var len = binStr.length;
        var arr = new Uint8Array(len);

        for (var i = 0; i < len; i++) {
          arr[i] = binStr.charCodeAt(i);
        }

        callback(new Blob([arr], { type: type || 'image/png' }));
      });
    }
  });
}

1.4. 头、DataURL()方法

Canvas本质上就是一个位图图像,因此,浏览器提供了若干API可以将Canvas图像转换成可以作为IMG呈现的数据,其中最老牌的方法就是HTMLCanvasElement.toDataURL(),此方法可以返回Canvas图像对应的data URI,也就是平常我们所说的base64地址。格式如下:

data:[<mime type>][;base64],<data>

我们可以指定转换的图片格式,转换后的图像的分辨率为96 dPI。

canvas.toDataURL(mimeType, quality);
参数说明
  1.  mimeType:(可选)String

mimeType表示需要转换的图像的mimeType类型。默认值是image/png,还可以是image/jpeg,甚至image/webp(前提浏览器支持)等。

quality:(可选)Number

quality表示转换的图片质量。范围是01。此参数要想有效,图片的mimeType需要是image/jpeg或者image/webp,其他mimeType值无效。默认压缩质量是0.92

根据自己的肉眼分辨,如果使用toDataURL()quality参数对图片进行压缩,同样的压缩百分比呈现效果要比Adobe Photoshop差一些。

返回值:返回base64 data图片数据。

案例

案例1:最基础的使用

如下,一个10x10纯透明Canvas:

<canvas width="10" height="10"></canvas>

如下JavaScript代码可以返回此Canvas的data-URL:

var canvas = document.querySelector('canvas');
var dataURL = canvas.toDataURL();console.log(dataURL);
// 结果是:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAF0lEQVQoU2NkIBIwEqmOYVQh3pAiOngACmkAC5eMKzgAAAAASUVORK5CYII="
案例2:转换成JPG图片

如果Canvas中颜色信息非常丰富,如实物照片,则建议指定mime type为image/jpeg,data-URL数据量会大大降低,但质量也依然很OK。例如:

canvas.toDataURL('image/jpeg');

如果Canvas图像本身是2倍图(画布实际尺寸是CSS样式尺寸的2倍+),则图像理论质量0.5~0.6足矣。但考虑到Canvas本身压缩后的图像品质不算高,实际质量值建议0.6~0.7。如果是一倍图,则使用默认值即可。

canvas.toDataURL('image/jpeg', 0.6);

案例3:检测浏览器是否支持webp
var isSupportWebp = !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0;

console.log(isSupportWebp);   // true or false

2. CanvasRenderingContext2D

CanvasRenderingContext2D顾名思义就是“Canvas 2D渲染上下文”,可以理解为下面代码中的context

var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');

context暴露了大量的API属性和方法,可以用来绘制文本,图形以及像素处理等,可以说是2D Canvas应用的核心所在。

按照功能,可以对这些暴露的标准API属性和方法进行如下的分组:

绘制矩形

(1) clearRect(): 清除Canvas元素的一块矩形的内容
context.clearRect(x, y, width, height);
x: number  矩形左上角的x坐标
y: number  矩形左上角的y坐标
width: number  要清除的矩形宽度
height: number   要清除的矩形高度



// 示例
// 先绘制图片
var img = new Image();
img.onload = function () {
    context.drawImage(img, 0, 0, 250, 167);
    // 中间开个方形的洞
    context.clearRect(50, 50, 100, 66);
};
img.src = './1.jpg';

(2) fillStyle():填充canvas
context.fillStyle = color;//纯色填充
context.fillStyle = gradient;//渐变色填充
context.fillStyle = pattern;//纹理填充、图片填充

纯色
<canvas id="canvasColor"></canvas>
var canvasColor = document.getElementById('canvasColor');
var contextColor = canvasColor.getContext('2d');
contextColor.fillStyle = 'RGB(255, 0, 0)';
contextColor.fillRect(10, 10, 100, 100);

所以我们借助createLinearGradient() API实现一个从红色到绿色的线性渐变填充效果,以及createRadialGradient()实现径向渐变的效果:

线性渐变

<canvas id="canvasLinear"></canvas>
var canvasLinear = document.getElementById('canvasLinear');
var contextLinear = canvasLinear.getContext('2d');
// 创建线性渐变对象
var gradientLinear = contextLinear.createLinearGradient(0, 0, 0, 100);
gradientLinear.addColorStop(0, 'red');// 从位置0开始时红色
gradientLinear.addColorStop(1, 'green');//到结束位置1的绿色
// 填充线性渐变
contextLinear.fillStyle = gradientLinear;
contextLinear.fillRect(10, 10, 100, 100);



径向渐变
<canvas id="canvasRadial"></canvas>
var canvasRadial = document.getElementById('canvasRadial');
var contextRadial = canvasRadial.getContext('2d');
// 创建径向渐变对象,半径50
var gradientRadial = contextRadial.createRadialGradient(60, 60, 0, 60, 60, 50);
gradientRadial.addColorStop(0, 'red');
gradientRadial.addColorStop(1, 'green');
// 填充径向渐变
contextLinear.fillStyle = gradientRadial;
contextLinear.fillRect(10, 10, 100, 100);

图片填充

<canvas id="canvasPattern"></canvas>
var canvasPattern = document.getElementById('canvasPattern');
var contextPattern = canvasPattern.getContext('2d');
// 创建图案对象
var imagePattern = document.createElement('img');
imagePattern.onload = function () {
    // 缩放原始图片到50*50大小
    var canvas = document.createElement('canvas');
    canvas.width = canvas.height = 50;
    var context = canvas.getContext('2d');
    // 通过drawImage()方法缩放
    context.drawImage(this, 0, 0, 50, 50);
    // 把这个创建的canvas图形作为图案使用
    var pattern = contextPattern.createPattern(canvas, 'repeat');
    // 填充图案
    contextPattern.fillStyle = pattern;
    contextPattern.fillRect(10, 10, 100, 100);
};
imagePattern.src = './pattern.jpg';

(3) strokeRect():矩形描边效果
context.strokeRect(x, y, width, height);
  • x:描边矩形起点横坐标
  • y:描边矩形起点纵坐标
  • width:描边矩形宽度
  • height:描边矩形高度
// 2像素宽矩形描边
context.lineWidth = 2;//  定义描边的线宽度为2
context.strokeRect(75, 25, 150, 100);// 定义一个矩形描边,起点为75,25.长度为150,宽度为100

绘制文本

(1)fillText() 文字填充(可以填充纯色,渐变,图片)

是绘制文本的主力方法。

context.fillText(text, x, y [, maxWidth]);
  • text:文本信息
  • x:填充文本的起点坐标
  • y:填充文本的终点坐标
  • maxWidth(可选):填充文本的最大宽度,文本 超出时,通过压缩文本宽度进行适配。
context.font = '24px STheiti, SimHei';
context.fillText('Canvas API中文网', 24, 66,200);

可以看到文本水平方向压缩了。

  • wrapText():文本自动换行
context.font = '24px STheiti, SimHei';
context.wrapText('Canvas API中文网,不只是文档', 24, 56, 200);

(2) strokeText():给文字描边
context.strokeText(text, x, y [, maxWidth]);
  • text:文本内容
  • x:起点横坐标
  • y:起点纵坐标
  • maxWidth【可选】:填充文本的最大宽度,超出换行
    // 文字描边
    context.font = '50px STHeiti, SimHei';
    context.strokeText('文字描边', 50, 90);

  • 实色文字描边

    // 文字样式
    context.font = '50px STHeiti, SimHei';
    // 文字先描边
    context.lineWidth = 3;
    context.strokeStyle = 'red';
    context.strokeText('文字描边', 50, 90);
    // 再填充
    context.fillText('文字描边', 50, 90);

(3) measureText(text):测量文本字符宽度等信息。

measureText()方法很实用,是文本自动换行效果实现的核心所在,此方法返回的字符宽度值非常精准。

context.measureText(text)
  • text:被测量的文本。
    // 设置字体字号
    context.font = '24px STHeiTi, SimHei';
    // 文本信息对象就有了
    var textZh = context.measureText('帅');
    var textEn = context.measureText('handsome');
    // 文字绘制
    context.fillText('帅', 60, 50);
    context.fillText('handsome', 60, 90);
    // 显示宽度
    context.font = '12px Arial';
    context.fillStyle = 'red';
    context.fillText('宽' + textZh.width, 62 + textZh.width, 40);
    context.fillText('宽' + textEn.width, 62 + textEn.width, 80);

线条样式

(1)lineWidth:线条宽度,默认1,支持小数
context.lineWidth = value;

value 表示线的宽度。数值类型,默认值是1.0。如果是负数,0,NaN,或者Infinity都会忽略。

<canvas width="240" height="120"></canvas>
// 随机三角形
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
// 随机三个坐标点
var positionA = [width * Math.random(), height / 2 * Math.random()];
var positionB = [width / 2 * Math.random(), height / 2 + height / 2 * Math.random()];
var positionC = [width / 2 + width / 2 * Math.random(), height / 2 + height / 2 * Math.random()];
// 开始绘制
context.beginPath();
context.moveTo(positionA[0], positionA[1]);
context.lineTo(positionB[0], positionB[1]);
context.lineTo(positionC[0], positionC[1]);
context.closePath();
// 绘制,由于默认宽度就是1像素,因此
// context.lineWidth设置缺省
context.stroke();

(2)lineCap:线条端点的样式。(线帽)
  • butt(默认值,断头,无端帽)
  • round(圆形端帽)
  • square(方形端帽)
    context.lineCap = 'butt';
    context.lineCap = 'round';
    context.lineCap = 'square';

绘制一个loading菊花效果,点击菊花可以旋转,如下:

loading图标实际上就是由一些线条组成,同时设置线断点为圆弧,这样看上去更舒适些。

相关代码如下:

<canvas width="40" height="40"></canvas>
// 圆心坐标
var center = [20, 20];
// 线长度和距离圆心距离
var length = 8, offset = 8;
// 开始绘制
context.lineWidth = 4;
context.lineCap = 'round';
for (var angle = 0; angle < 360; angle += 45) {
  // 正余弦
  var sin = Math.sin(angle / 180 * Math.PI);
  var cos = Math.cos(angle / 180 * Math.PI);
  // 开始绘制
  context.beginPath();
  context.moveTo(center[0] + offset * cos, center[1] + offset * sin);
  context.lineTo(center[0] + (offset + length) * cos, center[1] + (offset + length) * sin);
  context.strokeStyle = 'rgba(0,0,0,'+ (0.25 + 0.75 * angle / 360) +')';
  context.stroke();
}

(2)lineJoin:线条转角的样式。
  • miter(默认值,尖角)
  • round(圆角)
  • bevel(平角,平头)
context.lineJoin = 'miter';
context.lineJoin = 'round';
context.lineJoin = 'bevel';

案例:绘制一个圆润的三角箭头(单箭头,双箭头,双向单箭头)

<canvas id="arrow" width="210" height="100"></canvas>
var canvas = document.getElementById('arrow');    
var context = canvas.getContext('2d');
// 起止点坐标,这里是示意
var start = { x: 20, y: 20 };
var end = { x: 190, y: 80 };
// 计算两点距离,主要是为了计算斜率
var distanceX = end.x - start.x, distanceY = end.y - start.y;
var distance = Math.sqrt(distanceY * distanceY + distanceX * distanceX);
// 箭头的尺寸
var distanceArrow = 7, sharpeArrow = 3;
// 先确定轴线与三角两个尖角点交汇坐标
var arrowMoveTo = {
    x: start.x + distanceArrow * distanceX / distance,
    y: start.y + distanceArrow * distanceY / distance
};
var arrowLineTo = {
    x: end.x - distanceArrow * distanceX / distance,
    y: end.y - distanceArrow * distanceY / distance
};
// 4个对称点坐标
var arrowTo1 = {
    x: arrowMoveTo.x - sharpeArrow * distanceY / distance,
    y: arrowMoveTo.y + sharpeArrow * distanceX / distance
};
var arrowTo2 = {
    x: arrowMoveTo.x + sharpeArrow * distanceY / distance,
    y: arrowMoveTo.y - sharpeArrow * distanceX / distance
};
var arrowTo3 = {
    x: arrowLineTo.x - sharpeArrow * distanceY / distance,
    y: arrowLineTo.y + sharpeArrow * distanceX / distance
};
var arrowTo4 = {
    x: arrowLineTo.x + sharpeArrow * distanceY / distance,
    y: arrowLineTo.y - sharpeArrow * distanceX / distance
};
// 设置线的粗细和断点,转角样式
context.lineWidth = 2;
context.lineCap = 'round';
context.lineJoin = 'round';
// 绘制方法
var draw = function (arrow) {
    arrow = arrow || 'single';
    // 清除画布
    context.clearRect(0, 0, canvas.width, canvas.height);
    // 开始绘制
    context.beginPath();
    // 三种箭头类型
    switch (arrow) {
        case 'single': {
            context.moveTo(start.x, start.y);
            context.lineTo(end.x, end.y);
            // 两个结束对称点
            context.lineTo(arrowTo3.x, arrowTo3.y);
            context.lineTo(arrowTo4.x, arrowTo4.y);
            // 回到结束点
            context.lineTo(end.x, end.y);
            break;
        }
        case 'both': {
            context.moveTo(start.x, start.y);
            // 两个起始对称点
            context.lineTo(arrowTo1.x, arrowTo1.y);
            context.lineTo(arrowTo2.x, arrowTo2.y);
            // 回到起始点
            context.lineTo(start.x, start.y);
            // 重复single的绘制
            context.lineTo(end.x, end.y);
            context.lineTo(arrowTo3.x, arrowTo3.y);
            context.lineTo(arrowTo4.x, arrowTo4.y);
            context.lineTo(end.x, end.y);
            break;
        }
        case 'part-both': {
            // 先绘制起止线
            context.moveTo(start.x, start.y);
            context.lineTo(end.x, end.y);
            // 结束点位置的半个箭头
            context.lineTo(arrowTo4.x, arrowTo4.y);
            context.lineTo(arrowLineTo.x, arrowLineTo.y);
            context.closePath();
            // 另一端的半箭头
            context.moveTo(start.x, start.y);
            context.lineTo(arrowTo1.x, arrowTo1.y);
            context.lineTo(arrowMoveTo.x, arrowMoveTo.y);
            break;
        }
    }
    // 闭合,描边与填充
    context.closePath();
    context.stroke();
    context.fill();
};
// 绘制单箭头
draw();
// 绘制双箭头
// draw('both');
// 绘制双向单侧箭头
// draw('part-both');

(3)miterLimit:尖角限制比率,默认10,如果是负数,0,NaN,或者Infinity都会忽略。
context.miterLimit = value;
0 和 10
(4)getLineDash() 返回当前虚线数值
(5)setLineDash() 设置线条为虚线
ctx.setLineDash(segments);

案例

绘制一条虚线,只是虚线多尺寸并存:

context.beginPath();
context.setLineDash([5, 10, 15, 30]);
context.moveTo(20, 70);
context.lineTo(280, 70);
context.stroke();

(6)lineDashOffset() 设置虚线的起始偏移
context.lineDashOffset = value;

value 表示虚线起始绘制的偏移距离,为浮点型,默认值是0.0。

<canvas id="canvas" width="240" height="120"></canvas>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// 偏移大小
var offset = 0;
// 绘制
var draw = function () {
  context.clearRect(0,0, canvas.width, canvas.height);
  context.setLineDash([8, 4]);
  context.lineDashOffset = offset;
  context.strokeRect(2, 2, 236, 116);
}

var run = function () {
  offset += 0.5;
  if (offset > 24) {
    offset = 0;
  }
  draw();
  // 继续绘制
  requestAnimationFrame(run);
}

run();

文本样式

下面就是与Canvas中与文本样式相关的若干属性和方法。

CanvasRenderingContext2D.font

设置字体相关样式,包括字号,字体信息。默认值是10px sans-serif

CanvasRenderingContext2D.textAlign

设置文本水平对齐方式。支持属性值有:start(默认值),endleftright以及center

CanvasRenderingContext2D.textBaseline

设置文本基线对齐方式。支持属性值有:tophangingmiddlealphabetic(默认值),ideographicbottom

CanvasRenderingContext2D.direction

设置文本显示方向。支持属性值有:inherit(默认值),ltrrtl

填充和描边

就是下面这两个属性和两个方法,是高频使用的API。

(1) CanvasRenderingContext2D.fillStyle

填充样式。默认值是#000000纯黑色。

(2) CanvasRenderingContext2D.fill()

填充

(3) CanvasRenderingContext2D.strokeStyle

描边样式。默认值是#000000纯黑色。

(4) CanvasRenderingContext2D.stroke()

描边。

渐变相关

Canvas中与渐变相关的方法就是创建线性渐变和径向渐变这两个方法。

(1) createLinearGradient() 创建线性渐变

创建线性渐变渐变是全局的,而不是相对于填充元素,是相对于Canvas

context.createLinearGradient(x0, y0, x1, y1);
x0 : Number 渐变起始点横坐标。
y0 : Number  渐变起始点纵坐标。
x1 : Number 渐变结束点横坐标。
y1 : Number 渐变结束点纵坐标。

线性渐变效果比较好脑补,就是从坐标点[x0, y0]到坐标点[x1, y1]的位置画一条线,然后整个渐变色带与与这条线垂直

案例

演示下最简单的头尾二色渐变,以及渐变坐标的全局特性,如下JavaScript代码:

var context = canvas.getContext('2d');
// 创建渐变
var gradient = context.createLinearGradient(0, 0, 300, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'green');
// 设置填充样式为渐变
context.fillStyle = gradient;
// 左上角和右下角分别填充2个矩形
context.fillRect(10, 10, 160, 60);
context.fillRect(120, 80, 160, 60);

很直观展示了渐变坐标是全局的,而不是相对于填充元素(例如这里的矩形)。

(2) createRadialGradient() 创建径向渐变

用来创建径向渐变。和CSS3的径向渐变有所不同的是,在Canvas中,径向渐变的起始点由两个圆环坐标构成,而非点坐标。

context.createRadialGradient(x0, y0, r0, x1, y1, r1);
返回值是CanvasPattern对象。

参数 各个参数含义和作用如下:

x0 :Number  起始圆的横坐标。
y0:Number  起始圆的纵坐标。
r0:Number  起始圆的半径。
x1:Number  结束圆的横坐标。
y1:Number  结束圆的纵坐标。
r1:Number  结束圆的半径。
标准两色镜像渐变

从Canvas径向渐变语法上讲,看似标准两色径向渐变的实现并不是理所当然,反而是需要一些技巧,技巧就是我们的起始圆半径设置为0,化作一个点。例如实现一个红绿渐变,代码示意如下:

<canvas width="240" height="120"></canvas>
var context = canvas.getContext('2d');
// 创建一个起始圆半径为0的径向渐变对象
var gradient = context.createRadialGradient(120, 60, 0, 120, 60, 60);
// 设置起止颜色
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'green');
// 矩形填充
context.fillStyle = gradient;
context.fillRect(0, 0, 240, 120);

 色带分隔明显的色环

实现一个五彩色环效果,代码如下:

<canvas width="150" height="150"></canvas>
var context = canvas.getContext('2d');
// 创建一个起始圆半径为0的径向渐变对象
var gradient = context.createRadialGradient(75, 75, 0, 75, 75, 75);
// 设置起止颜色
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.2, 'red');
gradient.addColorStop(0.2, 'orange');
gradient.addColorStop(0.4, 'orange');
gradient.addColorStop(0.4, 'yellow');
gradient.addColorStop(0.6, 'yellow');
gradient.addColorStop(0.6, 'green');
gradient.addColorStop(0.8, 'green');
gradient.addColorStop(0.8, 'purple');
gradient.addColorStop(1, 'purple');
gradient.addColorStop(1, 'transparent');
// 矩形填充
context.fillStyle = gradient;
context.fillRect(0, 0, 150, 150);

对比截图,可以看到在渲染细节上,不同浏览器还是有差异的,Chrome浏览器下的锯齿比较明显。

实际上,如果起始渐变圆的半径如果不是0,则在Firefox浏览器下,第5环会无法呈现,且也会出现明显锯齿,这个案例可参见CanvasGradient.addColorStop

图案相关

Canvas中与图案相关的方法就是创建图案对象方法。

createPattern():创建纹理,可以是图片 

创建图案。图案内容可以是图片,可以是<canvas>元素,也可以是渐变。此方法返回CanvasPattern对象。

context.createPattern(image, repetition);

image : Object

用来平铺的CanvasImageSource图像。可以是下面的类型:

repetition : String

图案的平铺方式,可以是下面的值:

  • 'repeat',水平和垂直平铺。当repetition属性值为空字符串''或者null,也会按照'repeat'进行渲染。
  • 'repeat-x',仅水平平铺。
  • 'repeat-y',仅垂直平铺。
  • 'no-repeat',不平铺。
<canvas id="canvas" width="250" height="167"></canvas>
// 先绘制图片
var img = new Image();
img.onload = function () {
    // 我们创建一个Canvas元素
    var canvasCreated = document.createElement('canvas');
    canvasCreated.width = 50;
    canvasCreated.height = 34;
    canvasCreated.getContext('2d').drawImage(this, 0, 0, 50, 34);
    // 页面上需要呈现最终纹理的Canvas上下文
    var context = canvas.getContext('2d');
    // 创建纹理并填充,顺便测试null是否渲染为'repeat'
    var pattern = context.createPattern(canvasCreated, null);
    context.fillStyle = pattern;
    context.fillRect(0, 0, 250, 167);
};
img.src = './1.jpg';

练习,完整展开图片

<canvas id="myCanvas">你的浏览器不支持 HTML5 canvas 标签。</canvas>
	<canvas id="cav2" width="400" height="400"></canvas>
<script>
	let img = new Image();
	img.onload = function(){
		let c=document.getElementById('cav2');
		let ct = c.getContext("2d");
		//创建canvas画布
		var canvasCreated = document.createElement('canvas');
		canvasCreated.width=250
		canvasCreated.height=400
		canvasCreated.getContext("2d").drawImage(this,0,0,250,250);
		// 给创建纹理,填null会渲染为repeat
		let pattern = ct.createPattern(canvasCreated,"no-repeat");
		//填充纹理
		ct.fillStyle = pattern
		ct.fillRect(0, 0, 250, 250);
		
	}
	img.src = "https://img1.baidu.com/it/u=2956201572,2258203469&fm=253&fmt=auto&app=138&f=PNG?w=500&h=500";
	
	

阴影相关

Canvas中与阴影相关的属性就是下面这些。

(1) shadowBlur  模糊阴影

阴影模糊大小。默认值是0

context.shadowBlur = value;

value : 表示阴影的模糊程度。数值类型,可以是小数。默认值是8。会忽略负数,NaN,或者 Infinity 。

1. 矩形块的阴影模糊

绘制一个矩形,然后给个投影,加点模糊,代码如下:

 设置阴影红色,同时模糊大小为10
context.shadowColor = 'red';
context.shadowBlur = 10;
 填充个淡淡的颜色,以示尊敬
context.fillStyle = '#f0f3f9';
context.fillRect(40, 40, 160, 40);

2. 文字的阴影模糊

写两个大大的文字,然后给个投影,加点模糊,代码如下:

 设置阴影红色,同时模糊大小为10
context.shadowColor = 'red';
context.shadowBlur = 10;
文字80像素,黑体
context.font = '80px STheiti, simHei';
context.fillText('模糊', 40, 90);

(2)shadowColor  阴影颜色,默认透明黑

阴影颜色。默认值是全透明黑色。

context.shadowColor = color;
document.createElement('canvas').getContext('2d').shadowColor;
 当前浏览器输出结果是:rgba(0, 0, 0, 0)
文字阴影效果,深黑色,无模糊,代码如下:

// 设置阴影深黑色,同时右下角偏移3像素
context.shadowColor = 'rgb(50, 50, 50)';
context.shadowOffsetY = 3;
context.shadowOffsetX = 3;
// 文字80像素,黑体,红色
context.fillStyle = 'red';
context.font = '80px STheiti, simHei';
context.fillText('颜色', 40, 88);

(3)shadowOffsetX 阴影x偏移距离

阴影水平偏移大小。默认值是0

借助足够大的水平偏移,克隆一个相同的文字。代码如下:

设置阴影深黑色,同时右偏移1个字号大小
context.shadowColor = 'rgb(50, 50, 50)';
context.shadowOffsetX = 80;
文字80像素,黑体,红色
context.fillStyle = 'red';
context.font = '80px STheiti, simHei';
context.fillText('变', 40, 88);

(4)shadowOffsetY  阴影垂直偏移大小。默认值是0

借助足够大的水平偏移,克隆一个相同的文字。代码如下:

上阴影
context.shadowColor = 'rgb(50, 50, 50)';
context.shadowBlur = 5;
context.shadowOffsetY = -5;
 文字80像素,黑体,红色
context.fillStyle = 'red';
context.font = '70px STheiti, simHei';
context.fillText('上偏移', 10, 88);

绘制路径

下面的方法可以用来处理路径对象。

(1) beginPath()  :开始一个新路径。没有参数没有返回值。
开始路径
context.beginPath();
context.strokeStyle = 'blue';
context.moveTo(60, 20);
context.lineTo(220, 20);
context.stroke();
开始路径 again
context.beginPath();
context.strokeStyle = 'green';
context.moveTo(60, 20);
context.lineTo(160, 120);
context.stroke();

这个执行了2次beginPath(),实时效果如下:

开始路径
context.beginPath();
context.strokeStyle = 'blue';
context.moveTo(60, 20);
context.lineTo(220, 20);
context.stroke();

context.strokeStyle = 'green';
context.moveTo(60, 20);
context.lineTo(160, 120);
context.stroke();

因此,只要是非连续路径绘制,都要记得都要执行一句context.beginPath()

(2) closePath() : 闭合开始点和结束点,行程闭合区域

闭合一个路径。没有参数,没有返回

context.closePath();

 绘制三角
context.beginPath();
context.moveTo(10, 10);
context.lineTo(140, 70);
context.lineTo(70, 140);
// 不执行闭合,直接描边
context.stroke();

绘制另外一个三角
context.beginPath();
context.moveTo(160, 10);
context.lineTo(290, 70);
context.lineTo(220, 140);
 执行闭合,然后描边
context.closePath();
context.stroke();
(3)moveTo() : 路径绘制的起点

路径绘制起始点。

context.moveTo(x, y);
x :起点的横坐标
y : 起点的纵坐标
context.beginPath();
context.moveTo(50, 20);
context.lineTo(200, 100);
context.stroke();

(4)lineTo() : 绘制路径点当前最后的子路点到lineTo()指定的终点。

绘制直线到指定坐标点。

context.lineTo(x, y);

示意一个贝塞尔曲线然后执行lineTo()方法的效果,代码如下:

context.beginPath();
context.moveTo(50, 20);
context.bezierCurveTo(100, 100, 200, 40, 250, 120);
context.lineTo(50, 120);
context.stroke();

上图中下面那个“平底鞋”的平底就是执行lineTo()绘制出来的。

(5)bezierCurveTo()  贝塞尔曲线

绘制贝赛尔曲线到指定坐标点。

(6).quadraticCurveTo()  二次贝塞尔曲线

绘制二次贝赛尔曲线到指定坐标点。

(7)arc()  绘制圆弧和圆

绘制圆弧(包括圆)。

context.arc(x, y, radius, startAngle, endAngle [, anticlockwise]);

各个参数含义和作用如下:

  • x  【Number】:圆弧对应的圆心横坐标。
  • y  Number【】:圆弧对应的圆心纵坐标。
  • radius 【Number】:圆弧的半径大小。
  • startAngle【Number】 : 圆弧开始的角度,单位是弧度。
  • endAngle 【Number】: 圆弧结束的角度,单位是弧度。
  • anticlockwise (可选)Boolean】: 弧度的开始到结束的绘制是按照顺时针来算,还是按时逆时针来算。如何设置为true,则表示按照逆时针方向从startAngle绘制到endAngle
1. 绘制1/4弧度范围的圆弧

顺时针绘制一个角度从0到1/4弧度的圆弧,JavaScript代码如下:

 顺时针绘制0到1/4弧度圆弧
context.beginPath();
context.arc(150, 75, 50, 0, Math.PI / 2);
context.stroke();

逆时针绘制一个角度从0到1/4弧度的圆弧,JavaScript代码如下:

逆时针绘制0到1/4弧度圆弧
context.beginPath();
context.arc(150, 75, 50, 0, Math.PI / 2, true);
context.stroke();

2. 绘制一个圆

直接设置起止弧度范围是2个π即可,如下代码:

绘制完整圆
context.beginPath();
context.arc(150, 75, 50, 0, Math.PI * 2);
context.stroke();

CanvasRenderingContext2D.arcTo()

绘制圆弧,和之前的点以直线相连。

CanvasRenderingContext2D.rect()

绘制矩形路径。

CanvasRenderingContext2D.ellipse()

绘制椭圆路径。

CanvasRenderingContext2D.clip()

创建剪裁路径,之后绘制的路径只有在里面的才会显示。

位置检测

下面这两个方法可以判定某个坐标点是否路径或者描边中。

CanvasRenderingContext2D.isPointInPath()

当前点是否在指定路径内。

CanvasRenderingContext2D.isPointInStroke()

当前点是否在指定路径描边上。

变换

旋转缩放等变换方法。

CanvasRenderingContext2D.rotate()

旋转。

CanvasRenderingContext2D.scale()

缩放。

CanvasRenderingContext2D.translate()

位移。

CanvasRenderingContext2D.transform()

当前矩阵变换基础上再次矩阵变换。

CanvasRenderingContext2D.setTransform()

直接重置为当前设置的矩阵变换。

透明度和层级

一个是控制全局透明度,另外一个可以改变层级关系,设置混合模式,以及实现遮罩效果等。

(1)globalAlpha 全局透明度

全局透明度。

(2) globalCompositeOperation  图形混合模式,遮罩,剪裁,图形上下层叠关系。

设置图形叠加时候的混合方式,可以用来改变绘制元素上下叠加关系,也就是层级。

context.globalCompositeOperation = type;
其中type就是混合类型
  • source-over:(默认)纯视觉覆盖,图形被第二层图形遮盖。
  • source-in:绘制重叠部分

    仅在和原Canvas图形重叠的位置绘制新图形,否则处理为透明。如果重叠位置是半透明颜色,则也处理为半透明。此效果类似遮罩,新内容为显示层,原内容是遮罩层,遮罩层无论张什么样子,都不显示。

  • source-out :绘制不重叠部分, 重叠部分透明

    source-in相反,重叠的位置是透明的,不重叠的或者半透明的重叠区域反而显示新图形。同样,原内容无论性质如何,最终效果都不会出现。

  • source-atop: 新内容在旧内容上形成遮罩,没有重叠就不显示

  • destination-over:旧内容在新内容上方

destination-*系列和source-*系列的区别就是动作的主体是新内容还是原内容。source-*系列是新内容,而destination-*系列动作主体是元内容。例如这里的destination-over表示原内容在上方,也就是新内容在原内容的下方绘制。

  • destination-in:显示新内容和旧内容重叠部分。
  • destination-out:隐藏新旧内容重叠部分
  • lighter:自然光混合效果

无论是哪种语言,哪种工具的混合模式,其实概念都类似的。如果这里的lighter等同于Adobe Photoshop中lighter color的话,则这个属性值可以理解为自然光混合效果。红绿蓝混合会成为白色。其色值混合原理如下,比较新内容和原内容颜色的所有通道值的总和,并显示更高值的颜色。例如,红色RGB(255,0,0)和蓝色RGB(0,0,255)进行混合,则最终颜色值是RGB(255,0,255),也就是紫色。实际取色发现还是和PS还是有些出入的,并不是纯紫色。因此,我这里的理解并不一定完全准确,仅供参考。

  • screen:滤色。像素反转,相乘,然后再反转。最终得到更淡的图形(和multiply相反)。

  • lighten:变亮。保留原内容和新内容中最亮的像素。

  • hard-light:强光。类似overlay,是multiplyscreen组合效果。只不过底层和顶层位置交换下。
案例1:文字镂空效果

一个图片,然后文字部分是透明的。借助destination-out实现镂空效果,JS代码如下:

绘制图片
context.drawImage(img, 0, 0, 300, 200);
改变混合方式
context.globalCompositeOperation = 'destination-out';
绘制文本
context.font = 'bold 120px SimHei, STHeiti';
context.fillText('镂空', 25, 140);

 本效果使用xor也是可以的。

案例2:给图片增加装饰效果

假设底图和装饰图分别如下:

我们使用screen模式进行混合,结果实时效果如下:

相关JS代码如下,假设图片都已经完全加载,且imgBase表示底图DOM元素对象,imgScreen表示装饰图DOM元素对象:

绘制底图
context.drawImage(imgBase, 0, 0, 300, 200);
设置混合模式为滤色
context.globalCompositeOperation = 'screen';
绘制装饰图
context.drawImage(imgScreen, 0, 0, 300, 200);

图片与像素

绘制图片和图像像素信息处理方法。

(1)drawImage() : 图片绘制在画布上。

CanvasRenderingContext2D.drawImage()是Canvas与web结合使用最频繁的方法,没有之-因为Canvas的很多API效果其它web技术也能实现,但是,对于很多图像相关的处理,如图像压缩,水印合成,或者图像的像素操作等(见最后的相关资源),那就没得选择,非使用drawImage()不可。

保持原始图片尺寸和比例
context.drawImage(image, dx, dy); 
拉伸图片到指定大小和位置
context.drawImage(image, dx, dy, dWidth, dHeight);
拉伸图片同时保持图片比例
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
  • image:图片。<img>图片,SVG图像,Canvas元素本身等。
  • dx 和 dy:左上角的横坐标和纵坐标。
  • dWidth 和 dHeight:要放置图片的宽度和高度
  • sx 和 sy:图片元素绘制在canvas画布上的起始横坐标和起始纵坐标。 
  • sWidth 和 sHeight:表示从坐标点开始算,多大的宽度和高度内容绘制Canvas画布上
1. 保持原始图片尺寸和比例

使用context.drawImage(image, dx, dy)绘制图片,可以保持图片的原始尺寸和比例。

context.drawImage(image, 0, 0);

从实际开发经验来看,当图片的原始尺寸和我们的Canvas画布尺寸一模一样的时候,可以直接只使用imagedxdy这3个参数。

2. 拉伸图片到指定大小和位置

我们还可以指定Canvas中呈现图片的区域,这个时候,如果我们没有指定图片呈现的位置和尺寸的话,图片会被自动拉伸到这个指定区域内,很可能最终呈现的长宽比和原始图片就不一样。

context.drawImage(image, 0, 0, 300, 150);

可以看到图片虽然看到了全貌,但是被压扁了,不是原始的图片比例,这并不是我们需要的效果,需要进一步调整。

3. 拉伸图片同时保持图片比例

如何填满Canvas画布,同时保持图片的原始比例呢?这个就需要用到sxsysWidthsHeight这几个参数,注意,这4个参数是要写在dxdydWidthdHeight前面的,和一般的API不一样。

context.drawImage(image, 0, 42, 500, 250, 0, 0, 300, 150);

(原图尺寸500 * 333像素)

此案例关键是理解0, 42, 500, 250这4个坐标是怎么来的。我们最终图片要显示在300*150大小的Canvas画布中,比例是2:1,因此,我们最终设置的用来绘制的图片尺寸比例也是2:1。原始图片宽度是500,要保持2:1,则高度我们就设置为250;而原始高度是333,我们希望显示图片中心区域,因此起始垂直坐标计算下,(333 - 250) / 2,四舍五入就是42,因此,就有了对原始图片的剪裁坐标值和尺寸值0, 42, 500, 250

(2)createImageData() 创建一个新的空白的ImageData对象。

(3)getImageData() 获取Canvas画布的设定区域的ImageData对象。
(4)putImageData()  给定的ImageData对象应用在Canvas画布上。

将图片2绘制在图片1的指定位置

context.putImageData(imagedata, dx, dy);
context.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
  • imagedata:包含图像像素信息的ImageData对象。(膏药)
  • dx 和 dy:目标Canvas中被图像数据替换的起点横坐标和纵坐标。(图片膏药要贴在什么位置)
  • dirtyX 和 dirtyY(可选):图像数据渲染区域左上角横坐标和纵坐标。默认值是0。(图片膏药从膏药哪个地方开始贴)

  • dirtyWidth 和 dirtyHeight(可选):图像数据渲染区域的宽度和高度。默认值是imagedata图像的宽度。(图片膏药要贴多大的宽度和高度)

案例

下面这个图像是imagedata数据源:

接下来,使用getImageData()方法获取imagedata数据源,然后仅中心100*100区域替换原始Canvas。

最终效果如下:

<img id="image1" src="./1.jpg" alt="目标图片">
<img id="image2" src="./1.jpg" alt="数据源图片">

<canvas id="canvas" width="300" height="200"></canvas>
 尺寸
var width = 300, height = 200;
 目标Canvas上下文
var context = canvas.getContext('2d');
 目标Canvas绘制
context.drawImage(image1, 0, 0, width, height);
 获取覆盖图数据
var dirtyCanvas = document.createElement('canvas');
var dirtyContext = dirtyCanvas.getContext('2d');
 设置屏幕外Canvas尺寸
dirtyCanvas.width = width;
dirtyCanvas.height = height;
 绘制替换图
dirtyContext.drawImage(image2, 0, 0, width, height);
 此时可以得到imagedata数据
var imagedata = dirtyContext.getImageData(0, 0, width, height);
 然后中间100*100区域替换目标Canvas
context.putImageData(imagedata, 0, 0, 100, 50, 100, 100);

Canvas状态

Canvas状态管理几个方法。

CanvasRenderingContext2D.save()

存储当前Canvas的状态。

CanvasRenderingContext2D.restore()

恢复Canvas到前一次存储的状态。

CanvasRenderingContext2D.canvas

反向识别当前上下文源自哪个HTMLCanvasElement

其他方法

其他一些不常用的API方法。

CanvasRenderingContext2D.drawFocusIfNeeded()

如果给定元素被聚焦,则该方法在当前路径周围绘制焦点环。

CanvasRenderingContext2D.scrollPathIntoView()

将当前路径或给定路径滚动到视图中。

原文件地址 :https://www.canvasapi.cn/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值