Canvas画板

## 相关模板

目前站内使用这些的模板有：

1. Template:人设卡

## 上手指南

由于MediaWiki:Oc.js需要足够高的编辑权限，故我写下本篇教程：

我们用源代码编辑器打开Template:人设卡模板，看一下里面的结构：

```
<center><div class="occan" style="color:transparent;background-size:contain;width:960px;height:540px;">
{"width":"960","height":"540","data":[
{"save":[]},
{"SET":["fillStyle","gray"]},
{"fillRect":[0,0,960,540]},
{"SET":["globalAlpha",0.4]},
{"drawImage":["{{{1}}}",{{{x|0}}},{{{y|0}}},960,540,0,0,960,540]},
{"restore":[]},
{"save":[]},
{"drawImage":["{{{1}}}",{{#expr:{{{x|0}}}+30}},{{#expr:{{{y|0}}}+30}},300,480,30,30,300,480]},
{"SET":["textAlign","center"]},
{"SET":["fillStyle","white"]},
{"SET":["font","{{{nameSize|36}}}px rubik"]},
{"fillText":["{{{name|}}}",180,440]},
{"SET":["font","{{{eorzeaSize|20}}}px Eorzea"]},
{"fillText":["{{{eorzea|}}}",180,475]},
{"restore":[]},
{"save":[]},
{"SET":["textAlign","center"]},
{"SET":["shadowColor","rgba(0,0,0,0.5)"]},
{"SET":["shadowBlur","5"]},
{"SET":["shadowOffsetX","2"]},
{"SET":["shadowOffsetY","2"]},
{"SET":["font","{{{textSize|24}}}px Arial"]},
{"SET":["fillStyle","white"]},
{{#invoke:Oc|fillText|{{{text|}}}}}
{"restore":[]},
{"save":[]},
{"rotate":[-1.5708]},
{"SET":["globalAlpha",0.2]},
{"SET":["font","36px rubik"]},
{"scale":[1,3]},
{"SET":["strokeStyle","white"]},
{"strokeText":["FINAL FANTASY XIV",-530,325]},
{"restore":[]},
{"save":[]},
{{#invoke:Oc|drawIcon|{{{icon|}}}}}
{"restore":[]},
{"save":[]},
{"SET":["textAlign","right"]},
{"SET":["globalCompositeOperation","destination-out"]},
{"fillText":["https://ff14rp.fandom.com/zh/wiki/{{{link|}}}",955,535]}
]}</div></center></includeonly>

```
*noinclude*的部分就不说了。

* 首先是两张图片的引用，这两张图片我们可以在Special:文件列表中找到
* **<i class="eorzea"></i>**是字体的引用，可用字体参见游戏内字体(*游戏符号*可能无法使用)

这一部分我使用**<div style="display:none;"></div>**将其隐藏。

* 再之后是**<center><div class="occan" style="color:transparent;background-size:contain;width:960px;height:540px;">**与**</div></center>**包裹的一大段json，这个json是我们稍后重点讲述的内容。

包含了类名*class="occan"*的div相当于一个标志，Oc.js仅处理使用该类名包裹的json。

center标签用于居中，我同时还设置了这个div的宽与高，以免该div被其他页面元素“压扁”

## json介绍

我赌你懂json

这里使用的json包含三个值：width、height、data

* width：canvas（画布）的宽，单位px（像素），默认1280
* height：canvas的高，单位px，默认720
* data：一个数组，描述了具体的绘画过程

Oc.js主要做两件事：执行函数与设置属性，在此我向大家推荐学习HTML5 Canvas这一篇文章就够了。这篇文章上手简单，循序渐进，包含大量示例。

一起看一下原文2.5的示例。我们需要关注的或者说Oc.js根据json内容做的事情只有17、19、21、22这四行，我们将其单独列出来：

```
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect (30, 30, 55, 50);

```

第1、3行设置属性，第2、4行执行函数，在我们这里需要编写的json如下：

```
{"SET":["fillStyle","rgb(200,0,0)"]},
{"fillText":[10, 10, 55, 50]},
{"SET":["fillStyle","rgba(0, 0, 200, 0.5)"]},
{"fillText":[30, 30, 55, 50]},

```
正如我们前面所说，**Oc.js主要做两件事：执行函数与设置属性**。

* 如果我们想要设置属性，那么大括号内包含一个对象：key为"SET"，值为数组。数组的第一项为想要设置的属性名，第二项为属性值。
* 如果我们想要执行方法，那么大括号内包含两个对象：key为想要执行的方法名，值为传入该方法的参数列表。

通常来说这样就足够了，但**人设卡**作为一个模板，有些东西可以顺便提一下：

* 首先它是个模板：模板。
* 第11行的expr见解析器函數
* 第27、38行的invoke为Lua语言编写，见Lua\_templating
* 模板中Lua相关的源代码见[Module:Oc|]

## 源码浅析

我们来看MediaWiki:Oc.js(非管理员无法编辑)

源码主要可以分为三部分：

1. loopWait(循环等待，直到图片全部加载完毕)
2. draw(图片全部加载完毕后按照json进行绘制)
3. 其余部分(最先执行的部分，主要工作为json与图片的预处理)

代码并不是很长，我们来简述其执行过程：

1. 先看**其余部分**。这部分将json字符串解析为对象，筛出并加载所有将要使用的图片。在图片的onload函数中修改图片列表的已就绪图片张数。
2. **其余部分**处理完毕之后会调用**loopWait**。**loopWait**只做一件事：每250毫秒（四分之一秒）检查一下图片是否全部加载，全加载完毕则开始绘制。若十秒钟还没加载完则会只使用现有图片资源进行绘制。(若图片太大导致加载较慢可以效仿人设卡模板第一行，为其添加一个宽度参数)
3. 真正进行绘制的是**draw**函数，它创建一个不可视的**CanvasRenderingContext2D**对象（参见CanvasRenderingContext2D，这个文档还列出了各种可用的、不可用的——如实验性质的、缺少兼容的方法）。**draw**函数解析json后进行绘制，并最终将其作为填写该json的、class为**eorzea**的div的背景图片。你可以右键图片，使用新标签页打开该图片并保存。

## 控制台日志

只要在**Oc.js**中搜一下*console*我们就能看个通透。我的每行控制台日志都以**occan:**开头，均使用log而非debug/info/warn/error/table/time/timeEnd等一大堆我看不懂的方法——fandom的控制台已经够乱了。

列几条日志讲解一下：

* occan:0的蔓丸山茶花.png已处理,跳过 > 第一个画板的drawImage方法多次调用，且不止一次绘制了名为*蔓丸山茶花.png*的图片（正常非必须）
* occan:0:size,ready,count 2 0 0
* occan:0:size,ready,count 2 0 1
* occan:0:size,ready,count 2 0 2
* occan:0:size,ready,count 2 1 3
* occan:0:size,ready,count 2 2 4 > 这几行一起看，size表示（这个画板）一共有几张图片；ready表示已经加载完毕的图片数量；count表示这是第几次检测（每次250毫秒）。这个示例中第一张加载完成的图片耗时<750毫秒，全部加载完成耗时<1000毫秒。（正常）
* occan:0一张图片加载完毕
<img crossorigin="anonymous" src="https://static.wikia.noc…zh&vv=0.9463407406182595"> > 字面意思，你可以用来分析哪些图片加载成功了。（正常）
* occan:0开始绘制
Object { width: "960", height: "540", data: (44) […] } > 你可以在这里看看你的json传被转化成了什么样的对象，如果没打印，那么你的json很可能转化失败了，这大概率是json书写错误导致。（正常）
* occan:0,strokeText喵方法不存在 > strokeText喵这个方法不存在（或当前浏览器不支持），请查阅CanvasRenderingContext2D（异常）
* occan:0找不到图片蔓丸山茶花.png > 在页面添加**[[蔓丸山茶花.png]]**基本能解决该问题（前提是该图片已上传）。（异常）

## 将图像作为页面元素的背景

其他部分不变，只需要在json最外层（与**"data"**平级）添加一个**"imgNode"**属性，值为对该元素有效的选择器的值(‘.’表示class，‘#’表示id。js
代码中使用的是**querySelector**选择器)。

```
<div style="display:none;">[[File:蔓丸山茶花2.png]]]]</div>
<div class="occan" style="color:transparent;background-size:contain;">
{"width":"1920","height":"1080","imgNode":".page-蔓丸山茶花","data":[
{"drawImage":["蔓丸山茶花2.png",0,0,1920,1080]}
]}</div>

```
该示例截取了*蔓丸山茶花2.png*左上方1920\*1080个像素作为*.page-蔓丸山茶花*这个元素的背景。该元素为蔓丸山茶花页面的主体部分。当然，canvas画板的用处很多，你可以使用该方法装饰页面上大多数元素。

## 其他问题及解决方法

1. 图片未绘制/大小不正确 > 可能情况之一是这个图片在本页面有多份（如**[[蔓丸山茶花.png|20px]][[蔓丸山茶花.png]]**），Oc.js会以第一张图片作为蓝本绘制