Skip to content

Commit 5b162eb

Browse files
committed
Translate webgl-tips.md into Chinese
1 parent cfcea45 commit 5b162eb

File tree

1 file changed

+345
-0
lines changed

1 file changed

+345
-0
lines changed

webgl/lessons/zh_cn/webgl-tips.md

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
Title: WebGL2 小贴士
2+
Description: 使用 WebGL 时可能遇到的一些小问题
3+
TOC: 画布截屏
4+
5+
本文收集了一些你在使用 WebGL 时可能遇到的、看起来太小而不值得单独写一篇文章的问题。
6+
7+
---
8+
9+
<a id="screenshot" data-toc="Taking a screenshot"></a>
10+
11+
# 对 Canvas 截图
12+
13+
在浏览器中,实际上有两种函数可以对画布进行截图。
14+
一种旧方法是:
15+
[`canvas.toDataURL`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
16+
另一种新的更好的方法是:
17+
[`canvas.toBlob`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
18+
19+
所以你可能会认为,只需添加如下代码就能轻松截图:
20+
21+
```html
22+
<canvas id="c"></canvas>
23+
+<button id="screenshot" type="button">Save...</button>
24+
```
25+
26+
```js
27+
const elem = document.querySelector('#screenshot');
28+
elem.addEventListener('click', () => {
29+
canvas.toBlob((blob) => {
30+
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
31+
});
32+
});
33+
34+
const saveBlob = (function() {
35+
const a = document.createElement('a');
36+
document.body.appendChild(a);
37+
a.style.display = 'none';
38+
return function saveData(blob, fileName) {
39+
const url = window.URL.createObjectURL(blob);
40+
a.href = url;
41+
a.download = fileName;
42+
a.click();
43+
};
44+
}());
45+
```
46+
47+
这是来自[动画那篇文章](webgl-animation.html)的示例,在其中加入了上面的代码,并添加了一些 CSS 来放置按钮。
48+
49+
{{{example url="../webgl-tips-screenshot-bad.html"}}}
50+
51+
当我尝试时,我得到了这样的截图。
52+
53+
<div class="webgl_center"><img src="resources/screencapture-398x298.png"></div>
54+
55+
是的,这是一个空白图像。
56+
57+
根据你的浏览器/操作系统,它可能对你有效,但通常情况下它是无法工作的。
58+
59+
问题在于,出于性能和兼容性的考虑,浏览器默认会在你绘制完后,清除 WebGL 画布的绘图缓冲区。
60+
61+
有三种解决方案。
62+
63+
1. 在截图之前调用渲染代码
64+
65+
我们使用的代码是一个 `drawScene` 函数。
66+
最好让这段代码不改变任何状态,这样我们就可以在截图时调用它来进行渲染。
67+
68+
```js
69+
elem.addEventListener('click', () => {
70+
+ drawScene();
71+
canvas.toBlob((blob) => {
72+
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
73+
});
74+
});
75+
```
76+
77+
2. 在渲染循环中调用截图代码
78+
79+
在这种情况下,我们只需设置一个标志表示我们想要截图,然后在渲染循环中实际执行截图操作。
80+
81+
```js
82+
let needCapture = false;
83+
elem.addEventListener('click', () => {
84+
needCapture = true;
85+
});
86+
```
87+
88+
然后在我们的渲染循环中,也就是当前实现于 `drawScene` 的函数中,在所有内容绘制完成之后的某个位置。
89+
90+
```js
91+
function drawScene(time) {
92+
...
93+
94+
+ if (needCapture) {
95+
+ needCapture = false;
96+
+ canvas.toBlob((blob) => {
97+
+ saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
98+
+ });
99+
+ }
100+
101+
...
102+
}
103+
```
104+
105+
3. 在创建 WebGL 上下文时,设置 `preserveDrawingBuffer: true`
106+
107+
```js
108+
const gl = someCanvas.getContext('webgl2', {preserveDrawingBuffer: true});
109+
```
110+
111+
这会让 WebGL 在将画布与页面其他部分合成后不清除画布,但会阻止某些*可能的*优化。
112+
113+
我会选择上面的第 1 种方法。对于这个特定示例,我首先会把更新状态的代码部分与绘制的代码部分分离开。
114+
115+
```js
116+
var then = 0;
117+
118+
- requestAnimationFrame(drawScene);
119+
+ requestAnimationFrame(renderLoop);
120+
121+
+ function renderLoop(now) {
122+
+ // Convert to seconds
123+
+ now *= 0.001;
124+
+ // Subtract the previous time from the current time
125+
+ var deltaTime = now - then;
126+
+ // Remember the current time for the next frame.
127+
+ then = now;
128+
+
129+
+ // Every frame increase the rotation a little.
130+
+ rotation[1] += rotationSpeed * deltaTime;
131+
+
132+
+ drawScene();
133+
+
134+
+ // Call renderLoop again next frame
135+
+ requestAnimationFrame(renderLoop);
136+
+ }
137+
138+
// Draw the scene.
139+
+ function drawScene() {
140+
- function drawScene(now) {
141+
- // Convert to seconds
142+
- now *= 0.001;
143+
- // Subtract the previous time from the current time
144+
- var deltaTime = now - then;
145+
- // Remember the current time for the next frame.
146+
- then = now;
147+
-
148+
- // Every frame increase the rotation a little.
149+
- rotation[1] += rotationSpeed * deltaTime;
150+
151+
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
152+
153+
...
154+
155+
- // Call drawScene again next frame
156+
- requestAnimationFrame(drawScene);
157+
}
158+
```
159+
160+
现在我们只需在截图之前调用 `drawScene` 即可
161+
162+
```js
163+
elem.addEventListener('click', () => {
164+
+ drawScene();
165+
canvas.toBlob((blob) => {
166+
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
167+
});
168+
});
169+
```
170+
171+
现在它应该可以正常工作了。
172+
173+
{{{example url="../webgl-tips-screenshot-good.html" }}}
174+
175+
如果你实际检查捕获的图像,会看到背景是透明的。
176+
详情请参见[这篇文章](webgl-and-alpha.html)。
177+
178+
---
179+
180+
<a id="preservedrawingbuffer" data-toc="Prevent the Canvas Being Cleared"></a>
181+
182+
# 防止画布被清除
183+
184+
假设你想让用户用一个动画对象进行绘画。
185+
在创建 WebGL 上下文时,需要传入 `preserveDrawingBuffer: true`
186+
这可以防止浏览器清除画布。
187+
188+
采用[动画那篇文章](webgl-animation.html)中的最后一个示例
189+
190+
```js
191+
var canvas = document.querySelector("#canvas");
192+
-var gl = canvas.getContext("webgl2");
193+
+var gl = canvas.getContext("webgl2", {preserveDrawingBuffer: true});
194+
```
195+
196+
并修改对 `gl.clear` 的调用,使其只清除深度缓冲区。
197+
198+
```
199+
-// Clear the canvas.
200+
-gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
201+
+// Clear the depth buffer.
202+
+gl.clear(gl.DEPTH_BUFFER_BIT);
203+
```
204+
205+
{{{example url="../webgl-tips-preservedrawingbuffer.html" }}}
206+
207+
注意,如果你真想做一个绘图程序,这不是一个解决方案,
208+
因为每当我们改变画布的分辨率时,浏览器仍然会清除画布。
209+
我们是根据显示尺寸来改变分辨率的。显示尺寸会在窗口大小改变时变化,
210+
这可能发生在用户下载文件时,甚至在另一个标签页,浏览器添加状态栏时。
211+
还包括用户旋转手机,浏览器从竖屏切换到横屏时。
212+
213+
如果你真的想做绘图程序,应该[渲染到纹理](webgl-render-to-texture.html)。
214+
215+
---
216+
217+
<a id="tabindex" data-toc="Get Keyboard Input From a Canvas"></a>
218+
219+
# 获取键盘输入
220+
221+
如果你制作的是全页面/全屏的 WebGL 应用,那么你可以随意处理,
222+
但通常你希望某个 canvas 只是页面的一部分,
223+
并希望用户点击 canvas 时它能接收键盘输入。
224+
不过 canvas 默认是无法获取键盘输入的。为了解决这个问题,
225+
需要将 canvas 的 [`tabindex`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex)
226+
设置为 0 或更大。例如:
227+
228+
```html
229+
<canvas tabindex="0"></canvas>
230+
```
231+
232+
不过这会引发一个新问题。任何设置了 `tabindex` 的元素在获得焦点时都会被高亮显示。
233+
为了解决这个问题,需要将其获得焦点时的 CSS 边框(outline)设置为 none。
234+
235+
```css
236+
canvas:focus {
237+
outline:none;
238+
}
239+
```
240+
241+
为演示起见,这里有三个 canvas
242+
243+
```html
244+
<canvas id="c1"></canvas>
245+
<canvas id="c2" tabindex="0"></canvas>
246+
<canvas id="c3" tabindex="1"></canvas>
247+
```
248+
249+
以及仅针对最后一个 canvas 的一些 CSS
250+
251+
```css
252+
#c3:focus {
253+
outline: none;
254+
}
255+
```
256+
257+
让我们给所有 canvas 都附加相同的事件监听器
258+
259+
```js
260+
document.querySelectorAll('canvas').forEach((canvas) => {
261+
const ctx = canvas.getContext('2d');
262+
263+
function draw(str) {
264+
ctx.clearRect(0, 0, canvas.width, canvas.height);
265+
ctx.textAlign = 'center';
266+
ctx.textBaseline = 'middle';
267+
ctx.fillText(str, canvas.width / 2, canvas.height / 2);
268+
}
269+
draw(canvas.id);
270+
271+
canvas.addEventListener('focus', () => {
272+
draw('has focus press a key');
273+
});
274+
275+
canvas.addEventListener('blur', () => {
276+
draw('lost focus');
277+
});
278+
279+
canvas.addEventListener('keydown', (e) => {
280+
draw(`keyCode: ${e.keyCode}`);
281+
});
282+
});
283+
```
284+
285+
注意,第一个 canvas 无法接受键盘输入。
286+
第二个 canvas 可以,但它会被高亮显示。
287+
第三个 canvas 同时应用了这两个解决方案。
288+
289+
{{{example url="../webgl-tips-tabindex.html"}}}
290+
291+
---
292+
293+
<a id="html-background" data-toc="Use WebGL2 as Background in HTML"></a>
294+
295+
# 将背景设为WebGL动画
296+
297+
一个常见问题是如何将WebGL动画设置为网页背景。
298+
299+
以下是两种最常用的实现方式:
300+
301+
* 将Canvas的CSS `position` 设置为 `fixed`,如下所示:
302+
303+
```css
304+
#canvas {
305+
position: fixed;
306+
left: 0;
307+
top: 0;
308+
z-index: -1;
309+
...
310+
}
311+
```
312+
313+
并将 `z-index` 设为 -1。
314+
315+
这种方案的一个小缺点是:你的 JavaScript 代码必须与页面其他部分兼容,如果页面比较复杂,就需要确保 WebGL 代码中的 JavaScript 不会与页面其他功能的 JavaScript 产生冲突。
316+
317+
* 使用 `iframe`
318+
319+
这正是本站[首页](/)采用的解决方案。
320+
321+
在您的网页中,只需插入一个iframe即可实现,例如:
322+
323+
```html
324+
<iframe id="background" src="background.html"></iframe>
325+
<div>
326+
Your content goes here.
327+
</div>
328+
```
329+
330+
接下来将这个iframe设置为全屏背景样式,本质上和我们之前设置canvas的代码相同——只是需要额外将 `border` 设为 `none`,因为iframe默认带有边框。具体实现如下:
331+
332+
```css
333+
#background {
334+
position: fixed;
335+
width: 100vw;
336+
height: 100vh;
337+
left: 0;
338+
top: 0;
339+
z-index: -1;
340+
border: none;
341+
pointer-events: none;
342+
}
343+
```
344+
345+
{{{example url="../webgl-tips-html-background.html"}}}

0 commit comments

Comments
 (0)