WebGL Tutorial
and more

CanvasEditor Test

Web组件的次序

当使用highlight-with-trimpre.js时,该文件需放在CanvasEditor.js的下面。

<script type="module" src="/js/esm/web-widgets/canvas-editor/CanvasEditor.js"></script> <script type="module" src="/js/esm/web-widgets/trim-pre/TrimPre.js"></script> <script type="module" src="/js/esm/web-widgets/trim-pre/highlight-with-trimpre.js"></script>

否则,highlight-with-trimpre.js将试图加亮CM6的内容,犯了越俎代庖的错误。

与LiveEditor协作的问题

CanvasEditor特殊的地方在于,它需要灵活处置JavaScript标签与CSS标签。

设置自己的iframe模板

利用LiveEditor的钩子方法setupIframeTemplateCanvasditor进行特定的设置。

setupIframeTemplate(iframe) { iframe.src = '/js/esm/web-widgets/canvas-editor/iframe-template.html'; iframe.style.display = 'none'; iframe.style.height = '200px'; let iframeHeight = this.getAttribute('iframe-height'); if ( iframeHeight !== null) { iframe.style.height = iframeHeight; } }

一是设置自己的iframe模板页面。

二是将iframe初始隐藏。这样做的好处是当页面上有多个CanvasEditor时,只显示用户所关心的CanvasEditor运行的结果,避免造成过多打扰。

三是将其高度设置为200px。在高分辨率显示设备上,这可获得400px的高度,足够大了。如果页面上还设置了iframe-height属性值,则取其值。

JavaScript标签

与其在每个CanvasEditor的JavaScript标签处都要编写:

let canvas = document.querySelector('canvas'); let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height);

的代码,不如将这三行必不可少的代码自动插入到JavaScript标签的代码中。

LiveEditor有一个钩子方法doBeforeApplyJSFromTab,在用户按下Run按钮时被调用。只要该方法返回一个字符串,则将该字符串作为要运行的JavaScript代码放在JavaScript标签代码的前面。

doBeforeApplyJSFromTab(doc, resultDiv) { return ''; }

作为基类的LiveEditor只返回一个空字符串,而作为子类的CanvasEditor可返回实际需要的字符串。例如:

doBeforeApplyJSFromTab(doc, resultDiv) { let src = ` let canvas = document.querySelector('canvas'); canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); `; return src; }

上面代码可自动应用高分辨率。但此功能已被功能更强大的CanvasUtils所代替。详见下面。

当用户按下Run按钮后,通过调用另一个钩子方法doAfterRunClicked来显示iframe

doAfterRunClicked(iframe) { iframe.style.display = 'block'; }

该方法在读取并设置了所有标签内容后将被调用。

还有另一钩子方法:

doOnSetupDivs(ctrlDiv, resultDiv, consoleDiv) { }

该方法在初始化完所有的DOM后将被调用,可用于进一步设置上面3个参数的对象。目前CanvasEditor未做任何使用。

钩子方法doOnImportJSModule可用于导入所需的JavaScript模块:

doOnImportJSModule() { return ''; }

同样,作为子类,CanvasEditor导入了ColorUtilsCanvasUtils

doOnImportJSModule() { return ` import {ColorUtils} from '/js/esm/ColorUtils.js'; import {CanvasUtils} from '/js/esm/CanvasUtils.js'; `; } doBeforeApplyJSFromTab(doc, resultDiv) { let src = ` let canvas = document.querySelector('canvas'); let ctx = CanvasUtils.GetCtxProxy(canvas); ctx.clearRect(0, 0, canvas.width, canvas.height); `; return src; }

自动获取canvas的代理,自动清屏。并且,CanvasEditor中的JavaScript代码便可使用:

ctx.fillStyle = ColorUtils.GetRandomSoftRGB(); ctx.fillRect(250, 10, 200, 200);

设置多种CSS样式

从实际需要来看,CanvasEditor共需要3个设置CSS的地方。

第一个是canvas-default-style.css文件,作为每个CanvasEditor都需共享的初始样式,由iframe-template.html文件设置选用。一般可设置如下:

canvas { border: 1px solid gray; display: block; width: 100vw; height: 100vh; }

因为用户可能要多次运行,当运行到以下代码:

canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio;

必须确保clientWidthclientHeight回到初始值,否则,其大小将持续不断地增大。因此,给定初始值很重要。

第二个CSS可设置特定特殊的样式,以供同一页面上的部分CanvasEditor共同使用。这种样式通过设置CanvasEditorresult-default-css属性来设置。

...

上面的文件中设为style-at-running.css文件,表示这种设置只有当用户按下Run按钮后才会发生作用。

iframe-template.html文件有个地方专门接收上面CSS文件的内容:

<style id="custom-default-css"></style>

第三个CSSCSS标签下的的内容,可应用于每个CanvasEditor的具体样式。同样,iframe-template.html文件也有地方专门接收此类设置:

<style id="css-from-tab"></style>

通过上面的设置,应足可应对各种复杂的情况。

通过CSS标签设置CSS样式

ctx.beginPath(); ctx.strokeStyle = 'cyan'; ctx.strokeRect(100, 50, 100, 100);
canvas { width: 400px; height: 200px; }

由于使用了代理机制,不管是否高分辨率设备,均能自动应对。

通过属性设置CSS样式

下面通过CanvasEditor的属性result-default-css来设置特定的样式。

<canvas-editor result-default-css="style-at-running.css"> <div title="JavaScript"> <trim-pre><esc-code> ... </esc-code></trim-pre> </div> </canvas-editor>

style-at-running.css文件的内容:

canvas { width: 300px; height: 300px; }

实际效果:

ctx.beginPath(); ctx.arc(150, 100, 50, 0, 360); ctx.strokeStyle = 'cyan'; ctx.stroke();

arc

arc方法中的参数,最后两个无需转化。

ctx.beginPath(); ctx.arc(150, 100, 50, 0, 360); ctx.strokeStyle = 'cyan'; ctx.stroke();

参数中的变量

ctx.beginPath(); let x = 50; let y = 20; for (let i = 1; i <= 5; i++, y += 30) { ctx.moveTo(x, y); ctx.lineTo(x + 200, y); } ctx.strokeStyle = 'cyan'; ctx.stroke();
canvas { width: 300px; height: 300px; }

引用Canvas的宽高值

ctx.beginPath(); let padding = 20; let xStart = padding; let xEnd = canvas.clientWidth - padding; let yStart = padding; let yEnd = canvas.clientHeight - padding; ctx.moveTo(xStart, yStart); ctx.lineTo(xEnd, yStart); ctx.moveTo(xStart, yStart); ctx.lineTo(xStart, yEnd); ctx.strokeStyle = 'cyan'; ctx.stroke();
canvas { width: 300px; height: 200px; }

上面的代码:

let xEnd = canvas.clientWidth - padding; let yEnd = canvas.clientHeight - padding;

在涉及到需要引用canvas的宽度及高度的值以参与运算时,需分别使用clientWidthclientHeight。原因有二:一是要求不管是否有高分辨率设备,代码应一样。二是CanvasUtils将统一转换为widthheight属性值。

支持网格

ctx.font = 'bold italic 48px Helvetica'; ctx.textBaseline = 'middle'; ctx.textAlign = 'left'; let x = 50; let y = 50; ctx.fillStyle = 'DEEPSKYBLUE'; ctx.fillText('Hello Canvas', x, y);