-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathatom.xml
524 lines (293 loc) · 375 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Sanonz</title>
<link href="/atom.xml" rel="self"/>
<link href="https://sanonz.github.io/"/>
<updated>2024-01-24T02:42:46.303Z</updated>
<id>https://sanonz.github.io/</id>
<author>
<name>Sanonz</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>用 VS Code 编辑器开发 STM32 嵌入式</title>
<link href="https://sanonz.github.io/2024/debugging-support-for-arm-cortex-m-microcontrollers-on-vscode/"/>
<id>https://sanonz.github.io/2024/debugging-support-for-arm-cortex-m-microcontrollers-on-vscode/</id>
<published>2024-01-16T12:10:00.000Z</published>
<updated>2024-01-24T02:42:46.303Z</updated>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>目前大家用 <a href="https://www.keil.com" target="_blank" rel="noopener">Keil</a> 和 <a href="https://www.iar.com" target="_blank" rel="noopener">IAR</a> 工具来开发 STM32 的比较多,而 Keil 和 STM 都是 ARM 的自己产品,适配做的不错,目前大家也是用 Keil 的居多,Keil 有优点也有缺点,Keil 调试方面做的很好,但是编辑器做的一般,在编辑器使用体验上 VS Code 会好用很多,Keil 6 已经做成了 VS Code 的插件形式,名字叫 <a href="https://marketplace.visualstudio.com/items?itemName=stmicroelectronics.stm32-vscode-extension" target="_blank" rel="noopener">STM32 VS Code Extension</a>,不过目前还没正式上线,使用起来问题较多,今天介绍另一种方式在 VS Code 上开发 STM32。</p><a id="more"></a><h2 id="调试效果图"><a href="#调试效果图" class="headerlink" title="调试效果图"></a>调试效果图</h2><img src="/2024/debugging-support-for-arm-cortex-m-microcontrollers-on-vscode/[email protected]" width="800"><p>编辑器最底部的状态条有两个快捷按钮:<code>Build</code>、<code>Load</code>,添加教程查看 <a href="#增加快捷按钮">增加快捷按钮</a></p><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><h3 id="VS-Code"><a href="#VS-Code" class="headerlink" title="VS Code"></a>VS Code</h3><ol><li>安装 <a href="https://code.visualstudio.com" target="_blank" rel="noopener">VS Code</a> 编辑器</li><li>安装 <a href="https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug" target="_blank" rel="noopener">Cortex Debug</a> 调试插件</li></ol><h3 id="OpenOCD"><a href="#OpenOCD" class="headerlink" title="OpenOCD"></a>OpenOCD</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install openocd</span><br></pre></td></tr></table></figure><p>安装成功后测试下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ openocd --version</span><br><span class="line">Open On-Chip Debugger 0.12.0</span><br><span class="line">Licensed under GNU GPL v2</span><br><span class="line">For bug reports, <span class="built_in">read</span></span><br><span class="line"> http://openocd.org/doc/doxygen/bugs.html</span><br></pre></td></tr></table></figure><h3 id="GCC-ARM"><a href="#GCC-ARM" class="headerlink" title="GCC (ARM)"></a>GCC (ARM)</h3><p>安装指定版本 GCC,可以在官网查看有哪些版本 <a href="https://developer.arm.com/downloads/-/gnu-rm" target="_blank" rel="noopener">GNU Arm Embedded Toolchain Downloads</a>,例如我的项目用的是 <code>6-2017-q2-update</code> 版本,以下为安装步骤</p><ol><li>执行 <code>brew tap-new $USER/local-tap</code></li><li>进入 <code>/usr/local/Homebrew/Library/Taps/$USER/homebrew-local-tap</code> 目录</li><li>新建 <code>[email protected]</code>(注意文件名版本号)</li><li>写入下方内容,类名 <code>AT</code> 后、<code>url</code>、<code>version</code> 修改为指定版本号,<code>sha256</code> 修改为当前版本文件计算出来的</li></ol><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">require</span> <span class="string">'formula'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ArmNoneEabiAT62017Q2Update</span> < Formula</span></span><br><span class="line"> homepage <span class="string">'https://developer.arm.com/downloads/-/gnu-rm'</span></span><br><span class="line"> version <span class="string">'6-2017-q2-update'</span></span><br><span class="line"></span><br><span class="line"> url <span class="string">'https://developer.arm.com/-/media/Files/downloads/gnu-rm/6-2017q2/gcc-arm-none-eabi-6-2017-q2-update-mac.tar.bz2'</span></span><br><span class="line"> sha256 <span class="string">'7d3080514a2899d05fc55466cdc477e2448b6a62f536ffca3dd846822ff52900'</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">install</span></span></span><br><span class="line"> (prefix/<span class="string">"gcc"</span>).install Dir[<span class="string">"./*"</span>]</span><br><span class="line"> Dir.glob(prefix/<span class="string">"gcc/bin/*"</span>) { <span class="params">|file|</span> bin.install_symlink file }</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>执行以下命令安装 GCC 库</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install <span class="variable">$USER</span>/<span class="built_in">local</span>-tap/arm-none-eabi@6-2017-q2-update</span><br></pre></td></tr></table></figure><p>安装成功后测试下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ arm-none-eabi-gcc --version</span><br><span class="line">arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10) 10.3.1 20210824 (release)</span><br><span class="line">Copyright (C) 2020 Free Software Foundation, Inc.</span><br><span class="line">This is free software; see the <span class="built_in">source</span> <span class="keyword">for</span> copying conditions. There is NO</span><br><span class="line">warranty; not even <span class="keyword">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ arm-none-eabi-gdb --version</span><br><span class="line">GNU gdb (GNU Arm Embedded Toolchain 10.3-2021.10) 10.2.90.20210621-git</span><br><span class="line">Copyright (C) 2021 Free Software Foundation, Inc.</span><br><span class="line">License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html></span><br><span class="line">This is free software: you are free to change and redistribute it.</span><br><span class="line">There is NO WARRANTY, to the extent permitted by law.</span><br></pre></td></tr></table></figure><h2 id="配置调试环境"><a href="#配置调试环境" class="headerlink" title="配置调试环境"></a>配置调试环境</h2><h3 id="配置-OpenOCD"><a href="#配置-OpenOCD" class="headerlink" title="配置 OpenOCD"></a>配置 <code>OpenOCD</code></h3><p>在项目根目录创建 <code>./vscode/openocd.cfg</code>,以下配置调试器为 <code>cmsis-dap</code>,芯片为 <code>stm32f4x</code>,可以根据自己项目实际使用情况进行修改</p><ol><li>查看支持的调试器 <a href="https://github.com/openocd-org/openocd/tree/master/tcl/interface" target="_blank" rel="noopener">https://github.com/openocd-org/openocd/tree/master/tcl/interface</a> 列表</li><li>查看支持的芯片 <a href="https://github.com/openocd-org/openocd/tree/master/tcl/target" target="_blank" rel="noopener">https://github.com/openocd-org/openocd/tree/master/tcl/target</a> 列表</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">source [find interface/cmsis-dap.cfg]</span><br><span class="line"></span><br><span class="line">transport select swd</span><br><span class="line"></span><br><span class="line">source [find target/stm32f4x.cfg]</span><br><span class="line"></span><br><span class="line">$_TARGETNAME configure -event gdb-detach {</span><br><span class="line"> resume</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="配置-tasks-json"><a href="#配置-tasks-json" class="headerlink" title="配置 tasks.json"></a>配置 <code>tasks.json</code></h3><p>在项目根目录创建 <code>./vscode/tasks.json</code> 文件,配置两个任务,一个是编译源代码,另一个是把编译的固件下载到芯片中,可根据自己的项目情况修改 <code>command</code>、<code>args</code> 参数来指定编译和下载命令</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"version"</span>: <span class="string">"2.0.0"</span>,</span><br><span class="line"> <span class="attr">"tasks"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"label"</span>: <span class="string">"Build"</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"shell"</span>,</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"make"</span>,</span><br><span class="line"> <span class="attr">"args"</span>: [],</span><br><span class="line"> <span class="attr">"problemMatcher"</span>: <span class="string">"$gcc"</span>,</span><br><span class="line"> <span class="attr">"group"</span>: <span class="string">"build"</span>,</span><br><span class="line"> <span class="attr">"detail"</span>: <span class="string">"编译源码"</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"label"</span>: <span class="string">"Load"</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"shell"</span>,</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"openocd"</span>,</span><br><span class="line"> <span class="attr">"args"</span>: [</span><br><span class="line"> <span class="string">"-f"</span>,</span><br><span class="line"> <span class="string">".vscode/openocd.cfg"</span>,</span><br><span class="line"> <span class="string">"-c"</span>,</span><br><span class="line"> <span class="string">"program build/Project.bin 0x08000000 verify reset exit"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"dependsOn"</span>: [</span><br><span class="line"> <span class="string">"Build"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"detail"</span>: <span class="string">"下载固件"</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="配置-launch-json"><a href="#配置-launch-json" class="headerlink" title="配置 launch.json"></a>配置 <code>launch.json</code></h3><ol><li>下载 <a href="https://github.com/tinygo-org/stm32-svd/blob/main/svd/stm32f407.svd" target="_blank" rel="noopener">STM32F407x.svd</a> 文件到 <code>./vscode</code> 目录下,这里查看所有支持的 <a href="https://github.com/tinygo-org/stm32-svd/tree/main/svd" target="_blank" rel="noopener">芯片列表</a></li><li>在项目根目录创建 <code>./vscode/launch.json</code> 文件,写入下方数据,可以根据自己的项目进行修改相关参数</li></ol><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"version"</span>: <span class="string">"0.2.0"</span>,</span><br><span class="line"> <span class="attr">"configurations"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"Cortex Debug"</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"cortex-debug"</span>,</span><br><span class="line"> <span class="attr">"request"</span>: <span class="string">"launch"</span>,</span><br><span class="line"> <span class="attr">"cwd"</span>: <span class="string">"${workspaceRoot}"</span>,</span><br><span class="line"> <span class="attr">"preLaunchTask"</span>: <span class="string">"Load"</span>,</span><br><span class="line"> <span class="attr">"executable"</span>: <span class="string">"${workspaceRoot}/build/project.elf"</span>,</span><br><span class="line"> <span class="attr">"servertype"</span>: <span class="string">"openocd"</span>,</span><br><span class="line"> <span class="attr">"configFiles"</span>: [</span><br><span class="line"> <span class="string">".vscode/openocd.cfg"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"device"</span>: <span class="string">"STM32F407ZGT6"</span>,</span><br><span class="line"> <span class="attr">"interface"</span>: <span class="string">"swd"</span>,</span><br><span class="line"> <span class="attr">"svdFile"</span>: <span class="string">"./.vscode/STM32F407x.svd"</span>,</span><br><span class="line"> <span class="attr">"gdbPath"</span>: <span class="string">"/usr/local/bin/arm-none-eabi-gdb"</span>,</span><br><span class="line"> <span class="attr">"runToEntryPoint"</span>: <span class="string">"main"</span>,</span><br><span class="line"> <span class="attr">"liveWatch"</span>: {</span><br><span class="line"> <span class="attr">"enabled"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"samplesPerSecond"</span>: <span class="number">4</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="增加快捷按钮"><a href="#增加快捷按钮" class="headerlink" title="增加快捷按钮"></a>增加快捷按钮</h2><ol><li>安装 <a href="https://marketplace.visualstudio.com/items?itemName=spencerwmiles.vscode-task-buttons" target="_blank" rel="noopener">Task Buttons</a> 插件</li><li>在项目根目录创建 <code>./vscode/settings.json</code> 文件,展示两个按钮,一个编译,一个下载</li></ol><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"VsCodeTaskButtons.tasks"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"label"</span>: <span class="string">"$(notebook-open-as-text) Build"</span>,</span><br><span class="line"> <span class="attr">"task"</span>: <span class="string">"Build"</span>,</span><br><span class="line"> <span class="attr">"tooltip"</span>: <span class="string">"Build target files"</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"label"</span>: <span class="string">"$(notebook-move-down) Load"</span>,</span><br><span class="line"> <span class="attr">"task"</span>: <span class="string">"Load"</span>,</span><br><span class="line"> <span class="attr">"tooltip"</span>: <span class="string">"Download code to flash memory"</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>效果查看 <a href="#调试效果图">调试效果图</a> 编辑器最底部的状态条,有两个按钮:<code>Build</code>、<code>Load</code></p><h2 id="Dev-Container"><a href="#Dev-Container" class="headerlink" title="Dev Container"></a>Dev Container</h2><p>使用 Dev Container 可以实现只需配置一次开发环境,以后换别的开发机器只需要点几下就可快速启动一个一摸一样的开发环境,避免了每次配置开发环境所浪费的时间。还有就是开发环境可以和本机完全隔离,当不需要这个开发环境时,只需要在 <code>Docker</code> 中一键删除就行,非常的清爽、干净</p><h3 id="安装依赖-1"><a href="#安装依赖-1" class="headerlink" title="安装依赖"></a>安装依赖</h3><ol><li>安装 <a href="https://www.docker.com/products/docker-desktop/" target="_blank" rel="noopener">Docker Desktop</a></li><li>安装 <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers" target="_blank" rel="noopener">Dev Containers</a> VS Code 插件</li></ol><h3 id="配置-devcontainer-json"><a href="#配置-devcontainer-json" class="headerlink" title="配置 devcontainer.json"></a>配置 devcontainer.json</h3><p>在项目根目录创建 <code>./.devcontainer/devcontainer.json</code> 文件,写入下方数据</p><blockquote><p>如果想要安装其它版本的 gcc,可以在 <a href="#GCC-ARM">安装 gcc</a> 中查看</p></blockquote><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// For format details, see https://aka.ms/vscode-remote/devcontainer.json</span></span><br><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"STM32"</span>,</span><br><span class="line"> <span class="attr">"build"</span>: {</span><br><span class="line"> <span class="attr">"dockerfile"</span>: <span class="string">"Dockerfile"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"runArgs"</span>: [],</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Configure tool-specific properties.</span></span><br><span class="line"> <span class="attr">"customizations"</span>: {</span><br><span class="line"> <span class="comment">// Configure properties specific to VS Code.</span></span><br><span class="line"> <span class="attr">"vscode"</span>: {</span><br><span class="line"> <span class="comment">// Set *default* container specific settings.json values on container create.</span></span><br><span class="line"> <span class="attr">"settings"</span>: {},</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Add the IDs of extensions you want installed when the container is created.</span></span><br><span class="line"> <span class="attr">"extensions"</span>: [</span><br><span class="line"> <span class="string">"marus25.cortex-debug"</span>,</span><br><span class="line"> <span class="string">"ms-vscode.cpptools-extension-pack"</span>,</span><br><span class="line"> <span class="string">"spencerwmiles.vscode-task-buttons"</span></span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在项目根目录创建 <code>./.devcontainer/Dockerfile</code> 文件,配置一个容器,使用 <code>ubuntu</code> 系统,安装 <code>gcc</code> 编译环境</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ubuntu:<span class="number">20.04</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get update && \</span></span><br><span class="line"><span class="bash"> apt-get install wget -y \</span></span><br><span class="line"><span class="bash"> apt-get clean</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> <span class="built_in">cd</span> /tmp && \</span></span><br><span class="line"><span class="bash"> wget -q https://armkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/6-2017q2/gcc-arm-none-eabi-6-2017-q2-update-linux.tar.bz2 && \</span></span><br><span class="line"><span class="bash"> tar xf gcc-arm-none-eabi-6-2017-q2-update-linux.tar.bz2 && \</span></span><br><span class="line"><span class="bash"> mv gcc-arm-none-eabi-6-2017-q2-update /opt/gcc-arm-none-eabi && \</span></span><br><span class="line"><span class="bash"> rm gcc-arm-none-eabi-6-2017-q2-update-linux.tar.bz2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> PATH=<span class="string">"${PATH}:/opt/gcc-arm-none-eabi/bin"</span></span><br></pre></td></tr></table></figure><p>使用快捷键 <code>Command + Shift + P</code> 打开命令板面(或者点击左下角 <code>Open a Remote Window</code> 快捷图标),搜索 <code>Rebuild and Reopen in Container</code> 命令并执行,VS Code 就会连接到刚刚创建的容器中</p><h2 id="问题列表"><a href="#问题列表" class="headerlink" title="问题列表"></a>问题列表</h2><ol><li>调试区域的 <code>VARIABLES</code>、<code>WATCH</code> 板面变量不会自动更新,断点停止的时候才会刷新,可以使用 <code>CORTEX LIVE WATCH</code> 板面添加变量,添加到这里的会定时刷新,刷新周期可以在 <a href="#配置-launch-json">配置 launch.json</a> 的 <code>liveWatch</code> 字段中配置</li><li>调试区域的 <code>XPERIPHERAL</code> 板面外设点击刷新报错 <code>peripheral-viewer: readMemory failed @ 0x40021000 for 40 bytes: CodeExpectedError: Busy</code>,好像是 <a href="https://marketplace.visualstudio.com/items?itemName=mcu-debug.peripheral-viewer" target="_blank" rel="noopener">Peripheral Viewer</a> 插件有 BUG</li><li>如果断点结束后芯片停止运行,确认 <a href="#配置-OpenOCD">配置 OpenOCD</a> 环节是否忽略了以下内容,添加后即可恢复<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$_TARGETNAME configure -event gdb-detach {</span><br><span class="line"> resume</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<summary type="html">
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>目前大家用 <a href="https://www.keil.com" target="_blank" rel="noopener">Keil</a> 和 <a href="https://www.iar.com" target="_blank" rel="noopener">IAR</a> 工具来开发 STM32 的比较多,而 Keil 和 STM 都是 ARM 的自己产品,适配做的不错,目前大家也是用 Keil 的居多,Keil 有优点也有缺点,Keil 调试方面做的很好,但是编辑器做的一般,在编辑器使用体验上 VS Code 会好用很多,Keil 6 已经做成了 VS Code 的插件形式,名字叫 <a href="https://marketplace.visualstudio.com/items?itemName=stmicroelectronics.stm32-vscode-extension" target="_blank" rel="noopener">STM32 VS Code Extension</a>,不过目前还没正式上线,使用起来问题较多,今天介绍另一种方式在 VS Code 上开发 STM32。</p>
</summary>
<category term="embedded-system" scheme="https://sanonz.github.io/categories/embedded-system/"/>
<category term="arm" scheme="https://sanonz.github.io/tags/arm/"/>
<category term="mcu" scheme="https://sanonz.github.io/tags/mcu/"/>
<category term="stm32" scheme="https://sanonz.github.io/tags/stm32/"/>
<category term="vscode" scheme="https://sanonz.github.io/tags/vscode/"/>
</entry>
<entry>
<title>使用 FontCreator 编辑字体库</title>
<link href="https://sanonz.github.io/2021/use-fontcreator-to-edit-font-libraries/"/>
<id>https://sanonz.github.io/2021/use-fontcreator-to-edit-font-libraries/</id>
<published>2021-06-16T06:45:20.000Z</published>
<updated>2024-01-24T02:42:46.299Z</updated>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>编辑字体的软件有很多种,例如:<a href="https://www.high-logic.com/font-editor/fontcreator" target="_blank" rel="noopener">FontCreator</a>、<a href="https://www.fontlab.com/" target="_blank" rel="noopener">FontLab</a>、<a href="https://www.coreldraw.com/en/product/coreldraw/" target="_blank" rel="noopener">CorelDRAW</a>、<a href="https://github.com/adobe-type-tools/afdko/" target="_blank" rel="noopener">AFDKO</a>、<a href="https://fontforge.org/" target="_blank" rel="noopener">FontForge</a> 等,据说 FontCreator 是下载量最高,使用最傻瓜的一款,作为外行的我们当然选择最傻瓜的一款比较好上手,接下来我们就以 FontCreator 这款字体编辑器进行编辑探索。</p><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><ol><li>安装字体编辑软件:<a href="https://www.high-logic.com/font-editor/fontcreator" target="_blank" rel="noopener">FontCreator</a></li><li>下载上手编辑的字体:<a href="https://github.com/lvmorais/bebasneue" target="_blank" rel="noopener">BebasNeue-Regular.ttf</a></li></ol><a id="more"></a><h2 id="编辑字体"><a href="#编辑字体" class="headerlink" title="编辑字体"></a>编辑字体</h2><h3 id="导入字体"><a href="#导入字体" class="headerlink" title="导入字体"></a>导入字体</h3><p>打开 FontCreator 软件,导入下载下来的 BebasNeue-Regular.ttf 字体,导入后会列出当前所有字体</p><img src="/2021/use-fontcreator-to-edit-font-libraries/[email protected]" width="928"><h3 id="删除字体"><a href="#删除字体" class="headerlink" title="删除字体"></a>删除字体</h3><p>选择不需要的字体右键点击 <code>Delete</code> 可以删除字体,比如这里只留数字</p><img src="/2021/use-fontcreator-to-edit-font-libraries/[email protected]" width="928"><h3 id="占位符字体-notdef"><a href="#占位符字体-notdef" class="headerlink" title="占位符字体 (.notdef)"></a>占位符字体 (.notdef)</h3><p>上图的第一个字符 <code>.notdef</code> 叫做占位符,又叫故障字,英文称呼为:<a href="http://unicode.org/glossary/#replacement_glyph" target="_blank" rel="noopener">Replacement Glyph</a>,规定这个特殊字体必须存在不能删除,它放在第一位索引为 0,当字体识别不了时会显示这个 <code>.notdef</code> 字体,比如上方删除后的字体只保留数字,当我们用这个字体库展示 <code>A</code> 字母时不会显示 <code>A</code> 只会显示 <code>.notdef</code> 所对应的字体,因为字体库只有数字,没有字母。</p><h3 id="精细调整字体"><a href="#精细调整字体" class="headerlink" title="精细调整字体"></a>精细调整字体</h3><p>选择一个字体双击,就会弹窗设计字体的窗口,这里可以精细的调整字体图标</p><img src="/2021/use-fontcreator-to-edit-font-libraries/[email protected]" width="928"><p>关键字介绍</p><table><thead><tr><th>名称</th><th>介绍</th></tr></thead><tbody><tr><td>Baseline</td><td>基线,是字母放置的水平线</td></tr><tr><td>x-Height</td><td>x 字高,表示基线上小写字母 x 的高度</td></tr><tr><td>CapHeight</td><td>大写字高,表示基线上一个大写字母的高度</td></tr><tr><td>WinAscent / WinDescent</td><td>上、下界线,是指承受字体轮廓的最大范围,超过了界线,字体就会显示空白</td></tr></tbody></table><h3 id="使用工具批量调整字体"><a href="#使用工具批量调整字体" class="headerlink" title="使用工具批量调整字体"></a>使用工具批量调整字体</h3><p>FontCreator 内置了一些批量调整工具,位置在顶部菜单 <code>Tools -> Glyph Transfomer</code>,比如调整字体:大小、粗细、字间距等。下图 <code>Thin(8, 8)</code> 为调整水平与垂直尺寸,<code>Bearings(70, 70)</code> 为调整左边与右边间距大小,<code>Scale(100.00, 100.00)</code> 为缩放水平与垂直尺寸百分比,点击 <code>OK</code> 按钮就会应用到字体上。</p><img src="/2021/use-fontcreator-to-edit-font-libraries/[email protected]" width="516"><h3 id="字体映射"><a href="#字体映射" class="headerlink" title="字体映射"></a>字体映射</h3><p>选择一个字体,打开鼠标右键菜单,选择 <code>Glyph Properties</code> 打开设置窗口</p><img src="/2021/use-fontcreator-to-edit-font-libraries/[email protected]" width="305"><p>这里有一栏 <code>Code-points</code>,点击后边的红色图标 <code>UNICODE</code> 可以编辑字体映射</p><img src="/2021/use-fontcreator-to-edit-font-libraries/[email protected]" width="502"><p>比如这里我们要映射数字 <code>0</code>,它在 <code>Unicode Blocks</code> 分类下,对应的 Code 为 <code>$30</code></p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>当我们混用两种字体时,可能因为两种字体的粗细、字间距不同等,会导致使用 CSS 设置相同的字体大小而实际表现却不相同,使用以上方法可以解决此类问题。</p><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>编辑字体的软件有很多种,例如:<a href="https://www.high-logic.com/font-editor/fontcreator" target="_blank" rel="noopener">FontCreator</a>、<a href="https://www.fontlab.com/" target="_blank" rel="noopener">FontLab</a>、<a href="https://www.coreldraw.com/en/product/coreldraw/" target="_blank" rel="noopener">CorelDRAW</a>、<a href="https://github.com/adobe-type-tools/afdko/" target="_blank" rel="noopener">AFDKO</a>、<a href="https://fontforge.org/" target="_blank" rel="noopener">FontForge</a> 等,据说 FontCreator 是下载量最高,使用最傻瓜的一款,作为外行的我们当然选择最傻瓜的一款比较好上手,接下来我们就以 FontCreator 这款字体编辑器进行编辑探索。</p><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><ol><li>安装字体编辑软件:<a href="https://www.high-logic.com/font-editor/fontcreator" target="_blank" rel="noopener">FontCreator</a></li><li>下载上手编辑的字体:<a href="https://github.com/lvmorais/bebasneue" target="_blank" rel="noopener">BebasNeue-Regular.ttf</a></li></ol>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="font" scheme="https://sanonz.github.io/tags/font/"/>
<category term="FontCreator" scheme="https://sanonz.github.io/tags/FontCreator/"/>
</entry>
<entry>
<title>使用 React Context API 和 Hooks 实现全局状态管理和性能优化</title>
<link href="https://sanonz.github.io/2020/state-management-and-performance-optimization-for-react-context-api-with-hooks/"/>
<id>https://sanonz.github.io/2020/state-management-and-performance-optimization-for-react-context-api-with-hooks/</id>
<published>2020-09-09T11:22:35.000Z</published>
<updated>2024-01-24T02:42:46.295Z</updated>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>我们知道 React.js 默认没有全局状态的概念,需要安装第三方库来实现,最早的是流行的是 Facebook 自己出的 <a href="https://github.com/facebook/flux" target="_blank" rel="noopener">Flux</a>,因为 Flux 使用流程有点复杂,后来 <a href="https://github.com/reduxjs/redux" target="_blank" rel="noopener">Redux</a>、<a href="https://github.com/mobxjs/mobx" target="_blank" rel="noopener">MobX</a> 就兴起了。Redux 是借鉴 Flux 开发的,它们都是单向数据流,而 MobX 则有所不同,它是基于观察者模式实现。</p><p>虽然默认没有全局状态管理,但是也可以通过 <code>Context</code> 特性拼凑出来一个,那为啥以前没人拼凑一个出来用呢?那是因为 React.js 以前的 <code>Context</code> 不好用,也不稳定,官方不建议使用,所以一般是特殊情况非得用不可的时候才使用它,但是现在时过境迁,当初那个不成熟的 <code>Context</code> 现在已经变得强壮有力了。</p><p>在去年二月 React.js 发布了一个大的版本更新 <a href="https://github.com/facebook/react/blob/master/CHANGELOG.md#1680-february-6-2019" target="_blank" rel="noopener">v16.8.0</a> 加入了 hooks 功能,其中内置了 <code>useReducer()</code> hook,它是 <code>useState()</code> 的替代品,简单的状态可以直接使用 <code>useState</code>,当我们遇到复杂多层级的状态或者下个状态要依赖上个状态时使用 <code>useReducer()</code> 则非常方便,在配合 <code>Context</code> 与 <code>useContext()</code> 就能实现类似 Redux 库的功能。</p><a id="more"></a><h2 id="实现全局状态"><a href="#实现全局状态" class="headerlink" title="实现全局状态"></a>实现全局状态</h2><h3 id="useReducer-的简单使用"><a href="#useReducer-的简单使用" class="headerlink" title="useReducer 的简单使用"></a>useReducer 的简单使用</h3><p>这里借用了官方写的一个简单的示例,创建 <code>Counter.jsx</code> 文件。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> initialState = {<span class="attr">count</span>: <span class="number">0</span>};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">reducer</span>(<span class="params">state, action</span>) </span>{</span><br><span class="line"> <span class="keyword">switch</span> (action.type) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'increment'</span>:</span><br><span class="line"> <span class="keyword">return</span> {<span class="attr">count</span>: state.count + <span class="number">1</span>};</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'decrement'</span>:</span><br><span class="line"> <span class="keyword">return</span> {<span class="attr">count</span>: state.count - <span class="number">1</span>};</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Counter</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [state, dispatch] = useReducer(reducer, initialState);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <></span><br><span class="line"> Count: {state.count}</span><br><span class="line"> <button onClick={() => dispatch({<span class="attr">type</span>: <span class="string">'decrement'</span>})}>-<<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> <button onClick={() => dispatch({type: 'increment'})}>+</</span>button></span><br><span class="line"> <<span class="regexp">/></span></span><br><span class="line"><span class="regexp"> );</span></span><br><span class="line"><span class="regexp">}</span></span><br></pre></td></tr></table></figure><p>这样就实现了一个简单的 redux 方式的状态管理器,目前这种只是替代 <code>useState()</code> 在组件中绑定使用的方式,下边将会介绍提升到全局作为全局状态来使用。</p><h3 id="借助-Context-实现全局状态"><a href="#借助-Context-实现全局状态" class="headerlink" title="借助 Context 实现全局状态"></a>借助 <code>Context</code> 实现全局状态</h3><p>创建 <code>store.jsx</code> 文件。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, { createContext, useReducer, useContext } <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = {<span class="attr">count</span>: <span class="number">0</span>};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">reducer</span>(<span class="params">state, action</span>) </span>{</span><br><span class="line"> <span class="keyword">switch</span> (action.type) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'increment'</span>:</span><br><span class="line"> <span class="keyword">return</span> {<span class="attr">count</span>: state.count + <span class="number">1</span>};</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'decrement'</span>:</span><br><span class="line"> <span class="keyword">return</span> {<span class="attr">count</span>: state.count - <span class="number">1</span>};</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Context = createContext();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useStore</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> useContext(Context);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">StoreProvider</span>(<span class="params">{ children }</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [state, dispatch] = useReducer(reducer, initialState);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <Context.Provider value={[state, dispatch]}></span><br><span class="line"> {children}</span><br><span class="line"> <<span class="regexp">/Context.Provider></span></span><br><span class="line"><span class="regexp"> );</span></span><br><span class="line"><span class="regexp">}</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">export { useStore, StoreProvider };</span></span><br></pre></td></tr></table></figure><p>创建 <code>Header.jsx</code> 文件,把更新状态的行为放到此组件中。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> { useStore } <span class="keyword">from</span> <span class="string">'./store'</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Header</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [<span class="comment">/* state */</span>, dispatch] = useStore();</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'header udpate'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <></span><br><span class="line"> <button onClick={() => dispatch({<span class="attr">type</span>: <span class="string">'decrement'</span>})}>-<<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> <button onClick={() => dispatch({type: 'increment'})}>+</</span>button></span><br><span class="line"> <<span class="regexp">/></span></span><br><span class="line"><span class="regexp"> );</span></span><br><span class="line"><span class="regexp">}</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">export default Header;</span></span><br></pre></td></tr></table></figure><p>创建 <code>Footer.jsx</code> 文件,把引用全局计数状态的放到此组件中。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> { useStore } <span class="keyword">from</span> <span class="string">'./store'</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Footer</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [state] = useStore();</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'footer udpate'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <p>{state.count}<<span class="regexp">/p></span></span><br><span class="line"><span class="regexp"> );</span></span><br><span class="line"><span class="regexp">}</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">export default Footer;</span></span><br></pre></td></tr></table></figure><p>创建 <code>App.jsx</code> 文件,用 <code><StoreProvider /></code> 组件包装 <code><Header /></code> 与 <code><Footer /></code> 组件。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> Header <span class="keyword">from</span> <span class="string">'./Header'</span>;</span><br><span class="line"><span class="keyword">import</span> Footer <span class="keyword">from</span> <span class="string">'./Footer'</span>;</span><br><span class="line"><span class="keyword">import</span> { StoreProvider } <span class="keyword">from</span> <span class="string">'./store'</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <StoreProvider></span><br><span class="line"> <div></span><br><span class="line"> <Header /></span><br><span class="line"> <Footer /></span><br><span class="line"> <<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> </</span>StoreProvider></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> App;</span><br></pre></td></tr></table></figure><p>这样我们就实现了全局 Store,在需要使用全局状态的地方调用 <code>useStore()</code> 就可以使用状态以及更改状态。为了方便查看引用 <code>useStore()</code> hook 的组件的更新状况,我们把更新行为放到了 <code><Header /></code> 组件中,把引用计数的放到了 <code><Footer /></code> 组件中。这里放了一个演示窗口。</p><iframe src="https://codesandbox.io/embed/state-management-for-react-fk6jz?expanddevtools=1&fontsize=14&hidenavigation=1&theme=dark" width="100%" height="500" frameborder="0" loading="lazy" allowfullscreen></iframe><h2 id="全局状态优化"><a href="#全局状态优化" class="headerlink" title="全局状态优化"></a>全局状态优化</h2><h3 id="性能问题排查"><a href="#性能问题排查" class="headerlink" title="性能问题排查"></a>性能问题排查</h3><p>在上方演示中点击 <code>+</code> 按钮并注意控制台的打印,会有以下输出,其中前两个是组件初始化所打印的,后两个是我们点击 <code>+</code> 号按钮打印的,为了方便查看我在它们中间加了个换行,思考以下有什么性能问题呢?</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">header udpate</span><br><span class="line">footer udpate</span><br><span class="line"></span><br><span class="line">header udpate</span><br><span class="line">footer udpate</span><br></pre></td></tr></table></figure><p>在 <code><Header /></code> 组件中我们并没有使用 <code>state</code> 状态,只是使用了更新方法 <code>dispatch</code> 而已,但是当状态更新时 <code><Header /></code> 组件依然执行了重绘,当我们每次点击 <code>+</code>、<code>-</code> 按钮时 <code><Header /></code> 组件都会重绘,但是实际上这个重绘显然是不需要的。</p><p>在实际开发中,我们可能会在很多组件中使用 <code>const [, dispatch] = useStore()</code> 这种方式,只是使用了 <code>useStore()</code> 的 <code>dispatch</code> 方法,React 的机制是只要有组件调用了 <code>useStore()</code> 钩子,<code>state</code> 变化时此组件都会重绘,和是否使用 <code>state</code> 没有关系,这样我们的很多只引用了 <code>dispatch</code> 方法的组件都会执行重绘,引用的组件越多重绘计算就变得越是非常的浪费,那怎么解决呢?</p><h3 id="减少不必要的组件重绘"><a href="#减少不必要的组件重绘" class="headerlink" title="减少不必要的组件重绘"></a>减少不必要的组件重绘</h3><p><code>useStore()</code> 方法是我们为了方便调用封装的一个钩子,它的背后执行的是 <code>useContext(Context)</code>,也就是每当 <code><Context.Provider value={[state, dispatch]} /></code> 的 <code>value</code> 变化时,就会重绘对应引用 <code>useContext(Context)</code> 钩子的组件,知道了原因接下来就是解决问题了。</p><p>既然 <code>[state, dispatch]</code> 并不一定会一块使用,但会一块更新,那我们就把 <code><Context.Provider value={[state, dispatch]} /></code> 拆分成两个 <code>Context</code> 就能解决此问题,一个 <code><StateContext.Provider value={state} /></code>,另一个为 <code><DispatchContext.Provider value={dispatch} /></code>,然后分别封装 <code>useStateStore()</code> 与 <code>useDispatchStore()</code> 钩子,这样的话 <code>state</code> 变动时只调用 <code>useDispatchStore()</code> 钩子的组件并不会做多余的重绘,具体优化如下。</p><p>编辑 <code>store.jsx</code> 文件。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"> import React, { createContext, useReducer, useContext } from 'react';</span><br><span class="line"></span><br><span class="line"> const initialState = {count: 0};</span><br><span class="line"></span><br><span class="line"> function reducer(state, action) {</span><br><span class="line"> switch (action.type) {</span><br><span class="line"> case 'increment':</span><br><span class="line"> return {count: state.count + 1};</span><br><span class="line"> case 'decrement':</span><br><span class="line"> return {count: state.count - 1};</span><br><span class="line"> default:</span><br><span class="line"> throw new Error();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="deletion">- const Context = createContext();</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">- function useStore() {</span></span><br><span class="line"><span class="deletion">- return useContext(Context);</span></span><br><span class="line"><span class="deletion">- }</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+ const StateContext = createContext();</span></span><br><span class="line"><span class="addition">+ const DispatchContext = createContext();</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+ function useStateStore() {</span></span><br><span class="line"><span class="addition">+ return useContext(StateContext);</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+ function useDispatchStore() {</span></span><br><span class="line"><span class="addition">+ return useContext(DispatchContext);</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"></span><br><span class="line"> function StoreProvider({ children }) {</span><br><span class="line"> const [state, dispatch] = useReducer(reducer, initialState);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"><span class="deletion">- <Context.Provider value={[state, dispatch]}></span></span><br><span class="line"><span class="deletion">- {children}</span></span><br><span class="line"><span class="deletion">- </Context.Provider></span></span><br><span class="line"><span class="addition">+ <StateContext.Provider value={state}></span></span><br><span class="line"><span class="addition">+ <DispatchContext.Provider value={dispatch}></span></span><br><span class="line"><span class="addition">+ {children}</span></span><br><span class="line"><span class="addition">+ </DispatchContext.Provider></span></span><br><span class="line"><span class="addition">+ </StateContext.Provider></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="deletion">- export { useStore, StoreProvider };</span></span><br><span class="line"><span class="addition">+ export { useStateStore, useDispatchStore, StoreProvider };</span></span><br></pre></td></tr></table></figure><p>修改 <code>Header.jsx</code> 文件,只调用 <code>useDispatchStore()</code> 钩子。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> import React from 'react';</span><br><span class="line"><span class="deletion">- import { useStore } from './store';</span></span><br><span class="line"><span class="addition">+ import { useDispatchStore } from './store';</span></span><br><span class="line"></span><br><span class="line"> function Header() {</span><br><span class="line"><span class="deletion">- const [/* state */, dispatch] = useStore();</span></span><br><span class="line"><span class="addition">+ const dispatch = useDispatchStore();</span></span><br><span class="line"> console.log('header udpate');</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <button onClick={() => dispatch({type: 'decrement'})}>-</button></span><br><span class="line"> <button onClick={() => dispatch({type: 'increment'})}>+</button></span><br><span class="line"> </></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> export default Header;</span><br></pre></td></tr></table></figure><p>修改 <code>Footer.jsx</code> 文件,只调用 <code>useStateStore()</code> 钩子。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"> import React from 'react';</span><br><span class="line"><span class="deletion">- import { useStore } from './store';</span></span><br><span class="line"><span class="addition">+ import { useStateStore } from './store';</span></span><br><span class="line"></span><br><span class="line"> function Footer() {</span><br><span class="line"><span class="deletion">- const [state] = useStore();</span></span><br><span class="line"><span class="addition">+ const state = useStateStore();</span></span><br><span class="line"> console.log('footer udpate');</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <p>{state.count}</p></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> export default Footer;</span><br></pre></td></tr></table></figure><p>当我们再次运行时点击 <code>+</code>、<code>-</code> 按钮只会重绘引用 <code>useStateStore()</code> 的组件,而引用 <code>useDispatchStore()</code> 的组件则不会跟随重绘,效果如下。</p><iframe src="https://codesandbox.io/embed/state-management-and-performance-optimization-for-react-m26tf?expanddevtools=1&fontsize=14&hidenavigation=1&theme=dark" width="100%" height="500" frameborder="0" loading="lazy" allowfullscreen></iframe><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>如果你们的项目直接使用 Context 和 Hooks 实现全局状态管理的话可以试下这个优化点,在实际开发中能为我们省下无数根头发。</p><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>我们知道 React.js 默认没有全局状态的概念,需要安装第三方库来实现,最早的是流行的是 Facebook 自己出的 <a href="https://github.com/facebook/flux" target="_blank" rel="noopener">Flux</a>,因为 Flux 使用流程有点复杂,后来 <a href="https://github.com/reduxjs/redux" target="_blank" rel="noopener">Redux</a>、<a href="https://github.com/mobxjs/mobx" target="_blank" rel="noopener">MobX</a> 就兴起了。Redux 是借鉴 Flux 开发的,它们都是单向数据流,而 MobX 则有所不同,它是基于观察者模式实现。</p><p>虽然默认没有全局状态管理,但是也可以通过 <code>Context</code> 特性拼凑出来一个,那为啥以前没人拼凑一个出来用呢?那是因为 React.js 以前的 <code>Context</code> 不好用,也不稳定,官方不建议使用,所以一般是特殊情况非得用不可的时候才使用它,但是现在时过境迁,当初那个不成熟的 <code>Context</code> 现在已经变得强壮有力了。</p><p>在去年二月 React.js 发布了一个大的版本更新 <a href="https://github.com/facebook/react/blob/master/CHANGELOG.md#1680-february-6-2019" target="_blank" rel="noopener">v16.8.0</a> 加入了 hooks 功能,其中内置了 <code>useReducer()</code> hook,它是 <code>useState()</code> 的替代品,简单的状态可以直接使用 <code>useState</code>,当我们遇到复杂多层级的状态或者下个状态要依赖上个状态时使用 <code>useReducer()</code> 则非常方便,在配合 <code>Context</code> 与 <code>useContext()</code> 就能实现类似 Redux 库的功能。</p>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="React.js" scheme="https://sanonz.github.io/tags/React-js/"/>
</entry>
<entry>
<title>使用 Phonegap + Cordova 搭建混合开发平台</title>
<link href="https://sanonz.github.io/2020/make-hybrid-platform-cordova/"/>
<id>https://sanonz.github.io/2020/make-hybrid-platform-cordova/</id>
<published>2020-06-10T22:20:53.000Z</published>
<updated>2024-01-24T02:42:46.295Z</updated>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>使用 Cordova 工具,把 HTML5 打包为 IOS 及 Android 等应用,Cordova 提供了一组设备相关的 API,通过这组 API,移动应用能够用 JavaScript 访问原生的设备功能,如摄像头、麦克风等。Cordova 支持多个操作系统,例如:ios、android、brower、electron、oxs、windows。</p><h2 id="作者所使用的环境"><a href="#作者所使用的环境" class="headerlink" title="作者所使用的环境"></a>作者所使用的环境</h2><ol><li>macOS — High Sierra 10.15.5</li><li>xcode — v11.5.0</li><li>Android Studio — v4.0.0</li><li>Node.js — v14.2.0</li><li>Phonegap — v9.0.0</li><li>Apache Cordova — v9.0.0</li></ol><a id="more"></a><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><h3 id="Cordova"><a href="#Cordova" class="headerlink" title="Cordova"></a>Cordova</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install cordova -g</span><br></pre></td></tr></table></figure><h3 id="Phonegap"><a href="#Phonegap" class="headerlink" title="Phonegap"></a>Phonegap</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install phonegap -g</span><br></pre></td></tr></table></figure><h3 id="Android-SDK"><a href="#Android-SDK" class="headerlink" title="Android SDK"></a>Android SDK</h3><p>安装 <a href="https://developer.android.com/studio/index.html" target="_blank" rel="noopener">Android Studio</a>,建议使用默认安装,装好之后启动软件,需要根据项目所安装的 <a href="https://www.npmjs.com/package/cordova-android/v/10.1.1" target="_blank" rel="noopener">cordova-android</a> 版本不同进行不同的设置。</p><p>主要关注四个设置点:</p><ol><li>JDK</li><li>Gradle</li><li>Android Platform SDK</li><li>Android SDK build-tools</li></ol><p>这四个地方版本对应上了就可以了,在这里可以查看需要设置成什么版本:<a href="https://cordova.apache.org/docs/en/10.x/guide/platforms/android/index.html" target="_blank" rel="noopener">https://cordova.apache.org/docs/en/10.x/guide/platforms/android/index.html</a> 注意 URL 的 <code>10.x</code>,这个就是 <code>cordova-android</code> 的版本,进入到页面右上角可以切换版本,根据你项目安装的版本,在这个页面中找到这四个地方所需要包含的版本,<code>10.x</code> 所包含的版本如下:</p><ol><li>JDK: 8 (1.8.*)</li><li>Gradle: 6.4.0</li><li>Android Platform SDK: 29 (Q)</li><li>Android SDK build-tools: 29.0.2</li></ol><p>接下来将一一详细介绍怎么设置</p><h4 id="JDK-版本设置"><a href="#JDK-版本设置" class="headerlink" title="JDK 版本设置"></a>JDK 版本设置</h4><p>打开 <code>Android Studio > Preferences... > Build, Execution, Deployment > Build Tools > Gradle</code> 找到 <code>Gradle JDK:</code> 选择框,点击选择 <code>Download JDK</code> 进行设置版本,比如我们现在用的 <code>10.x</code> 的 <code>JDK</code> 依赖是 <code>JDK 8</code>,那么 <code>Version:</code> 这里就需要选择 <code>1.8</code> 版本(这里说下 <code>JDK 7</code> 就对应 <code>1.7</code>,<code>8</code> 就对应 <code>1.8</code>),<code>Vendor:</code> 默认就好,<code>Location:</code> 这里复制出来拼接 <code>/Contents/Home</code> 加到环境变量,详细请看下方 <a href="#设置环境变量">设置环境变量</a> <code>Java</code> 注释部分</p><h4 id="Gradle-版本设置"><a href="#Gradle-版本设置" class="headerlink" title="Gradle 版本设置"></a>Gradle 版本设置</h4><p>打开 <code>File > Project Structure > Project</code>,<code>Android Gradle Plugin Version</code> 选择 <code>4.0.0</code>,<code>Gradle Version</code> 选择 <code>6.4</code>,点击确认等待下载完成</p><h4 id="Android-Platform-SDK-版本设置"><a href="#Android-Platform-SDK-版本设置" class="headerlink" title="Android Platform SDK 版本设置"></a>Android Platform SDK 版本设置</h4><p>打开 <code>Android Studio > Preferences > Appearance & Behavior > System Settings > Android SDK</code>,选择 <code>SDK Platforms</code> 标签,勾选对应的 <a href="https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels" target="_blank" rel="noopener">API Level</a> 来安装 Cordova 所依赖的 SDK,最新的 Cordova 所支持的 API Level 版本为 29,具体 Cordova 版本所对应的 SDK 可看下表,在你看到这篇文章时,当前表格可能已经过时,可以到 <a href="https://cordova.apache.org/docs/en/10.x/guide/platforms/android/index.html#requirements-and-support" target="_blank" rel="noopener">官方网站</a> 查看最新表格,查看表格时不要忘记选择你项目对应的版本。</p><table><thead><tr><th>Cordova 版本</th><th>API Level 版本</th><th>对应 Android 版本</th></tr></thead><tbody><tr><td>9.X.X</td><td>22 - 29</td><td>5.1 - 10.0.0</td></tr><tr><td>8.X.X</td><td>19 - 28</td><td>4.4 - 9.0.0</td></tr><tr><td>7.X.X</td><td>19 - 27</td><td>4.4 - 8.1</td></tr><tr><td>6.X.X</td><td>16 - 26</td><td>4.1 - 8.0.0</td></tr><tr><td>5.X.X</td><td>14 - 23</td><td>4.0 - 6.0.1</td></tr><tr><td>4.1.X</td><td>14 - 22</td><td>4.0 - 5.1</td></tr><tr><td>4.0.X</td><td>10 - 22</td><td>2.3.3 - 5.1</td></tr><tr><td>3.7.X</td><td>10 - 21</td><td>2.3.3 - 5.0.2</td></tr></tbody></table><h4 id="Android-SDK-build-tools-版本设置"><a href="#Android-SDK-build-tools-版本设置" class="headerlink" title="Android SDK build-tools 版本设置"></a>Android SDK build-tools 版本设置</h4><p>打开 <code>Android Studio > Preferences > Appearance & Behavior > System Settings > Android SDK</code>,选择 <code>SDK Tools</code> 标签,勾选最下方 <code>Show Package Details</code> 复选框,展开 <code>Android SDK Build-Tools</code>,勾选 <code>29.0.2</code> 版本</p><h3 id="设置环境变量"><a href="#设置环境变量" class="headerlink" title="设置环境变量"></a>设置环境变量</h3><p>在 <code>~/.bash_profile</code> 或者 <code>~/.zshenv</code> 文件中追加以下内容。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Java</span></span><br><span class="line"><span class="built_in">export</span> JAVA_HOME=<span class="variable">${HOME}</span>/Library/Java/JavaVirtualMachines/corretto-1.8.0_312/Contents/Home</span><br><span class="line"></span><br><span class="line"><span class="comment"># Gradle</span></span><br><span class="line"><span class="built_in">export</span> PATH=<span class="variable">${PATH}</span>:<span class="variable">${HOME}</span>/.gradle/wrapper/dists/gradle-6.4-bin/aj6cyggqps6mdbpl6cfppfwqk/gradle-6.4/bin</span><br><span class="line"></span><br><span class="line"><span class="comment"># Android</span></span><br><span class="line"><span class="built_in">export</span> ANDROID_SDK_ROOT=<span class="variable">${HOME}</span>/Library/Android/sdk</span><br><span class="line"><span class="built_in">export</span> PATH=<span class="variable">${PATH}</span>:<span class="variable">${JAVA_HOME}</span>/bin:<span class="variable">${ANDROID_SDK_ROOT}</span>/emulator:<span class="variable">${ANDROID_SDK_ROOT}</span>/platform-tools:<span class="variable">${ANDROID_SDK_ROOT}</span>/tools</span><br></pre></td></tr></table></figure><h3 id="检查环境"><a href="#检查环境" class="headerlink" title="检查环境"></a>检查环境</h3><p>确保 <code>Java JDK</code>、<code>Android SDK</code>、<code>Android target</code>、<code>Gradle</code> 是正常的。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">$ cordova requirements</span><br><span class="line"></span><br><span class="line">Requirements check results <span class="keyword">for</span> android:</span><br><span class="line">Java JDK: installed 1.8.0</span><br><span class="line">Android SDK: installed <span class="literal">true</span></span><br><span class="line">Android target: installed android-29,android-28,android-26</span><br><span class="line">Gradle: installed /usr/<span class="built_in">local</span>/Cellar/gradle/6.4/bin/gradle</span><br><span class="line"></span><br><span class="line">Requirements check results <span class="keyword">for</span> browser:</span><br><span class="line"></span><br><span class="line">Requirements check results <span class="keyword">for</span> ios:</span><br><span class="line">Apple macOS: installed darwin</span><br><span class="line">Xcode: installed 11.5</span><br><span class="line">ios-deploy: not installed </span><br><span class="line">ios-deploy was not found. Please download, build and install version 1.9.2 or greater from https://github.com/ios-control/ios-deploy into your path, or <span class="keyword">do</span> <span class="string">'npm install -g ios-deploy'</span></span><br><span class="line">CocoaPods: installed 1.9.1</span><br><span class="line">Some of requirements check failed</span><br></pre></td></tr></table></figure><h2 id="创建一个项目"><a href="#创建一个项目" class="headerlink" title="创建一个项目"></a>创建一个项目</h2><h3 id="创建-Hello-项目"><a href="#创建-Hello-项目" class="headerlink" title="创建 Hello 项目"></a>创建 Hello 项目</h3><p>执行以下命令创建一个 Cordova 项目。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ cordova create hello com.example.hello HelloWorld</span><br></pre></td></tr></table></figure><p>默认创建的项目只支持 WEB 端,资源放在项目下的 <code>www</code> 目录,主页文件在 <code>www/index.html</code>。</p><h3 id="添加平台"><a href="#添加平台" class="headerlink" title="添加平台"></a>添加平台</h3><p>进入到项目目录。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> hello</span><br></pre></td></tr></table></figure><p>添加 IOS 与 Android 平台支持。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cordova platform add ios</span><br><span class="line">$ cordova platform add android</span><br></pre></td></tr></table></figure><blockquote><p>添加的平台在 <code>platforms</code> 目录下,到此目录下你可以看到多了两个刚刚添加的 <code>ios</code> 与 <code>android</code> 目录。此目录为自动生成,官方不建议修改此目录内容,因为当执行构建 APP 命令或者删除平台在重新添加时,此目录会重新生成,你修改的内容将会丢失。</p></blockquote><p>查看当前已经添加过的平台列表。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ cordova platform ls</span><br><span class="line">Installed platforms:</span><br><span class="line"> android 8.1.0</span><br><span class="line"> browser 6.0.0</span><br><span class="line"> ios 5.1.1</span><br><span class="line">Available platforms: </span><br><span class="line"> electron ^1.0.0</span><br><span class="line"> osx ^5.0.0</span><br><span class="line"> windows ^7.0.0</span><br></pre></td></tr></table></figure><h3 id="添加签名"><a href="#添加签名" class="headerlink" title="添加签名"></a>添加签名</h3><h4 id="创建签名配置文件"><a href="#创建签名配置文件" class="headerlink" title="创建签名配置文件"></a>创建签名配置文件</h4><p>创建 <code>signing</code> 目录,在此目录下新建 <code>config.json</code> 文件,内容为以下格式。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"ios"</span>: {</span><br><span class="line"> <span class="attr">"debug"</span>: {</span><br><span class="line"> <span class="attr">"codeSignIdentity"</span>: <span class="string">"iPhone Developer"</span>,</span><br><span class="line"> <span class="attr">"developmentTeam"</span>: <span class="string">"FG35JLLMXX4A"</span>,</span><br><span class="line"> <span class="attr">"packageType"</span>: <span class="string">"development"</span>,</span><br><span class="line"> <span class="attr">"automaticProvisioning"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"buildFlag"</span>: [</span><br><span class="line"> <span class="string">"EMBEDDED_CONTENT_CONTAINS_SWIFT = YES"</span>,</span><br><span class="line"> <span class="string">"ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO"</span>,</span><br><span class="line"> <span class="string">"LD_RUNPATH_SEARCH_PATHS = \"@executable_path/Frameworks\""</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"release"</span>: {</span><br><span class="line"> <span class="attr">"codeSignIdentity"</span>: <span class="string">"iPhone Developer"</span>,</span><br><span class="line"> <span class="attr">"developmentTeam"</span>: <span class="string">"FG35JLLMXX4A"</span>,</span><br><span class="line"> <span class="attr">"packageType"</span>: <span class="string">"app-store"</span>,</span><br><span class="line"> <span class="attr">"automaticProvisioning"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"buildFlag"</span>: [</span><br><span class="line"> <span class="string">"EMBEDDED_CONTENT_CONTAINS_SWIFT = YES"</span>,</span><br><span class="line"> <span class="string">"ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO"</span>,</span><br><span class="line"> <span class="string">"LD_RUNPATH_SEARCH_PATHS = \"@executable_path/Frameworks\""</span></span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"android"</span>: {</span><br><span class="line"> <span class="attr">"debug"</span>: {</span><br><span class="line"> <span class="attr">"keystore"</span>: <span class="string">"./android.keystore"</span>,</span><br><span class="line"> <span class="attr">"storePassword"</span>: <span class="string">"sanonz"</span>,</span><br><span class="line"> <span class="attr">"alias"</span>: <span class="string">"sanonz"</span>,</span><br><span class="line"> <span class="attr">"password"</span> : <span class="string">"sanonz"</span>,</span><br><span class="line"> <span class="attr">"keystoreType"</span>: <span class="string">"jks"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"release"</span>: {</span><br><span class="line"> <span class="attr">"keystore"</span>: <span class="string">"./android.keystore"</span>,</span><br><span class="line"> <span class="attr">"storePassword"</span>: <span class="string">"sanonz"</span>,</span><br><span class="line"> <span class="attr">"alias"</span>: <span class="string">"sanonz"</span>,</span><br><span class="line"> <span class="attr">"password"</span> : <span class="string">"sanonz"</span>,</span><br><span class="line"> <span class="attr">"keystoreType"</span>: <span class="string">"jks"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="IOS-签名"><a href="#IOS-签名" class="headerlink" title="IOS 签名"></a>IOS 签名</h4><p>注册一个苹果开发着账号,配置 <code>ios.{env}.developmentTeam</code> 为你的 <a href="https://developer.apple.com/account/#/membership/" target="_blank" rel="noopener">Team ID</a>。</p><h4 id="Android-签名"><a href="#Android-签名" class="headerlink" title="Android 签名"></a>Android 签名</h4><p>执行以下命令,密码使用 <code>sanonz</code>,当使用自己的密码时需要对应更改 <code>android.{env}.password</code> 与 <code>android.storePassword</code> 密码,同理别名 <code>-alias</code> 也需要对应修改。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ keytool -genkey -v -keystore android.keystore -keyalg RSA -keysize 2048 -validity 36500 -<span class="built_in">alias</span> sanonz</span><br></pre></td></tr></table></figure><p>其中 <code>android.{env}.keystore</code> 填写生成的 <code>android.keystore</code> 文件路径。</p><h3 id="构建-APP"><a href="#构建-APP" class="headerlink" title="构建 APP"></a>构建 APP</h3><p>构建所有添加的平台。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ cordova build</span><br></pre></td></tr></table></figure><p>构建指定平台。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ cordova build ios</span><br></pre></td></tr></table></figure><p>构建 debug 版本。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ cordova build android --verbose --device --debug --buildConfig=./signing/config.json</span><br></pre></td></tr></table></figure><p>构建成功后的 apk 文件在 <code>platforms/android/app/build/outputs/apk/debug/app-debug.apk</code>。</p><p>构建 release 版本。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ cordova build android --verbose --device --release --buildConfig=./signing/config.json</span><br></pre></td></tr></table></figure><p>构建成功后的 apk 文件在 <code>platforms/android/app/build/outputs/apk/release/app-release.apk</code>。</p><h3 id="测试-APP"><a href="#测试-APP" class="headerlink" title="测试 APP"></a>测试 APP</h3><p>以下命令会构建一个 debug 版本的 APP,自动打开模拟器并安装到模拟器上运行。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ cordova run android</span><br></pre></td></tr></table></figure><h3 id="Live-Reload"><a href="#Live-Reload" class="headerlink" title="Live Reload"></a>Live Reload</h3><p>利用 Phonegap 的应用,可以实时预览你的 APP,首先安装 Phonegap 的 <a href="http://docs.phonegap.com/getting-started/2-install-mobile-app/" target="_blank" rel="noopener">APP</a>,由于 AppStore 在 2018 年下架了 Phonegap APP,所以直接安装不了,可以克隆官方的 <a href="https://github.com/phonegap/phonegap-app-developer" target="_blank" rel="noopener">Phonegap 仓库</a> 自行编译安装,安装到手机有点麻烦,不过可以很容易安装的模拟器上,在模拟器上打开 Phonegap APP 一样可以方便的预览。</p><p>启动调试服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ phonegap serve</span><br><span class="line">[phonegap] starting app server...</span><br><span class="line">[phonegap] listening on 192.168.1.103:3000</span><br><span class="line">[phonegap] </span><br><span class="line">[phonegap] ctrl-c to stop the server</span><br></pre></td></tr></table></figure><p>启动后控制台会打印 IP 地址,默认端口为 <code>3000</code>,打开 Phonegap APP,在 <code>Server Address</code> 输入框中输入控制台打印的 <code>192.168.1.103:3000</code> 地址,然后点击 <code>Connect</code> 链接按钮,连接成功后就可以查看你的 APP,修改 <code>wwww/index.html</code> 文件,保存后 Phonegap APP 会自动刷新,展示修改后的页面。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>使用 Cordova 工具,把 HTML5 打包为 IOS 及 Android 等应用,Cordova 提供了一组设备相关的 API,通过这组 API,移动应用能够用 JavaScript 访问原生的设备功能,如摄像头、麦克风等。Cordova 支持多个操作系统,例如:ios、android、brower、electron、oxs、windows。</p><h2 id="作者所使用的环境"><a href="#作者所使用的环境" class="headerlink" title="作者所使用的环境"></a>作者所使用的环境</h2><ol><li>macOS — High Sierra 10.15.5</li><li>xcode — v11.5.0</li><li>Android Studio — v4.0.0</li><li>Node.js — v14.2.0</li><li>Phonegap — v9.0.0</li><li>Apache Cordova — v9.0.0</li></ol>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="Cordov" scheme="https://sanonz.github.io/tags/Cordov/"/>
<category term="Phonegap" scheme="https://sanonz.github.io/tags/Phonegap/"/>
</entry>
<entry>
<title>利用 Github Actions 自动部署 Hexo 博客</title>
<link href="https://sanonz.github.io/2020/deploy-a-hexo-blog-from-github-actions/"/>
<id>https://sanonz.github.io/2020/deploy-a-hexo-blog-from-github-actions/</id>
<published>2020-05-12T12:35:42.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>Github Actions 可以很方便实现 CI/CD 工作流,类似 Travis 的用法,来帮我们完成一些工作,比如实现自动化测试、打包、部署等操作。当我们运行 Jobs 时,它会创建一个容器 (runner),容器支持:Ubuntu、Windows 和 MacOS 等系统,在容器中我们可以安装软件,利用安装的软件帮我们处理一些数据,然后把处理好的数据推送到某个地方。</p><p>本文将介绍利用 Github Actions 实现自动部署 hexo 到 Github Pages,在之前我们需要写完文章执行 <code>hexo generate --deploy</code> 来部署,当你文章比较多的时候,可能还需要等待很久,而且还可能会遇到本地安装的 Node.js 版本与 Hexo 不兼容的问题,目前我就是因为电脑的 Node.js 版本升到 v14 版本导致与 Hexo 不兼容部署不了,才来捣腾 Github Actions 功能的。利用 Github Actions 你将会没有这些烦恼。</p><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><h3 id="创建所需仓库"><a href="#创建所需仓库" class="headerlink" title="创建所需仓库"></a>创建所需仓库</h3><ol><li>创建 <code>blog</code> 仓库用来存放 Hexo 项目</li><li>创建 <code>your.github.io</code> 仓库用来存放静态博客页面</li></ol><a id="more"></a><h3 id="生成部署密钥"><a href="#生成部署密钥" class="headerlink" title="生成部署密钥"></a>生成部署密钥</h3><p>一路按回车直到生成成功</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -f github-deploy-key</span><br></pre></td></tr></table></figure><p>当前目录下会有 <code>github-deploy-key</code> 和 <code>github-deploy-key.pub</code> 两个文件。</p><h3 id="配置部署密钥"><a href="#配置部署密钥" class="headerlink" title="配置部署密钥"></a>配置部署密钥</h3><p><del>复制 <code>github-deploy-key</code> 文件内容,在 <code>blog</code> 仓库 <code>Settings -> Secrets -> Add a new secret</code> 页面上添加。(旧版本)</del></p><p>复制 <code>github-deploy-key</code> 文件内容,在 <code>blog</code> 仓库 <code>Settings -> Secrets and variables -> Actions -> New repository secret</code> 页面上添加。(2024-01-16 更新)</p><ol><li>在 <code>Name</code> 输入框填写 <code>HEXO_DEPLOY_PRI</code>。</li><li>在 <code>Value</code> 输入框填写 <code>github-deploy-key</code> 文件内容。</li></ol><img src="/2020/deploy-a-hexo-blog-from-github-actions/[email protected]" width="800"><p>复制 <code>github-deploy-key.pub</code> 文件内容,在 <code>your.github.io</code> 仓库 <code>Settings -> Deploy keys -> Add deploy key</code> 页面上添加。</p><ol><li>在 <code>Title</code> 输入框填写 <code>HEXO_DEPLOY_PUB</code>。</li><li>在 <code>Key</code> 输入框填写 <code>github-deploy-key.pub</code> 文件内容。</li><li>勾选 <code>Allow write access</code> 选项。</li></ol><img src="/2020/deploy-a-hexo-blog-from-github-actions/[email protected]" width="800"><h2 id="编写-Github-Actions"><a href="#编写-Github-Actions" class="headerlink" title="编写 Github Actions"></a>编写 Github Actions</h2><h3 id="Workflow-模版"><a href="#Workflow-模版" class="headerlink" title="Workflow 模版"></a>Workflow 模版</h3><p>在 <code>blog</code> 仓库根目录下创建 <code>.github/workflows/deploy.yml</code> 文件,目录结构如下。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">blog (repository)</span><br><span class="line">└── .github</span><br><span class="line"> └── workflows</span><br><span class="line"> └── deploy.yml</span><br></pre></td></tr></table></figure><p>在 <code>deploy.yml</code> 文件中粘贴以下内容。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">CI</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="attr">push:</span></span><br><span class="line"> <span class="attr">branches:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">master</span></span><br><span class="line"></span><br><span class="line"><span class="attr">env:</span></span><br><span class="line"> <span class="attr">GIT_USER:</span> <span class="string">Sanonz</span></span><br><span class="line"> <span class="attr">GIT_EMAIL:</span> <span class="string">[email protected]</span></span><br><span class="line"> <span class="attr">THEME_REPO:</span> <span class="string">sanonz/hexo-theme-concise</span></span><br><span class="line"> <span class="attr">THEME_BRANCH:</span> <span class="string">master</span></span><br><span class="line"> <span class="attr">DEPLOY_REPO:</span> <span class="string">sanonz/sanonz.github.io</span></span><br><span class="line"> <span class="attr">DEPLOY_BRANCH:</span> <span class="string">master</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">build:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">on</span> <span class="string">node</span> <span class="string">${{</span> <span class="string">matrix.node_version</span> <span class="string">}}</span> <span class="string">and</span> <span class="string">${{</span> <span class="string">matrix.os</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">strategy:</span></span><br><span class="line"> <span class="attr">matrix:</span></span><br><span class="line"> <span class="attr">os:</span> <span class="string">[ubuntu-latest]</span></span><br><span class="line"> <span class="attr">node_version:</span> <span class="string">[16.x]</span></span><br><span class="line"></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">theme</span> <span class="string">repo</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">repository:</span> <span class="string">${{</span> <span class="string">env.THEME_REPO</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">ref:</span> <span class="string">${{</span> <span class="string">env.THEME_BRANCH</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">path:</span> <span class="string">themes/concise</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">deploy</span> <span class="string">repo</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">repository:</span> <span class="string">${{</span> <span class="string">env.DEPLOY_REPO</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">ref:</span> <span class="string">${{</span> <span class="string">env.DEPLOY_BRANCH</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">path:</span> <span class="string">.deploy_git</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Use</span> <span class="string">Node.js</span> <span class="string">${{</span> <span class="string">matrix.node_version</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/setup-node@v4</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">node-version:</span> <span class="string">${{</span> <span class="string">matrix.node_version</span> <span class="string">}}</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configuration</span> <span class="string">environment</span></span><br><span class="line"> <span class="attr">env:</span></span><br><span class="line"> <span class="attr">HEXO_DEPLOY_PRI:</span> <span class="string">${{secrets.HEXO_DEPLOY_PRI}}</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"> <span class="string">sudo</span> <span class="string">timedatectl</span> <span class="string">set-timezone</span> <span class="string">"Asia/Shanghai"</span></span><br><span class="line"> <span class="string">mkdir</span> <span class="string">-p</span> <span class="string">~/.ssh/</span></span><br><span class="line"> <span class="string">echo</span> <span class="string">"$HEXO_DEPLOY_PRI"</span> <span class="string">></span> <span class="string">~/.ssh/id_rsa</span></span><br><span class="line"> <span class="string">chmod</span> <span class="number">600</span> <span class="string">~/.ssh/id_rsa</span></span><br><span class="line"> <span class="string">ssh-keyscan</span> <span class="string">github.com</span> <span class="string">>></span> <span class="string">~/.ssh/known_hosts</span></span><br><span class="line"> <span class="string">git</span> <span class="string">config</span> <span class="string">--global</span> <span class="string">user.name</span> <span class="string">$GIT_USER</span></span><br><span class="line"> <span class="string">git</span> <span class="string">config</span> <span class="string">--global</span> <span class="string">user.email</span> <span class="string">$GIT_EMAIL</span></span><br><span class="line"> <span class="string">cp</span> <span class="string">_config.theme.yml</span> <span class="string">themes/concise/_config.yml</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">dependencies</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">hexo</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"> <span class="string">npm</span> <span class="string">run</span> <span class="string">deploy</span></span><br></pre></td></tr></table></figure><blockquote><p>2024-01-16 更新版本<br>升级 jobs.build.strategy.matrix.node_version: [12.x] 版本为 node_version: [16.x]<br>升级 actions/checkout@2、actions/setup-node@1 版本为 actions/checkout@4、actions/setup-node@4</p></blockquote><h3 id="模版参数说明"><a href="#模版参数说明" class="headerlink" title="模版参数说明"></a>模版参数说明</h3><ul><li><em>name</em> 为此 Action 的名字</li><li><em>on</em> 触发条件,当满足条件时会触发此任务,这里的 <code>on.push.branches.$.master</code> 是指当 <code>master</code> 分支收到 <code>push</code> 后执行任务。</li><li><em>env</em> 为环境变量对象<ul><li><em>env.GIT_USER</em> 为 Hexo 编译后使用此 git 用户部署到仓库。</li><li><em>env.GIT_EMAIL</em> 为 Hexo 编译后使用此 git 邮箱部署到仓库。</li><li><em>env.THEME_REPO</em> 为您的 Hexo 所使用的主题的仓库,这里为 <code>sanonz/hexo-theme-concise</code>。</li><li><em>env.THEME_BRANCH</em> 为您的 Hexo 所使用的主题仓库的版本,可以是:branch、tag 或者 SHA。</li><li><em>env.DEPLOY_REPO</em> 为 Hexo 编译后要部署的仓库,例如:<code>sanonz/sanonz.github.io</code>。</li><li><em>env.DEPLOY_BRANCH</em> 为 Hexo 编译后要部署到的分支,例如:master。</li></ul></li><li><em>jobs</em> 为此 Action 下的任务列表<ul><li><em>jobs.{job}.name</em> 任务名称</li><li><em>jobs.{job}.runs-on</em> 任务所需容器,可选值:<code>ubuntu-latest</code>、<code>windows-latest</code>、<code>macos-latest</code>。</li><li><em>jobs.{job}.strategy</em> 策略下可以写 <code>array</code> 格式,此 job 会遍历此数组执行。</li><li><em>jobs.{job}.steps</em> 一个步骤数组,可以把所要干的事分步骤放到这里。<ul><li><em>jobs.{job}.steps.$.name</em> 步骤名,编译时会会以 LOG 形式输出。</li><li><em>jobs.{job}.steps.$.uses</em> 所要调用的 Action,可以到 <a href="https://github.com/actions" target="_blank" rel="noopener">https://github.com/actions</a> 查看更多。</li><li><em>jobs.{job}.steps.$.with</em> 一个对象,调用 Action 传的参数,具体可以查看所使用 Action 的说明。</li></ul></li></ul></li></ul><h3 id="第三方-Actions"><a href="#第三方-Actions" class="headerlink" title="第三方 Actions"></a>第三方 Actions</h3><p>使用第三方 Actions 语法 <code>{owner}/{repo}@{ref}</code> 或者 <code>{owner}/{repo}/{path}@{ref}</code> 例如:</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">build:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br></pre></td></tr></table></figure><p>一、调用 <code>actions/checkout@v4</code> 可以实现 Checkout 一个 git 仓库到容器。</p><p>例如 Checkout 当前仓库到本地,<code>with.repo</code> 不填写默认为当前仓库。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">build:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"> <span class="comment"># with:</span></span><br><span class="line"> <span class="comment"># repository: ${{ github.repository }}</span></span><br></pre></td></tr></table></figure><p>例如 Checkout 第三方仓库 <code>[email protected]:sanonz/hexo-theme-concise.git</code> 的 <code>master</code> 分支到容器 <code>themes/concise</code> 目录。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">build:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">repository:</span> <span class="string">sanonz/hexo-theme-concise</span></span><br><span class="line"> <span class="attr">ref:</span> <span class="string">master</span></span><br><span class="line"> <span class="attr">path:</span> <span class="string">themes/concise</span></span><br></pre></td></tr></table></figure><p>二、调用 <code>actions/setup-node@v4</code> 可以配置容器 Node.js 环境。</p><p>例如安装 Node.js 版本 v16 到容器中,<code>with.node-version</code> 可以指定 Node.js 版本。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">build:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/setup-node@v4</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">node-version:</span> <span class="string">v16</span></span><br></pre></td></tr></table></figure><p>可以在这里查找更多 Actions 以及使用方式 <a href="https://github.com/marketplace?type=actions&query=checkout" target="_blank" rel="noopener">官方 Actions 市场</a>。</p><h3 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h3><p>复制一份 <a href="https://github.com/sanonz/hexo-theme-concise/blob/master/_config.example.yml" target="_blank" rel="noopener">https://github.com/sanonz/hexo-theme-concise/blob/master/_config.example.yml</a>,放到 <code>blog</code> 根目录下,名为 <code>_config.theme.yml</code>,如果您已经配置过此文件,只需要把您的复制过来就行。</p><p>最终目录结构</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">blog (repository)</span><br><span class="line">├── _config.theme.yml</span><br><span class="line">└── .github</span><br><span class="line"> └── workflows</span><br><span class="line"> └── deploy.yml</span><br></pre></td></tr></table></figure><p>把 <code>_config.theme.yml</code> 与 <code>deploy.yml</code> 文件推送到 <code>blog</code> 仓库,在此仓库 <code>Actions</code> 页面可以看到一个名字为 <code>CI</code> 的 Action。</p><h3 id="执行任务"><a href="#执行任务" class="headerlink" title="执行任务"></a>执行任务</h3><p>写一篇文章,<code>push</code> 到 <code>blog</code> 仓库的 <code>master</code> 分支,在此仓库 <code>Actions</code> 页面查看当前 task。</p><img src="/2020/deploy-a-hexo-blog-from-github-actions/[email protected]" width="800"><p>当任务完成后查看您的博客 <code>https://your.github.io</code>,如果不出意外的话已经可以看到新添加的文章了。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>偷懒是人类发展的动力,人都有偷懒的想法,目的就是为了让自己能够活得更好,经过几千年的不断发展,现在人偷懒的方式无疑更加的先进。</p><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>Github Actions 可以很方便实现 CI/CD 工作流,类似 Travis 的用法,来帮我们完成一些工作,比如实现自动化测试、打包、部署等操作。当我们运行 Jobs 时,它会创建一个容器 (runner),容器支持:Ubuntu、Windows 和 MacOS 等系统,在容器中我们可以安装软件,利用安装的软件帮我们处理一些数据,然后把处理好的数据推送到某个地方。</p><p>本文将介绍利用 Github Actions 实现自动部署 hexo 到 Github Pages,在之前我们需要写完文章执行 <code>hexo generate --deploy</code> 来部署,当你文章比较多的时候,可能还需要等待很久,而且还可能会遇到本地安装的 Node.js 版本与 Hexo 不兼容的问题,目前我就是因为电脑的 Node.js 版本升到 v14 版本导致与 Hexo 不兼容部署不了,才来捣腾 Github Actions 功能的。利用 Github Actions 你将会没有这些烦恼。</p><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><h3 id="创建所需仓库"><a href="#创建所需仓库" class="headerlink" title="创建所需仓库"></a>创建所需仓库</h3><ol><li>创建 <code>blog</code> 仓库用来存放 Hexo 项目</li><li>创建 <code>your.github.io</code> 仓库用来存放静态博客页面</li></ol>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="Hexo" scheme="https://sanonz.github.io/tags/Hexo/"/>
<category term="CI" scheme="https://sanonz.github.io/tags/CI/"/>
<category term="Github Actions" scheme="https://sanonz.github.io/tags/Github-Actions/"/>
</entry>
<entry>
<title>在 WEB 端实现亮/暗主题跟随系统功能</title>
<link href="https://sanonz.github.io/2020/to-build-dark-and-light-theme-with-web/"/>
<id>https://sanonz.github.io/2020/to-build-dark-and-light-theme-with-web/</id>
<published>2020-04-02T04:14:45.000Z</published>
<updated>2024-01-24T02:42:46.295Z</updated>
<content type="html"><![CDATA[<h2 id="黑暗模式介绍"><a href="#黑暗模式介绍" class="headerlink" title="黑暗模式介绍"></a>黑暗模式介绍</h2><p>在 2018~2019 年两年时间中,桌面端系统 Windows、MacOS 和 移动端系统 Android、iPhone 分别推出了 <strong>黑暗模式</strong>。在之前不管是微软的 Windows,还是苹果的 MacOS,界面都是以亮色为主,在夜间为了防止刺眼,我门通常会把屏幕亮度调暗,虽然调暗会有所改善,但是依然避免不了有点刺眼。有个别 APP 也提供的有黑暗模式,这种功能通常要在设置里手动打开,打开后白天 APP 依然是黑暗模式,虽然个别 APP 提供的有根据时间区间来控制开关黑暗模式,但是每个支持的 APP 都要设置一遍很麻烦,况且大多还不支持根据时间调节功能,即使都设置好了,返回到系统主页的时候,系统层面还会是刺眼的亮色。而目前系统提供的黑暗模式则可以让 APP 端跟随系统,这样就保证了不管是进系统界面还是进入 APP 页面都会保持统一的亮/暗色模式。</p><h3 id="这里分别引用苹果与谷歌的介绍"><a href="#这里分别引用苹果与谷歌的介绍" class="headerlink" title="这里分别引用苹果与谷歌的介绍"></a>这里分别引用苹果与谷歌的介绍</h3><p>苹果介绍:这是一种戏剧性的新外观,可以帮助您专注于工作,以及营造一种无干扰的环境。</p><p>谷歌介绍:</p><ol><li>可大幅度减少耗电量(取决于设备屏幕,例如 OLED 屏幕)。</li><li>为弱视以及对强光敏感的用户提高可视性。</li><li>让所有人都可以在光线较暗的环境中更轻松地使用设备。</li></ol><a id="more"></a><h2 id="各个平台对黑暗模式支持的时间"><a href="#各个平台对黑暗模式支持的时间" class="headerlink" title="各个平台对黑暗模式支持的时间"></a>各个平台对黑暗模式支持的时间</h2><h3 id="系统支持"><a href="#系统支持" class="headerlink" title="系统支持"></a>系统支持</h3><ul><li>苹果的 MacOS 在 2018 年 09 月推送了一个版本为 Mojave 10.14 的更新,其中加入了黑暗模式</li><li>微软的 Win10 在 2018 年 10 月推送了一个版本为 1809 的更新,其中加入了黑暗模式</li><li>谷歌的 Android 在 2019 年 09 月发布了 Android Q,其中加入了黑暗模式</li><li>IOS 13 在 2019 年 09 月推送,其中加入了黑暗模式</li></ul><h3 id="软件支持"><a href="#软件支持" class="headerlink" title="软件支持"></a>软件支持</h3><ul><li>苹果版微信则在 2020 年 03 月 22 日推送了版本为 7.0.12,其中加入了黑暗模式</li><li>安卓版微信则在 2020 年 03 月 22 日推送了版本为 7.0.12,其中加入了黑暗模式</li></ul><p>从上边时间点可以看出:系统方面目前各大系统都已经支持 <strong>黑暗模式</strong> 了;软件适配方面,目前只有少部分软件做了黑暗模式适配。在 2018~2019 两年各大系统做了适配支持,可以预测在 2020~2021 两年主流 APP 将会适配黑暗模式。</p><h2 id="CSS-实现主题跟随系统自动切换"><a href="#CSS-实现主题跟随系统自动切换" class="headerlink" title="CSS 实现主题跟随系统自动切换"></a>CSS 实现主题跟随系统自动切换</h2><h3 id="语法规则"><a href="#语法规则" class="headerlink" title="语法规则"></a>语法规则</h3><p>利用媒体查询的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/@media/prefers-color-scheme" target="_blank" rel="noopener"><code>prefers-color-scheme</code></a> 特性检测用户是否将系统的主题色设置为亮色或者暗色。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@media</span> (<span class="attribute">prefers-color-scheme:</span> {<span class="selector-tag">mode</span>}) {</span><br><span class="line"> <span class="comment">/* CSS Rules */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中 <code>mode</code> 可选值为:dark、light、no-preference。</p><ul><li><em>dark</em> 匹配系统暗色主题</li><li><em>light</em> 匹配系统亮色主题</li><li><em>no-preference</em> 系统未得知用户在这方面的选项</li></ul><h3 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h3><p>例如以下例子展示了当系统主题为 暗色(dark) 时,覆盖 body 样式的字体色与背景色。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Document<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span></span><br><span class="line"> body {</span><br><span class="line"><span class="css"> <span class="selector-tag">color</span>: <span class="selector-id">#333</span>;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">background-color</span>: <span class="selector-id">#eee</span>;</span></span><br><span class="line"> }</span><br><span class="line"><span class="css"> <span class="keyword">@media</span> (<span class="attribute">prefers-color-scheme:</span> dark) {</span></span><br><span class="line"> body {</span><br><span class="line"><span class="css"> <span class="selector-tag">color</span>: <span class="selector-id">#ccc</span>;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">background-color</span>: <span class="selector-id">#1f1f1f</span>;</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="tag"></<span class="name">style</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> Hello Theme!</span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>同理以上效果也可以用 light 模式实现</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">body</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#ccc</span>;</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#1f1f1f</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">prefers-color-scheme:</span> light) {</span><br><span class="line"> <span class="selector-tag">body</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#333</span>;</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#eee</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="结合-CSS-变量使用"><a href="#结合-CSS-变量使用" class="headerlink" title="结合 CSS 变量使用"></a>结合 CSS 变量使用</h3><p>首先在 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/:root" target="_blank" rel="noopener"><code>:root</code></a> 下定义亮色主题配色变量,然后利用主题媒体查询特性,判断当系统为暗色主题时覆盖 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/:root" target="_blank" rel="noopener"><code>:root</code></a> 下为亮色主题配色的变量。在需要使用变量的地方调用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/var" target="_blank" rel="noopener"><code>var()</code></a> 来引用变量值。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-pseudo">:root</span> {</span><br><span class="line"> <span class="attribute">--color</span>: <span class="number">#333</span>;</span><br><span class="line"> <span class="attribute">--bg-color</span>: <span class="number">#eee</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">prefers-color-scheme:</span> dark) {</span><br><span class="line"> <span class="selector-pseudo">:root</span> {</span><br><span class="line"> <span class="attribute">--color</span>: <span class="number">#ccc</span>;</span><br><span class="line"> <span class="attribute">--bg-color</span>: <span class="number">#1f1f1f</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">body</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">var</span>(--color);</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--bg-color);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h3><p>这个 gif 动画是截取上边的代码运行的效果,你也可以在这里实时运行 <a href="https://stackblitz.com/edit/auto-theme-for-the-web?file=style.css" target="_blank" rel="noopener">点击运行</a> (如果看不到效果检查下您的浏览器是否支持)</p><img src="/2020/to-build-dark-and-light-theme-with-web/theme.gif" width="840"><h2 id="JS-主题相关操作"><a href="#JS-主题相关操作" class="headerlink" title="JS 主题相关操作"></a>JS 主题相关操作</h2><h3 id="获取当前系统主题"><a href="#获取当前系统主题" class="headerlink" title="获取当前系统主题"></a>获取当前系统主题</h3><p>利用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/matchMedia" target="_blank" rel="noopener"><code>matchMedia(mediaQueryString)</code></a> 媒体查询方法,检测当前主题状态,返回一个新的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/MediaQueryList" target="_blank" rel="noopener"><code>MediaQueryList</code></a> 对象,表示指定的媒体查询字符串解析后的结果。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> mediaQueryListDark = <span class="built_in">window</span>.matchMedia(<span class="string">'(prefers-color-scheme: dark)'</span>);</span><br><span class="line"><span class="keyword">if</span> (mediaQueryListDark.matches) {</span><br><span class="line"> <span class="comment">// 系统当前是暗色(dark)主题</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mediaQueryListLight = <span class="built_in">window</span>.matchMedia(<span class="string">'(prefers-color-scheme: light)'</span>);</span><br><span class="line"><span class="keyword">if</span> (mediaQueryListLight.matches) {</span><br><span class="line"> <span class="comment">// 系统当前是亮色(light)主题</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="监控系统主题变动"><a href="#监控系统主题变动" class="headerlink" title="监控系统主题变动"></a>监控系统主题变动</h3><p>利用媒体查询可以获取 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/MediaQueryList" target="_blank" rel="noopener"><code>MediaQueryList</code></a> 对象,该对象有 <code>addListener</code> 方法,可以利用该方法绑定事件,当主题动态改变时会触发绑定的事件,例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleChange</span> (<span class="params">mediaQueryListEvent</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (mediaQueryListEvent.matches) {</span><br><span class="line"> <span class="comment">// 用户切换到了暗色(dark)主题</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 用户切换到了亮色(light)主题</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mediaQueryListDark = <span class="built_in">window</span>.matchMedia(<span class="string">'(prefers-color-scheme: dark)'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加主题变动监控事件</span></span><br><span class="line">mediaQueryListDark.addListener(handleChange);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除主题变动监控事件</span></span><br><span class="line">mediaQueryListDark.removeListener(handleChange);</span><br></pre></td></tr></table></figure><p>测试地址 <a href="https://stackblitz.com/edit/auto-theme-for-the-web?file=index.js" target="_blank" rel="noopener">点击运行</a>,先打开控制台版面,在自己的设备系统设置里来回切换系统主题,然后去控制台查看打印的日志。</p><h2 id="浏览器支持情况"><a href="#浏览器支持情况" class="headerlink" title="浏览器支持情况"></a>浏览器支持情况</h2><p>我们在进行开发的时候先写一套默认主题色,然后用媒体查询去覆盖默认主题色,这样做不具有破坏性,当浏览器不支持时就会使用默认色,支持时会根据系统当前主题显示对应的配色。</p><img src="/2020/to-build-dark-and-light-theme-with-web/[email protected]"><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>站点增加亮/暗主题配色,能有效防止用户在烈日下看不清暗主题,以及在夜间看亮主题太刺眼的问题,可以极大的提升用户浏览体验,建议大家给自己的站点增加亮/暗主题跟随系统功能。</p><p>本博客站点也增加了暗色主题,您也可以直接用本站点来测试。</p><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="黑暗模式介绍"><a href="#黑暗模式介绍" class="headerlink" title="黑暗模式介绍"></a>黑暗模式介绍</h2><p>在 2018~2019 年两年时间中,桌面端系统 Windows、MacOS 和 移动端系统 Android、iPhone 分别推出了 <strong>黑暗模式</strong>。在之前不管是微软的 Windows,还是苹果的 MacOS,界面都是以亮色为主,在夜间为了防止刺眼,我门通常会把屏幕亮度调暗,虽然调暗会有所改善,但是依然避免不了有点刺眼。有个别 APP 也提供的有黑暗模式,这种功能通常要在设置里手动打开,打开后白天 APP 依然是黑暗模式,虽然个别 APP 提供的有根据时间区间来控制开关黑暗模式,但是每个支持的 APP 都要设置一遍很麻烦,况且大多还不支持根据时间调节功能,即使都设置好了,返回到系统主页的时候,系统层面还会是刺眼的亮色。而目前系统提供的黑暗模式则可以让 APP 端跟随系统,这样就保证了不管是进系统界面还是进入 APP 页面都会保持统一的亮/暗色模式。</p><h3 id="这里分别引用苹果与谷歌的介绍"><a href="#这里分别引用苹果与谷歌的介绍" class="headerlink" title="这里分别引用苹果与谷歌的介绍"></a>这里分别引用苹果与谷歌的介绍</h3><p>苹果介绍:这是一种戏剧性的新外观,可以帮助您专注于工作,以及营造一种无干扰的环境。</p><p>谷歌介绍:</p><ol><li>可大幅度减少耗电量(取决于设备屏幕,例如 OLED 屏幕)。</li><li>为弱视以及对强光敏感的用户提高可视性。</li><li>让所有人都可以在光线较暗的环境中更轻松地使用设备。</li></ol>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
<category term="CSS" scheme="https://sanonz.github.io/tags/CSS/"/>
</entry>
<entry>
<title>Metalness vs Specular Workflows for PBR Shading</title>
<link href="https://sanonz.github.io/2019/pbr-materials-specular-vs-metalness-workflows/"/>
<id>https://sanonz.github.io/2019/pbr-materials-specular-vs-metalness-workflows/</id>
<published>2019-09-23T07:07:00.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>随着计算机硬件以及渲染上的进步,我们可以更好的从物理上去模拟灯光属性,从而诞生了新的工作流程 PBR 工作流。PBR是一种着色和渲染技术,基于物理的渲染过程,用于更精确的描述光如何与物体表面互动。</p><p>PBR的优势:</p><ol><li>方法论和算法基于精确的计算公式,免除创作表面的猜想过程。</li><li>在任何光照环境都能表现出正确的结果。</li><li>为不同的艺术家,提供统一的工作流程。</li></ol><h2 id="PBR-工作流分类"><a href="#PBR-工作流分类" class="headerlink" title="PBR 工作流分类"></a>PBR 工作流分类</h2><p>PBR有两种工作流:</p><ol><li>Metal Roughness Worfkow(金属性/粗超度工作流程)</li><li>Specular Glossiness Worfkow(高光/光泽度工作流程)</li></ol><blockquote><p>THREE.js 使用的是第一种工作流,名字子叫做 <code>Metallic-Roughness</code>,对应材质为 <a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial" target="_blank" rel="noopener">MeshStandardMaterial</a></p></blockquote><a id="more"></a><p>通用纹理:</p><table><thead><tr><th>Name</th><th>对应 THREE.js</th><th>说明</th></tr></thead><tbody><tr><td>Ambient Occlusion</td><td><a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.aoMap" target="_blank" rel="noopener">aoMap</a></td><td>表示有多少环境光,可以被表面上的一个点吸收。</td></tr><tr><td>Normal</td><td><a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.normalMap" target="_blank" rel="noopener">normalMap</a></td><td>用于视差映射,通过增加可见的深度感来提高渲染的真实性。</td></tr><tr><td>Height</td><td><a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.displacementMap" target="_blank" rel="noopener">displacementMap</a></td><td>不论 PBR 还是非 PBR,NormalMap 都是用来模拟表面细节,同时还会影响 Roughness 和 Glossiness 映射。</td></tr></tbody></table><h2 id="Metal-Roughness-Worfkow"><a href="#Metal-Roughness-Worfkow" class="headerlink" title="Metal Roughness Worfkow"></a>Metal Roughness Worfkow</h2><p>不易出错,省内存,但是白边现象更明显。</p><h3 id="Base-Color:-RGB-sRGB"><a href="#Base-Color:-RGB-sRGB" class="headerlink" title="Base Color:(RGB-sRGB)"></a>Base Color:(RGB-sRGB)</h3><p>包含信息:绝缘体,反射颜色。金属,反射率。最亮色不应超越 240RGB,最暗色应在 30-50 RBG 之间。</p><p>不包含其他光照信息,比如 amblient occlusion。金属反射率应该在 70%-100% 之间。也就是 180-255 RGB,油漆和氧化的部分应该按照绝缘体处理。</p><blockquote><p>对应 THREE.js 的 <a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.color" target="_blank" rel="noopener">MeshStandardMaterial.color</a>、<a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.map" target="_blank" rel="noopener">MeshStandardMaterial.map</a></p></blockquote><h3 id="Matallic-Map:-Grayscale-Linear"><a href="#Matallic-Map:-Grayscale-Linear" class="headerlink" title="Matallic Map:(Grayscale-Linear)"></a>Matallic Map:(Grayscale-Linear)</h3><p>描述哪些区域是金属,那些是绝缘体。值为 <code>1</code> 表示纯金属,为 <code>0</code> 表示绝缘体。</p><p>如果matalic map的灰度值低于235,那么对应的basecolor的反射率也应该降低一些。</p><blockquote><p>对应 THREE.js 的 <a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.metalness" target="_blank" rel="noopener">MeshStandardMaterial.metalness</a>、<a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.metalnessMap" target="_blank" rel="noopener">MeshStandardMaterial.metalnessMap</a></p></blockquote><h3 id="Roughness-Map:(Grayscale-Linear)"><a href="#Roughness-Map:(Grayscale-Linear)" class="headerlink" title="Roughness Map:(Grayscale-Linear)"></a>Roughness Map:(Grayscale-Linear)</h3><p>描述表面引起光纤漫反射的不规则程度,这将改变光纤的方向,但是不会改变光线的强度。光滑表面会有小而亮的高光,而粗糙表面将会出现大而暗淡的高光。在这个灰度图里面,值为 <code>0</code> 代表光滑表面,而 <code>1</code> 代表粗糙表面。</p><p>Roughness Map是一个“有故事”的贴图,它会告诉你这个表面经历过那些严酷(或者平和)的环境。</p><blockquote><p>对应 THREE.js 的 <a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.roughness" target="_blank" rel="noopener">MeshStandardMaterial.roughness</a>、<a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial.roughnessMap" target="_blank" rel="noopener">MeshStandardMaterial.roughnessMap</a></p></blockquote><h2 id="Specular-Glossiness-Worfkow"><a href="#Specular-Glossiness-Worfkow" class="headerlink" title="Specular Glossiness Worfkow"></a>Specular Glossiness Worfkow</h2><p>镜面反射工作流容易出错,因为 Specular 包含了全部的绝缘体 F0 信息,<code>1.0</code> 的 Diffuse 和 <code>1.0</code> 的 Specular 将会使得反射折射能量大于输入能量,这违背了能量守恒定律。也就是说在绘制贴图时,你无法预览结果。</p><blockquote><p>Specular 材质在 THREE.js 中未实现,可以利用自定义 <a href="https://threejs.org/docs/index.html#api/en/materials/ShaderMaterial" target="_blank" rel="noopener">ShaderMaterial</a> 来实现,具体实现参考 <a href="https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/GLTFLoader.js#L639" target="_blank" rel="noopener">这里</a></p></blockquote><h3 id="Diffuse:(RGB-sRGB)"><a href="#Diffuse:(RGB-sRGB)" class="headerlink" title="Diffuse:(RGB-sRGB)"></a>Diffuse:(RGB-sRGB)</h3><p>包含 Albedo Color,但是不包含任何反射率信息。纯金属为黑色,因为纯金属没有颜色,油漆和锈迹是需要带有颜色的。基础颜色应不包含光照信息,除了 micro-occlusion。黑色不应低于 30-50sRGB,亮色不应高于240sRGB。</p><h3 id="Specular:(RGB-sRGB)"><a href="#Specular:(RGB-sRGB)" class="headerlink" title="Specular:(RGB-sRGB)"></a>Specular:(RGB-sRGB)</h3><p>灰度值,表示非金属 F0 RGB 值,表示金属吸收的不同波长的光线。这种贴图允许使用不同的F0值来表现非金属。</p><h3 id="Glossiness:"><a href="#Glossiness:" class="headerlink" title="Glossiness:"></a>Glossiness:</h3><p>与 Roughness 相反,值为 <code>0</code> 代表粗糙表面,而 <code>1</code> 代表光滑表面。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>Metal/Roughness 与 Specular/Glossiness 是可以相互转换的,这里推荐个 <a href="https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows/" target="_blank" rel="noopener">小工具</a> 可以在线转换两种工作流。</p><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>随着计算机硬件以及渲染上的进步,我们可以更好的从物理上去模拟灯光属性,从而诞生了新的工作流程 PBR 工作流。PBR是一种着色和渲染技术,基于物理的渲染过程,用于更精确的描述光如何与物体表面互动。</p><p>PBR的优势:</p><ol><li>方法论和算法基于精确的计算公式,免除创作表面的猜想过程。</li><li>在任何光照环境都能表现出正确的结果。</li><li>为不同的艺术家,提供统一的工作流程。</li></ol><h2 id="PBR-工作流分类"><a href="#PBR-工作流分类" class="headerlink" title="PBR 工作流分类"></a>PBR 工作流分类</h2><p>PBR有两种工作流:</p><ol><li>Metal Roughness Worfkow(金属性/粗超度工作流程)</li><li>Specular Glossiness Worfkow(高光/光泽度工作流程)</li></ol><blockquote><p>THREE.js 使用的是第一种工作流,名字子叫做 <code>Metallic-Roughness</code>,对应材质为 <a href="https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial" target="_blank" rel="noopener">MeshStandardMaterial</a></p></blockquote>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
<category term="3D" scheme="https://sanonz.github.io/tags/3D/"/>
<category term="WebGL" scheme="https://sanonz.github.io/tags/WebGL/"/>
<category term="Canvas" scheme="https://sanonz.github.io/tags/Canvas/"/>
<category term="Three.js" scheme="https://sanonz.github.io/tags/Three-js/"/>
</entry>
<entry>
<title>以太坊概念与使用 wep3/infura 实现以太币及合约币交易流程</title>
<link href="https://sanonz.github.io/2019/eth-transaction-for-wep3-and-infura/"/>
<id>https://sanonz.github.io/2019/eth-transaction-for-wep3-and-infura/</id>
<published>2019-04-02T06:43:48.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在写一个自动转账的脚本,经过了一番搜寻查阅及实战编写,总结了一些心得和见解,写出来希望能帮助到更多有需要的人。</p><h2 id="以太币及合约币转账概念"><a href="#以太币及合约币转账概念" class="headerlink" title="以太币及合约币转账概念"></a>以太币及合约币转账概念</h2><p>一个以太坊账号包含三个部分,<code>助记词(Mnemonic Phrase)</code>、<code>私钥(Private Key)</code> 以及 <code>地址</code> ,其中助记词与私钥是可以相互转换的。</p><blockquote><p>由于私钥64位,长得太难看,没有可读性,而私钥的备份在电脑上复制起来容易,手抄下来就比较麻烦,但私钥保存在联网的电脑上毕竟不安全,有被其他人看到的风险,于是有了助记词工具,利用某种算法可以将64位私钥转换成十多个常见的英文单词,这些单词都来源于一个固定词库,根据一定算法得来。私钥与助记词之间的转换是互通的,助记词只是你的私钥的另一种外貌体现。</p></blockquote><p>以太坊是一个分布式的智能合约平台,有一套 <a href="https://eips.ethereum.org/EIPS/eip-20" target="_blank" rel="noopener">ERC-20</a> 标准,通过此标准可以发行自己的合约币(Token),最早叫代币,后来统一翻译为通证。以太币是以太坊发行的币,主要是用来结算合约币(Token)交易费用。</p><a id="more"></a><p>以太坊有一套核心 <code>EVM(以太坊虚拟机)</code> 运行在分布式的智能合约平台,这套核心在区块链上每个参与的节点上运行,开发者可以在其上开发各种应用。与比特币的脚本引擎不同,以太坊的 <code>EVM</code> 功能非常强大,号称“图灵完备”。运行在 <code>EVM</code> 上的脚本称作 <code>DApp(Decentralized Application)</code>,当我们发送一条交易时,这条交易会广播一条消息,收到这条消息的账户会运行消息相应的一系列指令,而运行指令的过程会消耗 <code>gas</code>,当整个交易完成后会根据总共运行的指令量计算出 <code>gasUsed</code>,<code>gas</code> 的单位为 <code>Gwei</code>,运行不同的指令会干不同的或活,干不同的活所消耗的资源也不尽相同,<a href="https://docs.google.com/spreadsheets/d/1m89CVujrQe5LAFJ8-YAUCcNK950dUzMQPMJBxRtGCqs/edit#gid=0" target="_blank" rel="noopener">这个表格</a> 列举了以太坊的指令所对应消耗的 <code>gas</code> 量。</p><p><code>gas</code> 这个名字起的非常贴切,翻译过来就是 <code>汽油</code> 的意思。如果把以太坊比做一台汽车,运行需要汽油驱动。汽油的价格称作 <code>gasPrice</code>,车跑的过程所消耗的油量称作 <code>gasUsed</code>,可以通过 <code>gasLimit</code> 来限制 <code>gasUsed</code> 的最大值,当超过这个值时就会终止,在终止之前所消耗的 <code>gasUsed</code> 依然会被扣除。如果直到交易完成也没触发或者恰好等于 <code>gasLimit</code>,那么这个交易就会成功,交易完成后只扣除 <code>gasUsed</code>。这里的 <code>gasPrice</code> 不像现实的汽油一样可以由自己控制,这是因为当你设置 <code>gasPrice</code> 油价越高时,你的交易就会被提前处理,举个不恰当的例子:相当于出高价可以买 98 的油,出低价只能买 92 的油一样。</p><p><code>gas</code> 常用单位为 <code>Gwei</code>,还有比它小的 <code>Wei</code>,<code>gas</code> 在交易完成后会转换为 <code>Ether</code> 进行结算,具体转换如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1 Gwei = 1,000,000,000 wei</span><br><span class="line">1 Ether = 1,000,000,000 Gwei</span><br><span class="line"></span><br><span class="line">交易费:</span><br><span class="line">fee = gasUsed * gasPrice</span><br></pre></td></tr></table></figure><blockquote><p>每笔交易所消耗的 <code>gas</code> 不能提前计算获得,只能交易完成后才能确定。虽然不能提前知道,但是可以根据最近转账所使用的 <code>gasUsed</code> 大概预估出来。</p></blockquote><p>通过以上的学习我们可以做个小练习,在以太坊浏览器 <a href="https://etherscan.io/txs" target="_blank" rel="noopener">https://etherscan.io</a> 随便找个交易然后计算它的 <code>gas</code> 消耗,比如拿这个交易进行测试 <a href="https://etherscan.io/tx/0x20b727ab46cc3d5d87d187a595bc50d4bfde6d9b6b1dfdd2ee497c8bb2866df6" target="_blank" rel="noopener">0x20b727…866df6</a>,只需要关注以下四个字段中的三个就能套用以上教程进行推导计算</p><table><thead><tr><th>key</th><th>value</th></tr></thead><tbody><tr><td>Transaction Fee</td><td>0.000393081 Ether ($0.06)</td></tr><tr><td>Gas Limit</td><td>30,237</td></tr><tr><td>Gas Used by Transaction</td><td>30,237 (100%)</td></tr><tr><td>Gas Price</td><td>0.000000013 Ether (13 Gwei)</td></tr></tbody></table><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> gasPrice = <span class="number">13</span>; <span class="comment">// Gwei</span></span><br><span class="line"><span class="keyword">const</span> gasLimit = gasUsed = <span class="number">30237</span>; <span class="comment">// 这里 Gas Used by Transaction 为 100%,说明刚好达到限制额度</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 由以上的 fee = gasUsed * gasPrice 公式可的</span></span><br><span class="line"><span class="keyword">const</span> fee = gasUsed * gasPrice; <span class="comment">// 393081 Gwei</span></span><br><span class="line"><span class="comment">// 由 1 ether = 1,000,000,000 Gwei 可得</span></span><br><span class="line"><span class="keyword">const</span> ether = gasUsed * gasPrice / <span class="number">1e9</span>; <span class="comment">// 0.000393081 Ether</span></span><br></pre></td></tr></table></figure><p>最终结果:0.000393081 Ether,与 Transaction Fee 字段结果一样,说明我们的算法是没毛病的。</p><h2 id="第三方接口及主要依赖库"><a href="#第三方接口及主要依赖库" class="headerlink" title="第三方接口及主要依赖库"></a>第三方接口及主要依赖库</h2><ol><li><a href="https://infura.io/docs" target="_blank" rel="noopener">https://infura.io/docs</a></li><li><a href="https://github.com/ethereum/web3.js" target="_blank" rel="noopener">https://github.com/ethereum/web3.js</a></li></ol><h2 id="转账业务逻辑编写"><a href="#转账业务逻辑编写" class="headerlink" title="转账业务逻辑编写"></a>转账业务逻辑编写</h2><p>安装依赖包</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ npm install bip39 eth-json-rpc-infura ethereumjs-wallet web3</span><br><span class="line">$ # 如果出错使用带版本号方式安装</span><br><span class="line">$ npm install bip39@<span class="number">2.5</span><span class="number">.0</span> eth-json-rpc-infura@<span class="number">4.0</span><span class="number">.0</span> ethereumjs-wallet@<span class="number">0.6</span><span class="number">.3</span> web3@<span class="number">1.0</span><span class="number">.0</span>-beta<span class="number">.51</span></span><br></pre></td></tr></table></figure><p>创建 <code>./provider.js</code>,让 web3 支持 <code>infura</code>,支持后可以链接到同步以太坊 <code>mainnet</code> 主网络</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> createInfuraProvider = <span class="built_in">require</span>(<span class="string">'eth-json-rpc-infura/src/createProvider'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> provider = {<span class="attr">send</span>: createInfuraProvider().sendAsync};</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = provider;</span><br></pre></td></tr></table></figure><p>创建 <code>./client.js</code>,初始化 web3 对象</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Web3 = <span class="built_in">require</span>(<span class="string">'web3'</span>);</span><br><span class="line"><span class="keyword">const</span> Transaction = <span class="built_in">require</span>(<span class="string">'ethereumjs-tx'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> provider = <span class="built_in">require</span>(<span class="string">'./provider'</span>);</span><br><span class="line"><span class="comment">// ERC20 ABI:https://github.com/ethereum/wiki/wiki/Contract-ERC20-ABI</span></span><br><span class="line"><span class="keyword">const</span> contractABI = <span class="built_in">require</span>(<span class="string">'./erc20-abi.json'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> web3 = <span class="keyword">new</span> Web3(provider);</span><br></pre></td></tr></table></figure><p>转账需要密钥,如果你使用的是助记词(Mnemonic Phrase),需要先转换成密钥:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bip39 = <span class="built_in">require</span>(<span class="string">'bip39'</span>);</span><br><span class="line"><span class="keyword">const</span> hdkey = <span class="built_in">require</span>(<span class="string">'ethereumjs-wallet/hdkey'</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">generateAddressesFromSeed</span>(<span class="params">seed, count = <span class="number">2</span></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(seed));</span><br><span class="line"> <span class="keyword">const</span> wallet_hdpath = <span class="string">"m/44'/60'/0'/0/"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> accounts = [];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < count; i++) {</span><br><span class="line"> <span class="keyword">let</span> wallet = hdwallet.derivePath(wallet_hdpath + i).getWallet();</span><br><span class="line"> <span class="keyword">let</span> address = <span class="string">'0x'</span> + wallet.getAddress().toString(<span class="string">"hex"</span>);</span><br><span class="line"> <span class="keyword">let</span> privateKey = wallet.getPrivateKey().toString(<span class="string">"hex"</span>);</span><br><span class="line"> accounts.push({ <span class="attr">address</span>: address, <span class="attr">privateKey</span>: privateKey });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> accounts;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> accounts = generateAddressesFromSeed(<span class="string">'epoch research about divide instrument with postdoctoral optional science someone epoch can'</span>);</span><br><span class="line"><span class="keyword">const</span> myAddress = accounts[<span class="number">0</span>].address;</span><br><span class="line"><span class="keyword">const</span> privateKey = accounts[<span class="number">0</span>].privateKey; <span class="comment">// 转换后的密钥</span></span><br></pre></td></tr></table></figure><p>以太币转账逻辑</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> privateKey = Buffer.from(<span class="string">'YOUR_PRIVATE_KEY'</span>, <span class="string">'hex'</span>); <span class="comment">// 私钥</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> myAddress = <span class="string">'ADDRESS_THAT_SENDS_TRANSACTION'</span>; <span class="comment">// 你的地址</span></span><br><span class="line"><span class="keyword">const</span> toAddress = <span class="string">'0x114fdf4ebd30ae646fac486b4e796616c95ca244'</span>; <span class="comment">// 接收者的地址</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 转账数量,单位 Wei,web3.utils.toWei 可将 1 个 Ether 转换为 Wei</span></span><br><span class="line"><span class="keyword">const</span> amount = web3.utils.toHex(web3.utils.toWei(<span class="string">'1'</span>, <span class="string">'ether'</span>));</span><br><span class="line"></span><br><span class="line">web3.eth.getTransactionCount(accounts[<span class="number">0</span>].address)</span><br><span class="line"> .then(<span class="keyword">async</span> count => {</span><br><span class="line"> <span class="comment">//creating raw tranaction</span></span><br><span class="line"> <span class="keyword">const</span> txParams = {</span><br><span class="line"> <span class="keyword">from</span>: myAddress,</span><br><span class="line"> gasPrice: web3.utils.toHex(<span class="number">1</span> * <span class="number">1e9</span>),</span><br><span class="line"> gasLimit: web3.utils.toHex(<span class="number">210000</span>),</span><br><span class="line"> to: toAddress,</span><br><span class="line"> value: amount,</span><br><span class="line"> nonce: web3.utils.toHex(count)</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">//creating tranaction via ethereumjs-tx</span></span><br><span class="line"> <span class="keyword">const</span> tx = <span class="keyword">new</span> Transaction(txParams);</span><br><span class="line"> <span class="comment">//signing transaction with private key</span></span><br><span class="line"> tx.sign(privateKey);</span><br><span class="line"> <span class="comment">//sending transacton via web3 module</span></span><br><span class="line"> web3.eth.sendSignedTransaction(<span class="string">'0x'</span> + tx.serialize().toString(<span class="string">'hex'</span>))</span><br><span class="line"> .on(<span class="string">'transactionHash'</span>, <span class="built_in">console</span>.log);</span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(err);</span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p>使用合约币(Token)转账,注意这里的 <code>amount</code> 合约币的转账数量需要乘上合约币发行时设置的精度(decimals)</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 合约币地址</span></span><br><span class="line"><span class="keyword">const</span> contractAddress = <span class="string">'0x3c76ef53be46ed2e9be224e8f0b92e8acbc24ea0'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建合约币对象</span></span><br><span class="line"><span class="keyword">const</span> contract = <span class="keyword">new</span> web3.eth.Contract(contractABI, contractAddress);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置 amount 前需要先获取合约币精度</span></span><br><span class="line"><span class="keyword">const</span> decimals = <span class="keyword">await</span> contract.methods.decimals().call();</span><br><span class="line"></span><br><span class="line"><span class="comment">// amount = 转账数量 × decimals,例如这里转一个 XXX 合约币</span></span><br><span class="line"><span class="keyword">const</span> amount = web3.utils.toHex(<span class="number">1</span> * decimals);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">web3.eth.getTransactionCount(accounts[<span class="number">0</span>].address)</span><br><span class="line"> .then(<span class="keyword">async</span> count => {</span><br><span class="line"> <span class="comment">//creating raw tranaction</span></span><br><span class="line"> <span class="keyword">const</span> txParams = {</span><br><span class="line"> <span class="keyword">from</span>: myAddress,</span><br><span class="line"> gasPrice: web3.utils.toHex(<span class="number">1</span> * <span class="number">1e9</span>),</span><br><span class="line"> gasLimit: web3.utils.toHex(<span class="number">210000</span>),</span><br><span class="line"> to: contractAddress,</span><br><span class="line"> value: web3.utils.toHex(<span class="number">0</span>),</span><br><span class="line"> data: contract.methods.transfer(toAddress, amount).encodeABI(),</span><br><span class="line"> nonce: web3.utils.toHex(count)</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">//creating tranaction via ethereumjs-tx</span></span><br><span class="line"> <span class="keyword">const</span> tx = <span class="keyword">new</span> Transaction(txParams);</span><br><span class="line"> <span class="comment">//signing transaction with private key</span></span><br><span class="line"> tx.sign(privateKey);</span><br><span class="line"> <span class="comment">//sending transacton via web3 module</span></span><br><span class="line"> web3.eth.sendSignedTransaction(<span class="string">'0x'</span> + tx.serialize().toString(<span class="string">'hex'</span>))</span><br><span class="line"> .on(<span class="string">'transactionHash'</span>, <span class="built_in">console</span>.log);</span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function"><span class="params">err</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(err);</span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p>txParams 参数说明:</p><table><thead><tr><th>key</th><th>description</th></tr></thead><tbody><tr><td>nonce</td><td>发送者发送交易数的计数,按当前账号下的交易进行递增(从 0 开始),使用相同的 <code>nonce</code> 则可覆盖 <code>pending</code> 状态的交易,为了保险起见调用 <code>web3.eth.getTransactionCount</code> 方法获取当前账号交易计数</td></tr><tr><td>gasPrice</td><td>发送者愿意支付执行交易所需的每个gas的Wei数量</td></tr><tr><td>gasLimit</td><td>发送者愿意为执行交易支付gas数量的最大值。这个数量被设置之后在任何计算完成之前就会被提前扣掉</td></tr><tr><td>to</td><td>接收者的地址。在合约创建交易中,合约账户的地址还没有存在,所以值先空着</td></tr><tr><td>value</td><td>从发送者转移到接收者的Wei数量。在合约创建交易中,value作为新建合约账户的开始余额</td></tr><tr><td>init</td><td>用来初始化新合约账户的EVM代码片段(只有在合约创建交易中存在)。init值会执行一次,然后就会被丢弃。当init第一次执行的时候,它返回一个账户代码体,也就是永久与合约账户关联的一段代码。</td></tr><tr><td>data</td><td>消息通话中的输入数据,也就是参数(可选域,只有在消息通信中存在)例如,如果智能合约就是一个域名注册服务,那么调用合约可能就会期待输入域例如域名和IP地址</td></tr><tr><td>v,r,s</td><td>用于产生标识交易发生着的签名</td></tr></tbody></table><p><code>txParams.nonce</code> 说明:</p><ul><li>当nonce太小,交易会被直接拒绝。</li><li>当nonce太大,交易会一直处于队列之中,这也就是导致我们上面描述的问题的原因。</li><li>当发送一个比较大的nonce值,然后补齐开始nonce到那个值之间的nonce,那么交易依旧可以被执行。</li><li>当交易处于queue中时停止geth客户端,那么交易queue中的交易会被清除掉。</li></ul><p>使用合约币转账注意事项:</p><ul><li><code>txParams.to</code> 地址要写合约币地址</li><li><code>txParams.data</code> 真正接收者的地址转换后写到这里</li></ul><h2 id="预估当前的转账价格"><a href="#预估当前的转账价格" class="headerlink" title="预估当前的转账价格"></a>预估当前的转账价格</h2><p>通过以下三种方式获取预估 <code>gasPrice</code>:</p><ol><li>使用第三方 API:<a href="https://ethgasstation.info/json/ethgasAPI.json" target="_blank" rel="noopener">https://ethgasstation.info/json/ethgasAPI.json</a></li><li>使用官方的 API,单位为 Gwei:<a href="https://www.etherchain.org/api/gasPriceOracle" target="_blank" rel="noopener">https://www.etherchain.org/api/gasPriceOracle</a></li><li>调用的 <code>web3.eth.getGasPrice()</code> 方法获取,单位为 Wei,除于 <code>1e9</code> 可以转换为 Gwei。这种方法只能获取的相当于以上第一种的 <code>average</code> 字段与第二种的 <code>standard</code> 字段,转账快,费用高。</li></ol><p>这里讲下第一种方式,因为这种获取到的结果比较丰富一些,方便应对五花八门的需求,学会了这种另外两种自然也就会了,请求接口:<a href="https://ethgasstation.info/json/ethgasAPI.json" target="_blank" rel="noopener">https://ethgasstation.info/json/ethgasAPI.json</a>,返回 JSON 中有以下字段:</p><table><thead><tr><th>字段</th><th>参考值</th><th>单位</th><th>说明</th></tr></thead><tbody><tr><td>fastest</td><td>200</td><td>除 10 后为 Gwei</td><td>转账速度很快,价格相对较贵</td></tr><tr><td>fast</td><td>100</td><td>同上</td><td>转账速度一般,价格相对一般</td></tr><tr><td>safeLow</td><td>30</td><td>同上</td><td>转账速度很慢,价格相对较低</td></tr><tr><td>fastestWait</td><td>14.2</td><td>分钟</td><td>快速转账预计花费时间</td></tr><tr><td>fastWait</td><td>2.2</td><td>同上</td><td>一般转账预计花费时间</td></tr><tr><td>safeLowWait</td><td>2.2</td><td>同上</td><td>较慢转账预计花费时间</td></tr></tbody></table><p>比如这里使用 <code>safeLow</code> 较慢转账的费用计算:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> params = {<span class="attr">from</span>: myAddress, <span class="attr">to</span>: toAddress};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> estimateGasPrice = <span class="number">30</span>; <span class="comment">// safeLow</span></span><br><span class="line"><span class="keyword">const</span> gwei = estimateGasPrice / <span class="number">10</span>;</span><br><span class="line"><span class="keyword">const</span> estimategasUsed = web3.eth.estimateGas(params); <span class="comment">// 21000</span></span><br><span class="line"><span class="keyword">const</span> gas = gwei * estimategasUsed;</span><br><span class="line"><span class="keyword">const</span> ether = gas / <span class="number">1e9</span>; <span class="comment">// 1e9 为 Gwei 转 Ether 的汇率</span></span><br><span class="line"><span class="keyword">const</span> usd = ether * <span class="number">171.645</span>; <span class="comment">// 假设 Ether(以太币) 转 美元的汇率为 171.645</span></span><br><span class="line"><span class="comment">// output: 0.010813635 ($)</span></span><br></pre></td></tr></table></figure><p>如果是合约币需换成以下 <code>params</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> parmas = {</span><br><span class="line"> <span class="keyword">from</span>: myAddress,</span><br><span class="line"> to: contractAddress,</span><br><span class="line"> data: contract.methods.transfer(toAddress, amount).encodeABI()</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>或者直接使用上边初始化过的合约对象 <code>contract</code> 来获取会更简单一些:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> estimategasUsed = contract.methods.transfer(toAddress, amount).estimateGas({<span class="attr">from</span>: myAddress});</span><br></pre></td></tr></table></figure><blockquote><p>合约币要注意的是 <code>amount</code> 不能大于账户实际的余额,要不然 <code>estimateGas</code> 请求会得到一个 code 为 <code>-32000</code> 的错误提示。</p></blockquote><p>Ether(以太币)转法币的实时汇率可以通过这个接口获取:<a href="https://api.infura.io/v1/ticker/symbols" target="_blank" rel="noopener">https://api.infura.io/v1/ticker/symbols</a>,接口会返回一个所支持对法币的列表:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"symbols"</span>: [</span><br><span class="line"> <span class="string">"ethusd"</span>, <span class="comment">// 以太币对美元</span></span><br><span class="line"> <span class="string">"ethhkd"</span>, <span class="comment">// 以太币对港元</span></span><br><span class="line"> <span class="string">"etheur"</span>, <span class="comment">// 以太币对欧元</span></span><br><span class="line"> <span class="string">"ethgbp"</span> <span class="comment">// 以太币对英镑</span></span><br><span class="line"> ...</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后找个支持的 symbol 来获取具体的汇率,例如使用以太币对美元的 symbol 为 <code>ethusd</code>,拼接后的 API 为:<a href="https://api.infura.io/v1/ticker/ethusd" target="_blank" rel="noopener">https://api.infura.io/v1/ticker/ethusd</a>,返回示例:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"base"</span>: <span class="string">"ETH"</span>,</span><br><span class="line"> <span class="attr">"quote"</span>: <span class="string">"USD"</span>,</span><br><span class="line"> <span class="attr">"bid"</span>: <span class="number">171.645</span>,</span><br><span class="line"> <span class="attr">"ask"</span>: <span class="number">171.691</span>,</span><br><span class="line"> <span class="attr">"volume"</span>: <span class="number">970347.3443</span>,</span><br><span class="line"> <span class="attr">"exchange"</span>: <span class="string">"hitbtc"</span>,</span><br><span class="line"> <span class="attr">"total_volume"</span>: <span class="number">2751280.28735102</span>,</span><br><span class="line"> <span class="attr">"num_exchanges"</span>: <span class="number">10</span>,</span><br><span class="line"> <span class="attr">"timestamp"</span>: <span class="number">1554259623</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为数字货币的波动很大,这里有一个最新撮合的 <code>bid</code> (买单价格)、 <code>ask</code> (卖单价格),我们提取取 <code>bid</code> 作为汇率即可。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ol><li>如果 gasPrice 设置的小或者网络繁忙的话,一般超过 10 小时就会 drop 掉,如果需要保证转账成功率的话需要检测区块状态,<code>dropped</code> 状态的要重新提交转账申请。</li><li>比特币为 10 分钟出一个块,一个块大概有 1M 左右;以太坊为 15 秒出一个块,块大小无限制。</li></ol><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在写一个自动转账的脚本,经过了一番搜寻查阅及实战编写,总结了一些心得和见解,写出来希望能帮助到更多有需要的人。</p><h2 id="以太币及合约币转账概念"><a href="#以太币及合约币转账概念" class="headerlink" title="以太币及合约币转账概念"></a>以太币及合约币转账概念</h2><p>一个以太坊账号包含三个部分,<code>助记词(Mnemonic Phrase)</code>、<code>私钥(Private Key)</code> 以及 <code>地址</code> ,其中助记词与私钥是可以相互转换的。</p><blockquote><p>由于私钥64位,长得太难看,没有可读性,而私钥的备份在电脑上复制起来容易,手抄下来就比较麻烦,但私钥保存在联网的电脑上毕竟不安全,有被其他人看到的风险,于是有了助记词工具,利用某种算法可以将64位私钥转换成十多个常见的英文单词,这些单词都来源于一个固定词库,根据一定算法得来。私钥与助记词之间的转换是互通的,助记词只是你的私钥的另一种外貌体现。</p></blockquote><p>以太坊是一个分布式的智能合约平台,有一套 <a href="https://eips.ethereum.org/EIPS/eip-20" target="_blank" rel="noopener">ERC-20</a> 标准,通过此标准可以发行自己的合约币(Token),最早叫代币,后来统一翻译为通证。以太币是以太坊发行的币,主要是用来结算合约币(Token)交易费用。</p>
</summary>
<category term="back-end" scheme="https://sanonz.github.io/categories/back-end/"/>
<category term="Nodejs" scheme="https://sanonz.github.io/tags/Nodejs/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
<category term="eth" scheme="https://sanonz.github.io/tags/eth/"/>
<category term="BlockChain" scheme="https://sanonz.github.io/tags/BlockChain/"/>
</entry>
<entry>
<title>实现树莓派(Raspberry Pi)联网发送本机IP到绑定的微信</title>
<link href="https://sanonz.github.io/2019/raspberry-ip-wechat/"/>
<id>https://sanonz.github.io/2019/raspberry-ip-wechat/</id>
<published>2019-02-16T11:33:00.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>通常我们使用树莓派做小主机时往往是不需要连接屏幕的,在开发树莓派时如果没有屏幕的话首先需要知道主机的 IP 才能访问树莓派。一般情况下我们不需要知道,直接可以通过 <code>ssh [email protected]</code> 来连接主机,但是有些设备没有装 avahi 是不支持 <code>raspberrypi.local</code> 域名访问的,还要回归本质老老实实使用 IP,这时候我们每次访问树莓派都要一些步骤去获取 IP,整个过程可能将变得麻烦。我看网上也有一些比如发邮件的解决方案,实际情况大家用微信的频率要远远高于邮箱,这个项目可以让树莓派联网后自动发送本机 IP 到微信,相比邮箱可以减少微信切邮箱…邮箱切微信的动作。安装本项目后可以节约我们的一些时间成本,还可以增加一些开发乐趣。</p><a id="more"></a> <img src="/2019/raspberry-ip-wechat/preview.png" width="375"><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><p>作者使用的是 <a href="https://www.raspberrypi.org/downloads/raspbian/" target="_blank" rel="noopener">Raspbian Stretch Lite</a> 版本的系统,以下所用的命令都基于这个系统上跑的,不同系统可能有一些差别,如果出现了问题欢迎提 <a href="https://github.com/sanonz/raspberry-ip-wechat/issues" target="_blank" rel="noopener">Issues</a> 一起探讨。</p><p>项目地址:<a href="https://github.com/sanonz/raspberry-ip-wechat" target="_blank" rel="noopener">https://github.com/sanonz/raspberry-ip-wechat</a><br>因为这个项目用了 nodejs,所以需要先安装才能使用,如果安装过了可以跳过</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -</span><br><span class="line">sudo apt-get install -y nodejs</span><br></pre></td></tr></table></figure><p>安装后执行以下命令查看是否成功,有版本输入则代表安装成功</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ node -v</span><br><span class="line">v11.9.0</span><br></pre></td></tr></table></figure><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>Git 方式安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> [email protected]:sanonz/raspberry-ip-wechat.git</span><br><span class="line">$ <span class="built_in">cd</span> raspberry-ip-wechat</span><br><span class="line">$ npm install</span><br><span class="line">$ <span class="built_in">pwd</span></span><br><span class="line">/home/htdocs/raspberry-ip-wechat <span class="comment"># 记录这个路径,下边添加自启动要用到</span></span><br></pre></td></tr></table></figure><p>或者 <a href="https://github.com/sanonz/raspberry-ip-wechat/archive/master.zip" target="_blank" rel="noopener">直接下载</a> 压缩包然后解压</p><h2 id="升级"><a href="#升级" class="headerlink" title="升级"></a>升级</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> raspberry-ip-wechat</span><br><span class="line">$ git pull</span><br></pre></td></tr></table></figure><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>修改 <code>template.json</code> 配置文件</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"userIds"</span>: [<span class="string">"o_bBF50Ewg8VYpIRJLYuo305d7EY"</span>],</span><br><span class="line"> <span class="attr">"template_id"</span>: <span class="string">"4YscLc2uaCnsdrEdUJ9HGAGAkdBcEQM9bUBy0gs69Hw"</span>,</span><br><span class="line"> <span class="attr">"url"</span>: <span class="string">""</span>,</span><br><span class="line"> <span class="attr">"data"</span>: {</span><br><span class="line"> <span class="attr">"first"</span>: {</span><br><span class="line"> <span class="attr">"value"</span>: <span class="string">"树莓派开发板网络连接状态"</span>,</span><br><span class="line"> <span class="attr">"color"</span>: <span class="string">"#856404"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"keyword1"</span>: {</span><br><span class="line"> <span class="attr">"value"</span>: <span class="string">"发送日期"</span>,</span><br><span class="line"> <span class="attr">"color"</span>: <span class="string">"#999999"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"keyword2"</span>: {</span><br><span class="line"> <span class="attr">"value"</span>: <span class="string">"连接成功"</span>,</span><br><span class="line"> <span class="attr">"color"</span>: <span class="string">"#28a745"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"remark"</span>: {</span><br><span class="line"> <span class="attr">"value"</span>: <span class="string">"服务器IP地址: {ip} ({type})"</span>,</span><br><span class="line"> <span class="attr">"color"</span>: <span class="string">"#721c24"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><strong>userIds</strong> - 接收者的微信 OpenID,微信搜索公众号 <code>wxpusher</code> 关注后把自动推送的 userId 填写到这里。</li><li><strong>template_id</strong> - 模板 ID,无需理会。</li><li><strong>url</strong> - 如果填写推送消息则可以点击,为空则不能点击。</li><li><strong>data</strong> - 推送的详细信息,<code>value</code> 与 <code>color</code> 字段都可以更改。支持模板,{ip} 会被替换成主机的 IP,{type} 会被替换成是 wifi/还是网线连接的。</li></ul><p>添加开机启动脚本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">$ sudo nano /etc/rc.local</span><br><span class="line"><span class="comment">#!/bin/sh -e</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># rc.local</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># This script is executed at the end of each multiuser runlevel.</span></span><br><span class="line"><span class="comment"># Make sure that the script will "exit 0" on success or any other</span></span><br><span class="line"><span class="comment"># value on error.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># In order to enable or disable this script just change the execution</span></span><br><span class="line"><span class="comment"># bits.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># By default this script does nothing.</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Print the IP address</span></span><br><span class="line">_IP=$(hostname -I) || <span class="literal">true</span></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">"<span class="variable">$_IP</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">printf</span> <span class="string">"My IP address is %s\n"</span> <span class="string">"<span class="variable">$_IP</span>"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">node /home/htdocs/raspberry-ip-wechat/index.js <span class="comment"># 增加这一行,放在 `exit 0` 前边,路径使用上方记录的</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">exit</span> 0</span><br></pre></td></tr></table></figure><p>添加完成后按 <code>ctrl+X</code> 然后按 <code>Y</code> 保存修改</p><p>然后赶紧重启你的树莓派试下吧,如果不出什么问题的话,你将收到一条树莓派给你发的微信。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>这个项目用了微信企业号的模板消息,申请需要提供公司资料认证,对于个人而言门槛还是比较高,所以本项目用了第三方提供的接口进行推送,稳定性及安全性不能保证,如果要求比较高请自行研究替换为自己的企业号。</p><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>通常我们使用树莓派做小主机时往往是不需要连接屏幕的,在开发树莓派时如果没有屏幕的话首先需要知道主机的 IP 才能访问树莓派。一般情况下我们不需要知道,直接可以通过 <code>ssh [email protected]</code> 来连接主机,但是有些设备没有装 avahi 是不支持 <code>raspberrypi.local</code> 域名访问的,还要回归本质老老实实使用 IP,这时候我们每次访问树莓派都要一些步骤去获取 IP,整个过程可能将变得麻烦。我看网上也有一些比如发邮件的解决方案,实际情况大家用微信的频率要远远高于邮箱,这个项目可以让树莓派联网后自动发送本机 IP 到微信,相比邮箱可以减少微信切邮箱…邮箱切微信的动作。安装本项目后可以节约我们的一些时间成本,还可以增加一些开发乐趣。</p>
</summary>
<category term="back-end" scheme="https://sanonz.github.io/categories/back-end/"/>
<category term="Nodejs" scheme="https://sanonz.github.io/tags/Nodejs/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
<category term="IoT" scheme="https://sanonz.github.io/tags/IoT/"/>
<category term="Raspberry" scheme="https://sanonz.github.io/tags/Raspberry/"/>
</entry>
<entry>
<title>使用 Three.js 的 RenderTarget 实现离屏渲染</title>
<link href="https://sanonz.github.io/2018/offscreen-render-target-with-three-js/"/>
<id>https://sanonz.github.io/2018/offscreen-render-target-with-three-js/</id>
<published>2018-09-27T09:48:29.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="RenderTarget-介绍"><a href="#RenderTarget-介绍" class="headerlink" title="RenderTarget 介绍"></a>RenderTarget 介绍</h2><p>在渲染的时候调用 <code>renderer.render(scene, camera, renderTarget, forceClear)</code> 方法,<code>render</code> 方法有四个参数,我们平时使用只传前两个参数,第一个参数是要绘制的场景,第二个参数是指定相机,相机照射的区域会转换成 2D 绘制到屏幕,而我们今天要讲的就是使用第三个参数,不让渲染的内容直接绘制到屏幕,而是存放到 <code>renderTarget</code> 里。RenderTarget 是一个缓冲区,用来记录渲染后的像素,而不是直接绘制到屏幕上,因此我们可以在绘制屏幕之前做一些处理,以满足特殊的需求。</p><h2 id="把-RenderTarget-作为贴图使用"><a href="#把-RenderTarget-作为贴图使用" class="headerlink" title="把 RenderTarget 作为贴图使用"></a>把 RenderTarget 作为贴图使用</h2><p>首先创建两个场景:一个 <code>RTScene</code> 场景,用来渲染 RenderTarget 贴图、另一个 <code>Scene</code> 场景,用来渲染最终显示到屏幕上的,并调用 <code>RTScene</code> 场景作为贴图。</p><p>创建一个尺寸为 300×300 大小的 RenderTarget 存放 RTScene 的渲染结果</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> RT_SIZE = <span class="number">300</span>;</span><br><span class="line"><span class="keyword">const</span> RenderTarget = <span class="keyword">new</span> THREE.WebGLRenderTarget( RT_SIZE, RT_SIZE);</span><br></pre></td></tr></table></figure><a id="more"></a><p>创建相机</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Camera = <span class="keyword">new</span> THREE.PerspectiveCamera( <span class="number">45</span>, <span class="built_in">window</span>.innerWidth / <span class="built_in">window</span>.innerHeight, <span class="number">1</span>, <span class="number">1000</span> );</span><br><span class="line">Camera.position.x = <span class="number">2</span>;</span><br><span class="line">Camera.position.y = <span class="number">2</span>;</span><br><span class="line">Camera.position.z = <span class="number">2</span>;</span><br><span class="line">Camera.lookAt(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>创建 RTScene,并向 RTScene 场景添加灯光、环面</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建场景</span></span><br><span class="line"><span class="keyword">const</span> RTScene = <span class="keyword">new</span> THREE.Scene();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向场景添加灯光</span></span><br><span class="line"><span class="keyword">const</span> RTLight = <span class="keyword">new</span> THREE.DirectionalLight( <span class="number">0xffffff</span>, <span class="number">1.5</span> );</span><br><span class="line">RTLight.position.set( <span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span> ).normalize();</span><br><span class="line">RTScene.add(RTLight);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向场景添加环面</span></span><br><span class="line"><span class="keyword">const</span> geometryTorus = <span class="keyword">new</span> THREE.TorusGeometry( <span class="number">10</span>, <span class="number">3</span>, <span class="number">16</span>, <span class="number">100</span> );</span><br><span class="line"><span class="comment">// 使用红色的材质</span></span><br><span class="line"><span class="keyword">const</span> materialTorus = <span class="keyword">new</span> THREE.LineBasicMaterial( {<span class="attr">color</span>: <span class="number">0xff0000</span>} );</span><br><span class="line"><span class="keyword">const</span> Torus = <span class="keyword">new</span> THREE.Mesh( geometryTorus, materialTorus );</span><br><span class="line">Torus.scale.set( <span class="number">0.05</span>, <span class="number">0.05</span>, <span class="number">0.05</span> );</span><br><span class="line">RTScene.add(Torus);</span><br></pre></td></tr></table></figure><p>创建 Scene,并向 Scene 场景添加灯光、立方体,立方体使用 RTScene 作为贴图</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Scene = <span class="keyword">new</span> THREE.Scene();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> AmbientLight = <span class="keyword">new</span> THREE.AmbientLight( <span class="number">0xffffff</span>, <span class="number">1.5</span> );</span><br><span class="line">Scene.add(AmbientLight);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Light = <span class="keyword">new</span> THREE.DirectionalLight( <span class="number">0xffffff</span>, <span class="number">1.5</span> );</span><br><span class="line">Light.position.set( <span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span> ).normalize();</span><br><span class="line">Scene.add(Light);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向场景添加立方体</span></span><br><span class="line"><span class="keyword">const</span> geometryBox = <span class="keyword">new</span> THREE.BoxBufferGeometry( <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span> );</span><br><span class="line"><span class="keyword">const</span> materialBox = <span class="keyword">new</span> THREE.MeshStandardMaterial( {</span><br><span class="line"> color: <span class="number">0xff9300</span>, <span class="comment">// 橙色</span></span><br><span class="line"> map: RenderTarget.texture <span class="comment">// 使用 RTScene 作为贴图,也就是会贴一张环面图</span></span><br><span class="line">} );</span><br><span class="line"><span class="keyword">const</span> Box = <span class="keyword">new</span> THREE.Mesh( geometryBox, materialBox );</span><br><span class="line">Scene.add(Box);</span><br></pre></td></tr></table></figure><p>创建 Renderer</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Renderer = <span class="keyword">new</span> THREE.WebGLRenderer( {</span><br><span class="line"> antialias: <span class="literal">true</span> <span class="comment">// 开启消除锯齿</span></span><br><span class="line">} );</span><br><span class="line">Renderer.setClearColor(<span class="number">0xffffff</span>, <span class="number">1.0</span>); <span class="comment">// 设置画布背景色</span></span><br><span class="line">Renderer.setPixelRatio( <span class="built_in">window</span>.devicePixelRatio ); <span class="comment">// 设置屏幕像素比</span></span><br><span class="line">Renderer.setSize( <span class="built_in">window</span>.innerWidth, <span class="built_in">window</span>.innerHeight );</span><br><span class="line"><span class="built_in">document</span>.body.appendChild( Renderer.domElement ); <span class="comment">// 把 canvas 元素放到 body 内</span></span><br></pre></td></tr></table></figure><p>场景设置完毕接下来我们就可以渲染场景了,注意这两个 <code>render</code> 的顺序,第一个的结果要给第二个使用,所以不能写反了</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Renderer.render(RTScene, Camera, RenderTarget); <span class="comment">// 离屏渲染并存放到 RenderTarget 里</span></span><br><span class="line">Renderer.render(Scene, Camera); <span class="comment">// 渲染显示到屏幕上的场景</span></span><br></pre></td></tr></table></figure><p>如果场景有动画则需要使用 <code>requestAnimationFrame</code> 马不停蹄的进行渲染</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">animation();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">animation</span>(<span class="params"></span>) </span>{</span><br><span class="line"> Renderer.clear();</span><br><span class="line"></span><br><span class="line"> Box.rotation.y += <span class="number">0.01</span>; <span class="comment">// 让立方体沿着 y 轴不停的旋转</span></span><br><span class="line"></span><br><span class="line"> Renderer.render(RTScene, Camera, RenderTarget);</span><br><span class="line"> Renderer.render(Scene, Camera);</span><br><span class="line"></span><br><span class="line"> requestAnimationFrame(animation); <span class="comment">// 循环</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>实例 Demo</p><iframe src="https://sanonz.github.io/threejs-examples/render-target/scene-to-texture.html" width="100%" height="300" frameborder="0" loading="lazy" allowfullscreen></iframe><h2 id="把-RenderTarget-画到-2D-Canvas-上"><a href="#把-RenderTarget-画到-2D-Canvas-上" class="headerlink" title="把 RenderTarget 画到 2D Canvas 上"></a>把 RenderTarget 画到 2D Canvas 上</h2><p>我们可以使用 <code>Renderer.readRenderTargetPixels(renderTarget, x, y, width, height, buffer)</code> 方法读取离屏渲染的像素数据到一个 <code>Uint8Array(length)</code> 实例里,<code>x</code>、<code>y</code> 可以限制读取的起点,<code>width</code>、<code>height</code> 为读取的宽高。</p><p>新建一个画布,通过 <code>canvas.getContext('2d')</code> 方法获取 2D 的对象,此对象有个 <code>ctx.putImageData(imagedata, dx, dy)</code> 可以让画布绘制 <code>ImageData(data, width, height)</code> 实例类型的矩形像素,<code>data</code> 为 <code>Uint8ClampedArray(length)</code> 实例,<code>width</code>、<code>height</code> 为图片的宽和高(必须保证 <code>Uint8ClampedArray</code> 的 <code>length = 4 * width * height</code> 才不会报错)。</p><p>指定 canvas 元素,并获取 2D 对象</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> preview = <span class="built_in">document</span>.getElementById(<span class="string">'preview'</span>);</span><br><span class="line"><span class="keyword">const</span> ctx = preview.getContext(<span class="string">'2d'</span>);</span><br></pre></td></tr></table></figure><p>创建 buffer 用来存放 RenderTarget 的像素数据,实例化 <code>Uint8ClampedArray</code> 以便创建 <code>ImageData</code> 时使用。在处理图片像素中与 <code>Uint8Array</code> 相比 <code>Uint8ClampedArray</code> 更安全,因为 <code>Uint8ClampedArray</code> 能保证写入的值在 0-255 之间,当写入的值小于 0 时会自动改为 0,当大于 255 时会自动改为 255。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> buffer = <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(RT_SIZE * RT_SIZE * <span class="number">4</span>);</span><br><span class="line"><span class="keyword">const</span> clamped = <span class="keyword">new</span> <span class="built_in">Uint8ClampedArray</span>(buffer.buffer);</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Renderer.readRenderTargetPixels(RenderTarget, <span class="number">0</span>, <span class="number">0</span>, RT_SIZE, RT_SIZE, buffer); <span class="comment">// 读取像素到 buffer</span></span><br><span class="line"><span class="keyword">const</span> imageData = <span class="keyword">new</span> ImageData(clamped, RT_SIZE, RT_SIZE); <span class="comment">// 创建可供 canvas 使用的图像数据类型</span></span><br><span class="line">ctx.putImageData(imageData, <span class="number">0</span>, <span class="number">0</span>); <span class="comment">// 绘制到 canvas 中</span></span><br></pre></td></tr></table></figure><p>实例 Demo</p><iframe src="https://sanonz.github.io/threejs-examples/render-target/scene-to-image-data.html" width="100%" height="300" frameborder="0" loading="lazy" allowfullscreen></iframe><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>至此结束,完整代码请查看例子,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="RenderTarget-介绍"><a href="#RenderTarget-介绍" class="headerlink" title="RenderTarget 介绍"></a>RenderTarget 介绍</h2><p>在渲染的时候调用 <code>renderer.render(scene, camera, renderTarget, forceClear)</code> 方法,<code>render</code> 方法有四个参数,我们平时使用只传前两个参数,第一个参数是要绘制的场景,第二个参数是指定相机,相机照射的区域会转换成 2D 绘制到屏幕,而我们今天要讲的就是使用第三个参数,不让渲染的内容直接绘制到屏幕,而是存放到 <code>renderTarget</code> 里。RenderTarget 是一个缓冲区,用来记录渲染后的像素,而不是直接绘制到屏幕上,因此我们可以在绘制屏幕之前做一些处理,以满足特殊的需求。</p><h2 id="把-RenderTarget-作为贴图使用"><a href="#把-RenderTarget-作为贴图使用" class="headerlink" title="把 RenderTarget 作为贴图使用"></a>把 RenderTarget 作为贴图使用</h2><p>首先创建两个场景:一个 <code>RTScene</code> 场景,用来渲染 RenderTarget 贴图、另一个 <code>Scene</code> 场景,用来渲染最终显示到屏幕上的,并调用 <code>RTScene</code> 场景作为贴图。</p><p>创建一个尺寸为 300×300 大小的 RenderTarget 存放 RTScene 的渲染结果</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> RT_SIZE = <span class="number">300</span>;</span><br><span class="line"><span class="keyword">const</span> RenderTarget = <span class="keyword">new</span> THREE.WebGLRenderTarget( RT_SIZE, RT_SIZE);</span><br></pre></td></tr></table></figure>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="WebGL" scheme="https://sanonz.github.io/tags/WebGL/"/>
<category term="Three.js" scheme="https://sanonz.github.io/tags/Three-js/"/>
</entry>
<entry>
<title>使用 redux-store-provider 简化 react 开发流程</title>
<link href="https://sanonz.github.io/2018/redux-store-provider-example/"/>
<id>https://sanonz.github.io/2018/redux-store-provider-example/</id>
<published>2018-09-19T19:50:00.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>仓库地址: <a href="https://github.com/sanonz/redux-store-provider" target="_blank" rel="noopener">https://github.com/sanonz/redux-store-provider</a><br>演示地址: <a href="https://sanonz.github.io/redux-store-provider/examples/">https://sanonz.github.io/redux-store-provider/examples/</a></p><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install react react-dom react-redux lodash redux-store-provider blueimp-md5 --save</span><br></pre></td></tr></table></figure><h2 id="创建-index-html"><a href="#创建-index-html" class="headerlink" title="创建 index.html"></a>创建 <code>index.html</code></h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Redux Store Provider Example<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"https://unpkg.com/[email protected]/dist/css/bootstrap.min.css"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span></span><br><span class="line"><span class="css"> <span class="selector-class">.content</span>{<span class="attribute">margin-top</span>:<span class="number">20px</span>;}</span></span><br><span class="line"> <span class="tag"></<span class="name">style</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"root"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="创建-users-json-和-posts-json-存放模拟请求数据"><a href="#创建-users-json-和-posts-json-存放模拟请求数据" class="headerlink" title="创建 users.json 和 posts.json 存放模拟请求数据"></a>创建 <code>users.json</code> 和 <code>posts.json</code> 存放模拟请求数据</h2><p><code>data/users.json</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"Sanonz"</span>,</span><br><span class="line"> <span class="attr">"email"</span>: <span class="string">"[email protected]"</span></span><br><span class="line"> }, {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"Toni Schneider"</span>,</span><br><span class="line"> <span class="attr">"email"</span>: <span class="string">"[email protected]"</span></span><br><span class="line"> }, {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"Beau Lebens"</span>,</span><br><span class="line"> <span class="attr">"email"</span>: <span class="string">"[email protected]"</span></span><br><span class="line"> }</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p><code>data/posts.json</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"primary"</span>,</span><br><span class="line"> <span class="attr">"text"</span>: <span class="string">"This is a primary alert—check it out!"</span>,</span><br><span class="line"> <span class="attr">"isLike"</span>: <span class="literal">false</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">2</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"secondary"</span>,</span><br><span class="line"> <span class="attr">"text"</span>: <span class="string">"This is a secondary alert—check it out!"</span>,</span><br><span class="line"> <span class="attr">"isLike"</span>: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">3</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"success"</span>,</span><br><span class="line"> <span class="attr">"text"</span>: <span class="string">"This is a success alert—check it out!"</span>,</span><br><span class="line"> <span class="attr">"isLike"</span>: <span class="literal">false</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">4</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"danger"</span>,</span><br><span class="line"> <span class="attr">"text"</span>: <span class="string">"This is a danger alert—check it out!"</span>,</span><br><span class="line"> <span class="attr">"isLike"</span>: <span class="literal">false</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">5</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"warning"</span>,</span><br><span class="line"> <span class="attr">"text"</span>: <span class="string">"This is a warning alert—check it out!"</span>,</span><br><span class="line"> <span class="attr">"isLike"</span>: <span class="literal">false</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">6</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"info"</span>,</span><br><span class="line"> <span class="attr">"text"</span>: <span class="string">"This is a info alert—check it out!"</span>,</span><br><span class="line"> <span class="attr">"isLike"</span>: <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="创建-user-js-和-posts-js-Reducer"><a href="#创建-user-js-和-posts-js-Reducer" class="headerlink" title="创建 user.js 和 posts.js Reducer"></a>创建 <code>user.js</code> 和 <code>posts.js</code> Reducer</h2><p><code>providers/user.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> ReduxStoreProvider <span class="keyword">from</span> <span class="string">'redux-store-provider'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> users <span class="keyword">from</span> <span class="string">'./data/users.json'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">new</span> ReduxStoreProvider({ <span class="attr">key</span>: <span class="string">'USER'</span> })</span><br><span class="line"> .setInitialState({<span class="attr">value</span>: users[<span class="number">0</span>]});</span><br></pre></td></tr></table></figure><p><code>providers/posts.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> lodash <span class="keyword">from</span> <span class="string">'lodash'</span>;</span><br><span class="line"><span class="keyword">import</span> ReduxStoreProvider <span class="keyword">from</span> <span class="string">'redux-store-provider'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">new</span> ReduxStoreProvider({ <span class="attr">key</span>: <span class="string">'POST'</span>, <span class="attr">type</span>: <span class="string">'list'</span> })</span><br><span class="line"> .begin(<span class="string">'like'</span>)</span><br><span class="line"> <span class="comment">// 定义触发 Reducer 的 Action</span></span><br><span class="line"> .action(<span class="function">(<span class="params">type, value</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> type,</span><br><span class="line"> value,</span><br><span class="line"> };</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 定义 Reducer</span></span><br><span class="line"> .reducer(<span class="function">(<span class="params">type, state, action</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> newState = state;</span><br><span class="line"> <span class="keyword">let</span> index = state.list.findIndex(<span class="function"><span class="params">row</span> =></span> row === action.value);</span><br><span class="line"> <span class="keyword">if</span> (!!~index) {</span><br><span class="line"> newState = lodash.cloneDeep(state);</span><br><span class="line"> <span class="keyword">const</span> row = newState.list[index];</span><br><span class="line"> row.isLike = !row.isLike;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> newState;</span><br><span class="line"> })</span><br><span class="line"> .end();</span><br></pre></td></tr></table></figure><h2 id="创建-users-js-和-posts-js-Action"><a href="#创建-users-js-和-posts-js-Action" class="headerlink" title="创建 users.js 和 posts.js Action"></a>创建 <code>users.js</code> 和 <code>posts.js</code> Action</h2><p><code>actions.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> UserStore <span class="keyword">from</span> <span class="string">'../providers/user'</span>;</span><br><span class="line"><span class="keyword">import</span> PostsStore <span class="keyword">from</span> <span class="string">'../providers/posts'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> userAction = UserStore.getAction();</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> postsAction = PostsStore.getAction();</span><br></pre></td></tr></table></figure><h2 id="创建-UI-组件"><a href="#创建-UI-组件" class="headerlink" title="创建 UI 组件"></a>创建 UI 组件</h2><p><code>components/Header.jsx</code></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> md5 <span class="keyword">from</span> <span class="string">'blueimp-md5'</span>;</span><br><span class="line"><span class="keyword">import</span> { connect } <span class="keyword">from</span> <span class="string">'react-redux'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@connect(<span class="function"><span class="params">state</span> =></span> ({<span class="attr">userStore</span>: state.userStore}))</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Header</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">const</span> { userStore } = <span class="keyword">this</span>.props;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <nav className=<span class="string">"navbar navbar-dark bg-dark"</span>></span><br><span class="line"> <div className=<span class="string">"container"</span>></span><br><span class="line"> <a className=<span class="string">"navbar-brand"</span> href=<span class="string">"#"</span>></span><br><span class="line"> ReduxStoreProvider</span><br><span class="line"> <<span class="regexp">/a></span></span><br><span class="line"><span class="regexp"> <span className="navbar-text"></span></span><br><span class="line"><span class="regexp"> <img</span></span><br><span class="line"><span class="regexp"> className="d-inline-block align-middle"</span></span><br><span class="line"><span class="regexp"> src={`https:/</span><span class="regexp">/www.gravatar.com/</span>avatar/${md5(userStore.value.email)}?s=<span class="number">40</span><span class="string">`}</span></span><br><span class="line"><span class="string"> width="20"</span></span><br><span class="line"><span class="string"> height="20"</span></span><br><span class="line"><span class="string"> /></span></span><br><span class="line"><span class="string"> &ensp;</span></span><br><span class="line"><span class="string"> <span className="d-inline-block align-middle">{userStore.value.name}</span></span></span><br><span class="line"><span class="string"> </span></span></span><br><span class="line"><span class="string"> </div></span></span><br><span class="line"><span class="string"> </nav></span></span><br><span class="line"><span class="string"> );</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">};</span></span><br></pre></td></tr></table></figure><p><code>components/Posts.jsx</code></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> { connect } <span class="keyword">from</span> <span class="string">'react-redux'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> { postsAction } <span class="keyword">from</span> <span class="string">'./actions.js'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">connect(<span class="function"><span class="params">state</span> =></span> ({<span class="attr">userStore</span>: state.userStore, <span class="attr">postsStore</span>: state.postsStore}))</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Posts</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"></span><br><span class="line"> componentDidMount() {</span><br><span class="line"> <span class="keyword">const</span> { dispatch } = <span class="keyword">this</span>.props;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 模拟HTTP请求数据</span></span><br><span class="line"> fetch(<span class="string">'./data/post.json'</span>)</span><br><span class="line"> .then(<span class="function"><span class="params">response</span> =></span> response.json())</span><br><span class="line"> .then(<span class="function"><span class="params">rows</span> =></span> {</span><br><span class="line"> <span class="comment">// 请求的数据填充到 PostsStore 中</span></span><br><span class="line"> dispatch(postsAction.fill(rows);</span><br><span class="line"> }));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> onLike(e, row) {</span><br><span class="line"> <span class="keyword">const</span> { dispatch } = <span class="keyword">this</span>.props;</span><br><span class="line"> dispatch(postAction.like(row));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> onRemove(e, index) {</span><br><span class="line"> <span class="keyword">const</span> { dispatch } = <span class="keyword">this</span>.props;</span><br><span class="line"> dispatch(postAction.remove(index));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">const</span> { postsStore } = <span class="keyword">this</span>.props;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> {postsStore.list.map(<span class="function">(<span class="params">row, index</span>) =></span></span><br><span class="line"> <div</span><br><span class="line"> key={row.id}</span><br><span class="line"> className={<span class="string">`alert alert-<span class="subst">${row.type}</span>`</span>}</span><br><span class="line"> role=<span class="string">"alert"</span></span><br><span class="line"> ></span><br><span class="line"> {row.text}</span><br><span class="line"> &ensp;</span><br><span class="line"> <button</span><br><span class="line"> type=<span class="string">"button"</span></span><br><span class="line"> className={<span class="string">`btn btn-sm btn-<span class="subst">${row.isLike ? <span class="string">'success'</span> : <span class="string">'light'</span>}</span>`</span>}</span><br><span class="line"> onClick={e => <span class="keyword">this</span>.onLike(e, row)}</span><br><span class="line"> ></span><br><span class="line"> Like</span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> <button</span></span><br><span class="line"><span class="regexp"> type="button"</span></span><br><span class="line"><span class="regexp"> className="close"</span></span><br><span class="line"><span class="regexp"> aria-label="Close"</span></span><br><span class="line"><span class="regexp"> onClick={e => this.onRemove(e, index)}</span></span><br><span class="line"><span class="regexp"> ></span></span><br><span class="line"><span class="regexp"> <span aria-hidden="true">&times;</</span>span></span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> )}</span><br><span class="line"> <<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> );</span></span><br><span class="line"><span class="regexp"> }</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">};</span></span><br></pre></td></tr></table></figure><p><code>App.jsx</code></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> lodash <span class="keyword">from</span> <span class="string">'lodash'</span>;</span><br><span class="line"><span class="keyword">import</span> { connect } <span class="keyword">from</span> <span class="string">'react-redux'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> Header <span class="keyword">from</span> <span class="string">'./components/Header'</span>;</span><br><span class="line"><span class="keyword">import</span> Posts <span class="keyword">from</span> <span class="string">'./components/Posts'</span>;</span><br><span class="line"><span class="keyword">import</span> { userAction, postsAction } <span class="keyword">from</span> <span class="string">'./actions'</span>;</span><br><span class="line"><span class="keyword">import</span> users <span class="keyword">from</span> <span class="string">'./data/users.json'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@connect(<span class="function"><span class="params">state</span> =></span> ({<span class="attr">userStore</span>: state.userStore, <span class="attr">postsStore</span>: state.postsStore}))</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"></span><br><span class="line"> onRandomUser() {</span><br><span class="line"> <span class="keyword">const</span> { dispatch, userStore } = <span class="keyword">this</span>.props;</span><br><span class="line"> <span class="keyword">const</span> data = users.filter(<span class="function"><span class="params">user</span> =></span> user.email !== userStore.value.email);</span><br><span class="line"> dispatch(userAction.merge(lodash.sample(data)));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> onShufflePosts() {</span><br><span class="line"> <span class="keyword">const</span> { dispatch, postsStore } = <span class="keyword">this</span>.props;</span><br><span class="line"> dispatch(postsAction.fill(lodash.shuffle(postsStore.list)));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> onAddPost() {</span><br><span class="line"> <span class="keyword">const</span> { dispatch, postsStore } = <span class="keyword">this</span>.props;</span><br><span class="line"> dispatch(postAction.push(lodash.sample(postsStore.list)));</span><br><span class="line"> <span class="comment">// dispatch(postAction.unshift(lodash.sample(postsStore.list)));</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div className=<span class="string">"app"</span>></span><br><span class="line"> <Header /></span><br><span class="line"> <div className=<span class="string">"container"</span>></span><br><span class="line"> <div className=<span class="string">"content"</span>></span><br><span class="line"> <div className=<span class="string">"row"</span>></span><br><span class="line"> <div className=<span class="string">"col-sm-4"</span>></span><br><span class="line"> <div className=<span class="string">"list-group"</span>></span><br><span class="line"> <button</span><br><span class="line"> type=<span class="string">"button"</span></span><br><span class="line"> className=<span class="string">"list-group-item list-group-item-action"</span></span><br><span class="line"> onClick={e => <span class="keyword">this</span>.onAddPost(e)}</span><br><span class="line"> ></span><br><span class="line"> Add Post</span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> <button</span></span><br><span class="line"><span class="regexp"> type="button"</span></span><br><span class="line"><span class="regexp"> className="list-group-item list-group-item-action"</span></span><br><span class="line"><span class="regexp"> onClick={e => this.onShufflePosts(e)}</span></span><br><span class="line"><span class="regexp"> ></span></span><br><span class="line"><span class="regexp"> Shuffle Posts</span></span><br><span class="line"><span class="regexp"> </</span>button></span><br><span class="line"> <button</span><br><span class="line"> type=<span class="string">"button"</span></span><br><span class="line"> className=<span class="string">"list-group-item list-group-item-action"</span></span><br><span class="line"> onClick={e => <span class="keyword">this</span>.onRandomUser(e)}</span><br><span class="line"> ></span><br><span class="line"> Random User</span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> <<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> <div className="col-sm-8"></span></span><br><span class="line"><span class="regexp"> <Posts /</span>></span><br><span class="line"> <<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> <<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> <<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> );</span></span><br><span class="line"><span class="regexp"> }</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">}</span></span><br></pre></td></tr></table></figure><h2 id="创建-APP"><a href="#创建-APP" class="headerlink" title="创建 APP"></a>创建 APP</h2><p><code>setup.js</code></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./App'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> UserStore <span class="keyword">from</span> <span class="string">'./providers/user'</span>;</span><br><span class="line"><span class="keyword">import</span> PostsStore <span class="keyword">from</span> <span class="string">'./providers/posts'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = Redux.createStore(</span><br><span class="line"> Redux.combineReducers({</span><br><span class="line"> userStore: UserStore.getReducer(),</span><br><span class="line"> postsStore: PostsStore.getReducer(),</span><br><span class="line"> })</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">ReactDOM.render(</span><br><span class="line"> <ReactRedux.Provider store={store}></span><br><span class="line"> <App /></span><br><span class="line"> <<span class="regexp">/ReactRedux.Provider>,</span></span><br><span class="line"><span class="regexp"> document.getElementById('root')</span></span><br><span class="line"><span class="regexp">);</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>仓库地址: <a href="https://github.com/sanonz/redux-store-provider" target="_blank" rel="noopener">https://github.com/sanonz/redux-store-provider</a><br>演示地址: <a href="https://sanonz.github.io/redux-store-provider/examples/">https://sanonz.github.io/redux-store-provider/examples/</a></p><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install react react-dom react-redux lodash redux-store-provider blueimp-md5 --save</span><br></pre></td></tr></table></figure><h2 id="创建-index-html"><a href="#创建-index-html" class="headerlink" title="创建 index.html"></a>创建 <code>index.html</code></h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">title</span>&gt;</span>Redux Store Provider Example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"https://unpkg.com/[email protected]/dist/css/bootstrap.min.css"</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css"> <span class="selector-class">.content</span>&#123;<span class="attribute">margin-top</span>:<span class="number">20px</span>;&#125;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"root"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="React" scheme="https://sanonz.github.io/tags/React/"/>
<category term="Redux" scheme="https://sanonz.github.io/tags/Redux/"/>
<category term="Store" scheme="https://sanonz.github.io/tags/Store/"/>
</entry>
<entry>
<title>浏览器 HTTP 并发请求规则探讨</title>
<link href="https://sanonz.github.io/2018/http-max-persistent-connections-per-server/"/>
<id>https://sanonz.github.io/2018/http-max-persistent-connections-per-server/</id>
<published>2018-03-13T02:34:36.000Z</published>
<updated>2024-01-24T02:42:46.279Z</updated>
<content type="html"><![CDATA[<h2 id="并发请求介绍"><a href="#并发请求介绍" class="headerlink" title="并发请求介绍"></a>并发请求介绍</h2><p>由于端口数量和线程切换开销的考虑,浏览器不能无限量的并发请求,再者将所有请求一起发给服务器,也很可能会引发服务器的并发阈值控制而被 BAN,而另外一个控制的原因是 keep alive 技术的存在使得浏览器复用现有连接和服务器通信比创建新连接的性能要更好一些,出于客户端和服务端因素的综合考虑,因此衍生出来了并发限制。</p><p>以下为各大浏览器并发请求限制的数量(只在同一与域名下有效)</p><table><thead><tr><th>浏览器</th><th>HTTP 1.1</th><th>HTTP 1.0</th></tr></thead><tbody><tr><td>IE 6, 7</td><td>2</td><td>4</td></tr><tr><td>IE 8, 9</td><td>6</td><td>6</td></tr><tr><td>Firefox 13</td><td>6</td><td>6</td></tr><tr><td>Chrome 20</td><td>6</td><td>?</td></tr><tr><td>Safari 5.1.7</td><td>6</td><td>?</td></tr><tr><td>Opera 11.64</td><td>8</td><td>?</td></tr></tbody></table><a id="more"></a><h2 id="并发请求测试"><a href="#并发请求测试" class="headerlink" title="并发请求测试"></a>并发请求测试</h2><p>以上数据来源于网络,以下针对与 <code>Chrome 65</code> 和 <code>Firefox 58</code> 分别进行了测试,思路是首先写一个循环 12 次的请求,打开控制台的 Network 版面,使用录屏软件进行录取,然后慢放帧查看请求的状态,其实不用录屏直接看也能看出效果,眼睛要盯紧一点。</p><p>新建一个测试网页</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Document<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>></span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> startTime = <span class="built_in">Date</span>.now();</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> q = <span class="keyword">new</span> <span class="built_in">Array</span>(<span class="number">12</span>).fill(<span class="number">0</span>).map(<span class="function"><span class="params">v</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> p = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">r</span>) =></span> {</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> xhr = <span class="keyword">new</span> XMLHttpRequest();</span></span><br><span class="line"></span><br><span class="line"><span class="javascript"> xhr.onreadystatechange = <span class="function"><span class="params">()</span> =></span> {</span></span><br><span class="line"> if (xhr.readyState === 4 && xhr.status === 200) {</span><br><span class="line"> r();</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"><span class="actionscript"> xhr.open(<span class="string">'GET'</span>, <span class="string">'https://hacker-news.firebaseio.com/v0/topstories.json'</span>, <span class="literal">true</span>);</span></span><br><span class="line"> xhr.send();</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="keyword">return</span> p;</span></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"><span class="javascript"> <span class="built_in">Promise</span>.all(q).then(<span class="function"><span class="params">rs</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(<span class="built_in">Date</span>.now() - startTime);</span></span><br><span class="line"> });</span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>以下为录取 Chrome Network 控制版面的慢放帧,可以看出在 12 个请求中,先发出前 6 个请求,后 6 个处于 <code>pending</code> 状态,当前 6 个请求完成时余下的请求才发出。</p><img src="/2018/http-max-persistent-connections-per-server/chrome-max-persistent-connections-per-server-6.gif" width="515"><p>以下为使用 Firefox 的测试结果,可见和 Chrome 的一样</p><img src="/2018/http-max-persistent-connections-per-server/firefox-max-persistent-connections-per-server-6.gif" width="1152"><h2 id="修改并发请求数量"><a href="#修改并发请求数量" class="headerlink" title="修改并发请求数量"></a>修改并发请求数量</h2><p>浏览器这样做出发点是好的,但有时候我们想要个性化定制(比如做个插件开个挂之类的😈)就没那么方便了,那能不能修改限制上限呢?</p><ol><li>Chrome 的并发请求数量是不能修改的,因为已经固定写到源码中了,具体可以查看:<a href="https://chromium.googlesource.com/chromium/src/+/65.0.3325.162/net/socket/client_socket_pool_manager.cc#44" target="_blank" rel="noopener">https://chromium.googlesource.com/chromium/src/+/65.0.3325.162/net/socket/client_socket_pool_manager.cc#44</a>,可以使用 Firefox,如果非要修改可以尝试修改源码然后自行编译。</li><li>Firefox 是可以修改的,想要修改首先在地址栏输入 <code>about:config</code>,搜索 <code>http.max</code> 关键字,<code>network.http.max-connections</code> 为全局 HTTP 同时最大的连接数量,默认为 900;<code>network.http.max-persistent-connections-per-server</code> 为单个域名最大链接数量,默认为 6,双击此列可以进行修改。</li></ol><p>这里我们把 <code>network.http.max-persistent-connections-per-server</code> 从 6 修改到 8 然后再次进行测试</p><img src="/2018/http-max-persistent-connections-per-server/firefox-max-persistent-connections-per-server-8.gif" width="1152"><p>我们可以看到请求 <code>topstories.json</code> 第一次请求发出的 8 个,剩余 4 个处于等待状态,当 8 个请求完成时余下 4 个才发出,这里测试的时候网络比较稳定,前 8 个几乎同时发出和返回,所以后 4 个几乎一块发出。</p><p>我们分别进行两轮测试,分别在看下控制台打印的 500 个请求总共消耗的时间(单位ms)进行结果再次确认</p><table><thead><tr><th></th><th>并发 6</th><th>并发 12</th></tr></thead><tbody><tr><td>1</td><td>16554</td><td>8631</td></tr><tr><td>2</td><td>18037</td><td>9427</td></tr></tbody></table><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>随着现在网站越来越丰富化,并发 6 个请求可能已经满足不了千变万化的需求,这时我们可以使用不同的域名进行资源分离,这样就可以绕过单个域名只能并发请求 6 个资源的限制,在加上 <code>dns-prefetch</code> 特性来提升网站打开速度而又不丢失站点的丰富性,简直完美 👏👏👏。</p><p>以上只测试了 <code>Chrome 65</code> 和 <code>Firefox 58</code> 有兴趣的可以使用这种方法测试其他浏览器,测试完欢迎回来交流。</p><p>至此结束,感谢阅读。</p>]]></content>
<summary type="html">
<h2 id="并发请求介绍"><a href="#并发请求介绍" class="headerlink" title="并发请求介绍"></a>并发请求介绍</h2><p>由于端口数量和线程切换开销的考虑,浏览器不能无限量的并发请求,再者将所有请求一起发给服务器,也很可能会引发服务器的并发阈值控制而被 BAN,而另外一个控制的原因是 keep alive 技术的存在使得浏览器复用现有连接和服务器通信比创建新连接的性能要更好一些,出于客户端和服务端因素的综合考虑,因此衍生出来了并发限制。</p><p>以下为各大浏览器并发请求限制的数量(只在同一与域名下有效)</p><table><thead><tr><th>浏览器</th><th>HTTP 1.1</th><th>HTTP 1.0</th></tr></thead><tbody><tr><td>IE 6, 7</td><td>2</td><td>4</td></tr><tr><td>IE 8, 9</td><td>6</td><td>6</td></tr><tr><td>Firefox 13</td><td>6</td><td>6</td></tr><tr><td>Chrome 20</td><td>6</td><td>?</td></tr><tr><td>Safari 5.1.7</td><td>6</td><td>?</td></tr><tr><td>Opera 11.64</td><td>8</td><td>?</td></tr></tbody></table>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
</entry>
<entry>
<title>JavaScript Event Loop、Micro Task、Macro Task</title>
<link href="https://sanonz.github.io/2018/javascript-event-loop-microtasks-macrotasks/"/>
<id>https://sanonz.github.io/2018/javascript-event-loop-microtasks-macrotasks/</id>
<published>2018-02-24T08:23:50.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="Event-Loop"><a href="#Event-Loop" class="headerlink" title="Event Loop"></a>Event Loop</h2><p>Event Loop:事件循环、Micro Task:微任务、Macro Task:宏任务</p><p>我们知道 JavaScript 是单线程语言,一心不能二用,也就是说它只能把一件事干完才能去干另一件事,但前端的一些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让它们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以为了保证使用流畅,浏览器采用了 Event Loop 事件循环系统来管理,把那些耗时的任务放入 task queues 栈中,等主程序执行完再执行 task queues 中的任务,通常使用回调函数监听 task queues 中的任务状态。</p><p>在其它语言中可以迟延执行,比如 C 语言的 <code>sleep(3)</code> 可以迟延 3 秒执行后边的逻辑,但是在 JavaScript 中是没有这样的操作(除了 <code>alert</code>、<code>confrim</code>、<code>prompt</code> 和异步 <code>xhr</code>,官方说这些是历史遗留的错误设计😂),只能使用异步方式去模拟,模拟的实际也不是真正意义上的 <code>sleep</code> 效果。</p><a id="more"></a><h2 id="示例代码"><a href="#示例代码" class="headerlink" title="示例代码"></a>示例代码</h2><p>在讨论 Micro Task、Macro Task 之前我们先来看一个例子,分析以下代码并思考 log 打的印顺序</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'script start'</span>);</span><br><span class="line"></span><br><span class="line">setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'setTimeout 1'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.resolve()</span><br><span class="line">.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'promise 1'</span>);</span><br><span class="line">})</span><br><span class="line">.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'promise 2'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'script end'</span>);</span><br></pre></td></tr></table></figure><p>运行结果为</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">script start</span><br><span class="line">script end</span><br><span class="line">promise 1</span><br><span class="line">promise 2</span><br><span class="line">setTimeout 1</span><br></pre></td></tr></table></figure><p>有人可能会有疑问,为什么不是按顺序输出?又或者是这样</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">script start</span><br><span class="line">script end</span><br><span class="line">setTimeout 1</span><br><span class="line">promise 1</span><br><span class="line">promise 2</span><br></pre></td></tr></table></figure><h2 id="Micro-Task、Macro-Task"><a href="#Micro-Task、Macro-Task" class="headerlink" title="Micro Task、Macro Task"></a>Micro Task、Macro Task</h2><p>想要搞明白这个问题,首先要了解 Event Loop 下的 <code>Micro Task</code> 和 <code>Macro Task</code>,在运行主程序时会把异步逻辑根据情况推到 <code>Micro Task</code> 或者 <code>Macro Task</code> 栈中,具体规则如下</p><p>以下情况会推到 <strong>Micro Task</strong> 栈中</p><ul><li>process.nextTick</li><li>Promise</li><li>Object.observe</li><li>MutationObserver</li></ul><p>以下情况会推到 <strong>Macro Task</strong> 栈中</p><ul><li>setTimeout</li><li>setInterval</li><li>setImmediate</li><li>I/O</li><li>UI render</li></ul><p>然后再来看实例代码的实际执行逻辑</p><ol><li>执行 <code>console.log('script start')</code> 直接输出</li><li>执行 <code>setTimeout</code> 把回调函数放入 <strong>Macro Task</strong> 栈中</li><li>执行 <code>Promise</code> 把两个 then 的回调函数放入 <strong>Micro Task</strong> 栈中</li><li>执行 <code>console.log('script end')</code> 直接输出</li><li>执行 <strong>Micro Task</strong> 中所有任务<ol><li>执行 <code>console.log('promise 1')</code> 直接输出</li><li>执行 <code>console.log('promise 2')</code> 直接输出</li></ol></li><li>执行 <strong>Macro Task</strong> 中所有任务<ol><li>执行 <code>console.log('setTimeout 1')</code> 直接输出</li></ol></li></ol><h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>事件循环的顺序,决定了 JavaScript 代码的执行顺序。它从 script (整体代码) 开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的 Micro Task。当所有可执行的 Micro Task 执行完毕之后。循环再次从 Macro Task 开始,找到其中一个任务队列执行完毕,然后再执行所有的 Micro Task,在执行 Micro Task、Macro Task 的时候同样遵循 Event Loop 原则,就这样一直循环下去。</p><p>至此结束,感谢阅读。<br><br></p><p>参考站点</p><ul><li><a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loop" target="_blank" rel="noopener">https://html.spec.whatwg.org/multipage/webappapis.html#event-loop</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop" target="_blank" rel="noopener">https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop</a></li></ul>]]></content>
<summary type="html">
<h2 id="Event-Loop"><a href="#Event-Loop" class="headerlink" title="Event Loop"></a>Event Loop</h2><p>Event Loop:事件循环、Micro Task:微任务、Macro Task:宏任务</p><p>我们知道 JavaScript 是单线程语言,一心不能二用,也就是说它只能把一件事干完才能去干另一件事,但前端的一些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让它们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以为了保证使用流畅,浏览器采用了 Event Loop 事件循环系统来管理,把那些耗时的任务放入 task queues 栈中,等主程序执行完再执行 task queues 中的任务,通常使用回调函数监听 task queues 中的任务状态。</p><p>在其它语言中可以迟延执行,比如 C 语言的 <code>sleep(3)</code> 可以迟延 3 秒执行后边的逻辑,但是在 JavaScript 中是没有这样的操作(除了 <code>alert</code>、<code>confrim</code>、<code>prompt</code> 和异步 <code>xhr</code>,官方说这些是历史遗留的错误设计😂),只能使用异步方式去模拟,模拟的实际也不是真正意义上的 <code>sleep</code> 效果。</p>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
</entry>
<entry>
<title>WebAssembly 从入门到吃鸡</title>
<link href="https://sanonz.github.io/2018/webassembly-tutorial/"/>
<id>https://sanonz.github.io/2018/webassembly-tutorial/</id>
<published>2018-01-12T06:58:00.000Z</published>
<updated>2024-01-24T02:42:46.287Z</updated>
<content type="html"><![CDATA[<h2 id="WebAssembly-介绍"><a href="#WebAssembly-介绍" class="headerlink" title="WebAssembly 介绍"></a>WebAssembly 介绍</h2><p>我们知道 JavaScript 是一门脚本语言,具有动态类型和灵活的表达力,我们知道脚本语言通常要解释运行,这也将要消耗一些性能开销,于是 Google 在 2009 年在 V8 中引入了 JIT(Just in time compiling) 技术,把 JavaScript 运行时的性能推到了顶峰,同年利用 V8 引擎诞生了 Node.js,打开了使用 JavaScript 写后端应用的大门。对于目前写网络应用而言 JavaScript 已经足够用了,加上 Google V8 引擎能帮我们解决掉大部分问题。但是当我们把 JavaScript 应用到诸如 3D 游戏、虚拟现实、增强现实、计算机视觉、图像/视频编辑以及大量的要求原生性能的其他领域的时候,就遇到了性能问题,尤其是移动平台进一步放大了这些性能瓶颈。</p><p>而 WebAssembly 的出现就是为了解决这个问题,它是一门低级的类汇编语言,可以运行在现代网络浏览器中的新型代码并且提供新的性能特性和效果。它设计的目的不是为了手写代码,而是为了诸如 C、C++ 和 Rust 等低级源语言提供一个高效的编译目标以便它们能够在网络上运行。对于网络平台而言,这具有巨大的意义——这为客户端 app 提供了一种在网络平台以接近本地速度的方式运行多种语言编写代码的方式;在这之前,客户端 app 是不可能做到的。</p><a id="more"></a><h2 id="WebAssembly-使用"><a href="#WebAssembly-使用" class="headerlink" title="WebAssembly 使用"></a>WebAssembly 使用</h2><p>WebAssembly 有二进制 (.wasm) 和文本 (.wast) 两种格式,并且两种格式可以互享转换,在传输和运行时使用二进制格式,而文本格式是为了阅读和开发调试所使用。</p><p>以 C 为例,首先把 C 程序编译为 WebAssembly 格式,本地编译可以查看 <a href="https://developer.mozilla.org/zh-CN/docs/WebAssembly/C_to_wasm" target="_blank" rel="noopener">编译 C/C++ 为 WebAssembly</a> 文章进行相关软件的安装,为了方便可以直接使用这款在线工具进行转换 <a href="https://wasdk.github.io/WasmFiddle/" target="_blank" rel="noopener">WasmFiddle</a></p><p>我们先来个简单的,在 <code>main</code> 中返回一个数字,在 <code>add</code> 方法中求和</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">return</span> <span class="number">42</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">add</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a + b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>转换过后的 .wast 文本格式为,我们可以从这个文本看出分别 export 了 <code>main</code> 与 <code>add</code> 方法</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">(module</span><br><span class="line"> (table 0 anyfunc)</span><br><span class="line"> (memory $0 1)</span><br><span class="line"> (export "memory" (memory $0))</span><br><span class="line"> (export "main" (func $main))</span><br><span class="line"> (export "add" (func $add))</span><br><span class="line"> (func $main (; 0 ;) (result i32)</span><br><span class="line"> (i32.const 42)</span><br><span class="line"> )</span><br><span class="line"> (func $add (; 1 ;) (param $0 i32) (param $1 i32) (result i32)</span><br><span class="line"> (i32.add</span><br><span class="line"> (get_local $1)</span><br><span class="line"> (get_local $0)</span><br><span class="line"> )</span><br><span class="line"> )</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" target="_blank" rel="noopener">Fetch</a> 获取模块并初始化调用</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'module.wasm'</span>)</span><br><span class="line"> .then(<span class="function"><span class="params">response</span> =></span> response.arrayBuffer())</span><br><span class="line"> .then(<span class="function"><span class="params">bytes</span> =></span> WebAssembly.instantiate(bytes))</span><br><span class="line"> .then(<span class="function"><span class="params">results</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">typeof</span> results.instance.exports.main); <span class="comment">// function</span></span><br><span class="line"> <span class="built_in">console</span>.log(results.instance.exports.main()); <span class="comment">// 42</span></span><br><span class="line"> <span class="built_in">console</span>.log(results.instance.exports.add(<span class="number">1</span>, <span class="number">2</span>)); <span class="comment">// 3</span></span><br><span class="line"> })</span><br></pre></td></tr></table></figure><p>我们可以使用 <code>results.instance.exports.main()</code> 的形式进行调用</p><p>使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest" target="_blank" rel="noopener">XMLHttpRequest</a> 获取模块并初始化调用</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> request = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line">request.open(<span class="string">'GET'</span>, <span class="string">'module.wasm'</span>);</span><br><span class="line">request.responseType = <span class="string">'arraybuffer'</span>;</span><br><span class="line"></span><br><span class="line">request.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> bytes = request.response;</span><br><span class="line"> WebAssembly</span><br><span class="line"> .instantiate(bytes)</span><br><span class="line"> .then(<span class="function"><span class="params">results</span> =></span> {</span><br><span class="line"> results.instance.exports.main();</span><br><span class="line"> });</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">request.send();</span><br></pre></td></tr></table></figure><p>目前只实现了 JavaScript 语言调用 C 语言,接下来使用 C 调用 JavaScript 方法,声明并在需要的地方调用 <code>alert</code> 方法</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">alert</span><span class="params">(<span class="keyword">int</span> a)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">add</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> alert(a + b);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>获取实例时传入 <code>importObj</code> 对象,把 <code>env</code> 环境变量注入进来</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> importObj = {</span><br><span class="line"> env: {</span><br><span class="line"> alert: <span class="built_in">window</span>.alert</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line">fetch(<span class="string">'module.wasm'</span>)</span><br><span class="line"> .then(<span class="function"><span class="params">response</span> =></span> response.arrayBuffer())</span><br><span class="line"> .then(<span class="function"><span class="params">bytes</span> =></span> WebAssembly.instantiate(bytes, importObj))</span><br><span class="line"> .then(<span class="function"><span class="params">results</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(results.instance.exports.add(<span class="number">1</span>, <span class="number">2</span>));</span><br><span class="line"> })</span><br></pre></td></tr></table></figure><p>至此相互调用演示完成,我们可以畅想下以后 WebAssembly 普及的话,到时我们用的 React、Vue、Angular 可能就是用 C/C++ 或者其他底层语言写的。</p>]]></content>
<summary type="html">
<h2 id="WebAssembly-介绍"><a href="#WebAssembly-介绍" class="headerlink" title="WebAssembly 介绍"></a>WebAssembly 介绍</h2><p>我们知道 JavaScript 是一门脚本语言,具有动态类型和灵活的表达力,我们知道脚本语言通常要解释运行,这也将要消耗一些性能开销,于是 Google 在 2009 年在 V8 中引入了 JIT(Just in time compiling) 技术,把 JavaScript 运行时的性能推到了顶峰,同年利用 V8 引擎诞生了 Node.js,打开了使用 JavaScript 写后端应用的大门。对于目前写网络应用而言 JavaScript 已经足够用了,加上 Google V8 引擎能帮我们解决掉大部分问题。但是当我们把 JavaScript 应用到诸如 3D 游戏、虚拟现实、增强现实、计算机视觉、图像/视频编辑以及大量的要求原生性能的其他领域的时候,就遇到了性能问题,尤其是移动平台进一步放大了这些性能瓶颈。</p><p>而 WebAssembly 的出现就是为了解决这个问题,它是一门低级的类汇编语言,可以运行在现代网络浏览器中的新型代码并且提供新的性能特性和效果。它设计的目的不是为了手写代码,而是为了诸如 C、C++ 和 Rust 等低级源语言提供一个高效的编译目标以便它们能够在网络上运行。对于网络平台而言,这具有巨大的意义——这为客户端 app 提供了一种在网络平台以接近本地速度的方式运行多种语言编写代码的方式;在这之前,客户端 app 是不可能做到的。</p>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
<category term="WebAssembly" scheme="https://sanonz.github.io/tags/WebAssembly/"/>
</entry>
<entry>
<title>Let's Encrypt 使用教程,免费的 SSL 证书,让你的网站拥抱 HTTPS</title>
<link href="https://sanonz.github.io/2017/let's-encrypt-free-ssl-https/"/>
<id>https://sanonz.github.io/2017/let's-encrypt-free-ssl-https/</id>
<published>2017-11-15T07:45:47.000Z</published>
<updated>2024-01-24T02:42:46.279Z</updated>
<content type="html"><![CDATA[<h2 id="Let’s-Encrypt-简介"><a href="#Let’s-Encrypt-简介" class="headerlink" title="Let’s Encrypt 简介"></a>Let’s Encrypt 简介</h2><p>Let’s Encrypt 是国外一个公共的免费SSL项目,由 Linux 基金会托管,它的来头不小,由 Mozilla、思科、Akamai、IdenTrust 和 EFF 等组织发起,目的就是向网站自动签发和管理免费证书,以便加速互联网由 HTTP 过渡到 HTTPS,目前 Facebook 等大公司开始加入赞助行列。</p><p>Let’s Encrypt 已经得了 IdenTrust 的交叉签名,这意味着其证书现在已经可以被 Mozilla、Google、Microsoft 和 Apple 等主流的浏览器所信任,你只需要在 Web 服务器证书链中配置交叉签名,浏览器客户端会自动处理好其它的一切,Let’s Encrypt 安装简单,使用非常方便。</p><h2 id="Certbot-简介"><a href="#Certbot-简介" class="headerlink" title="Certbot 简介"></a>Certbot 简介</h2><p><a href="https://certbot.eff.org/" target="_blank" rel="noopener">Certbot</a> 为 Let’s Encrypt 项目发布了一个官方的客户端 Certbot ,利用它可以完全自动化的获取、部署和更新安全证书,并且 Certbot 是支持所有 Unix 内核的操作系统。</p><h2 id="安装-Certbot-客户端"><a href="#安装-Certbot-客户端" class="headerlink" title="安装 Certbot 客户端"></a>安装 Certbot 客户端</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ yum install certbot <span class="comment"># centos</span></span><br><span class="line">$ <span class="comment"># apt install certbot # ubuntu</span></span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="Certbot-的两种使用方式"><a href="#Certbot-的两种使用方式" class="headerlink" title="Certbot 的两种使用方式"></a>Certbot 的两种使用方式</h2><ol><li><em>webroot</em> 方式: certbot 会利用既有的 web server,在其 web root 目录下创建隐藏文件,Let’s Encrypt 服务端会通过域名来访问这些隐藏文件,以确认你的确拥有对应域名的控制权。</li><li><em>standalone</em> 方式: Certbot 会自己运行一个 web server 来进行验证。如果我们自己的服务器上已经有 web server 正在运行 (比如 Nginx 或 Apache ),用 standalone 方式的话需要先关掉它,以免冲突。</li></ol><h2 id="获取证书"><a href="#获取证书" class="headerlink" title="获取证书"></a>获取证书</h2><h3 id="webroot-模式"><a href="#webroot-模式" class="headerlink" title="webroot 模式"></a>webroot 模式</h3><p>使用这种模式会在 web root 中创建 .well-known 文件夹,这个文件夹里面包含了一些验证文件,Certbot 会通过访问 example.com/.well-known/acme-challenge 来验证你的域名是否绑定的这个服务器,所以需要编辑 nginx 配置文件确保可以访问刚刚创建的 .well-known 文件夹及里边存放的验证文件,以便在生成证书时进行验证:</p><p>使用以下命令查看 nginx 配置文件地址:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ sudo nginx -t</span><br><span class="line">nginx: the configuration file /usr/<span class="built_in">local</span>/nginx/nginx.conf syntax is ok</span><br><span class="line">nginx: configuration file /usr/<span class="built_in">local</span>/nginx/nginx.conf <span class="built_in">test</span> is successful</span><br></pre></td></tr></table></figure><p>编辑 /usr/local/nginx/nginx.conf 配置</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> {</span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> /.well-known/acme-challenge/ {</span><br><span class="line"> <span class="attribute">default_type</span> <span class="string">"text/plain"</span>;</span><br><span class="line"> <span class="attribute">root</span> /var/www/example;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>重启 nginx 服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ nginx -s reload</span><br></pre></td></tr></table></figure><p>获取证书,<code>--email</code> 为申请者邮箱,<code>--webroot</code> 为 webroot 方式,<code>-w</code> 为站点目录,<code>-d</code> 为要加 https 的域名,以下命令会为 example.com 和 <a href="http://www.example.com" target="_blank" rel="noopener">www.example.com</a> 这两个域名生成一个证书:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ certbot certonly --email [email protected] --webroot -w /var/www/example -d example.com -d www.example.com</span><br></pre></td></tr></table></figure><h3 id="standalone-模式获取证书"><a href="#standalone-模式获取证书" class="headerlink" title="standalone 模式获取证书"></a>standalone 模式获取证书</h3><p>但是有些时候我们的一些服务并没有根目录,例如一些微服务,这时候使用 webroot 模式就走不通了。这时可以使用模式 standalone 模式,这种模式不需要指定网站根目录,他会自动启用服务器的443端口,来验证域名的归属。我们有其他服务(例如nginx)占用了443端口,就必须先停止这些服务,在证书生成完毕后,再启用。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ certbot certonly --email [email protected] --standalone -d example.com -d www.example.com</span><br></pre></td></tr></table></figure><h2 id="nginx-开启-https"><a href="#nginx-开启-https" class="headerlink" title="nginx 开启 https"></a>nginx 开启 https</h2><p>证书生成完成后可以到 /etc/letsencrypt/live/ 目录下查看对应域名的证书文件。编辑 nginx 配置文件监听 443 端口,启用 SSL,并配置 SSL 的公钥、私钥证书路径:</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> {</span><br><span class="line"></span><br><span class="line"> <span class="attribute">listen</span> <span class="number">443</span>;</span><br><span class="line"> <span class="attribute">server_name</span> example.com;</span><br><span class="line"> <span class="attribute">root</span> /var/www/example;</span><br><span class="line"> <span class="attribute">index</span> index.html index.htm;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">ssl</span> <span class="literal">on</span>;</span><br><span class="line"> <span class="attribute">ssl_certificate</span> /etc/letsencrypt/live/example.com/fullchain.pem;</span><br><span class="line"> <span class="attribute">ssl_certificate_key</span> /etc/letsencrypt/live/example.com/privkey.pem;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">charset</span> utf-<span class="number">8</span>;</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>添加 HTTP 跳转到 HTTPS:</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> {</span><br><span class="line"> <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"> <span class="attribute">server_name</span> example.com;</span><br><span class="line"> <span class="attribute">return</span> <span class="number">301</span> https://<span class="variable">$server_name</span><span class="variable">$request_uri</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>重启 nginx 服务,访问 <a href="https://example.com" target="_blank" rel="noopener">https://example.com</a> 查看是否配置成功</p><h2 id="自动续期"><a href="#自动续期" class="headerlink" title="自动续期"></a>自动续期</h2><p>Let’s Encrypt 提供的证书只有90天的有效期,所以我们要在在证书到期之前重新获取这些证书,Certbot 提供了一个方便的命令 <code>certbot renew</code>,我们可以先使用 <code>--dry-run</code> 测试是否可用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ certbot renew --dry-run</span><br></pre></td></tr></table></figure><p>linux 系统上有 cron 可以来搞定这件事情,使用以下命令新建任务:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ crontab -e</span><br></pre></td></tr></table></figure><p>写入以下任务内容。这段内容的意思就是 每隔 两个月的 凌晨 2:15 执行 更新操作</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">15</span> <span class="number">2</span> * */<span class="number">2</span> * certbot renew --quiet --renew-hook <span class="string">"service nginx restart"</span></span><br></pre></td></tr></table></figure><table><thead><tr><th>参数</th><th>表述</th></tr></thead><tbody><tr><td>–quiet</td><td>执行时屏蔽错误以外的所有输出,也可以使用 -q</td></tr><tr><td>–pre-hook</td><td>执行更新操作之前要做的事情</td></tr><tr><td>–pre-hook</td><td>执行更新操作之前要做的事情</td></tr><tr><td>–post-hook</td><td>执行更新操作完成后要做的事情</td></tr></tbody></table><h2 id="取消证书"><a href="#取消证书" class="headerlink" title="取消证书"></a>取消证书</h2><p>可以使用以下命令取消刚刚生成的密匙,也就是以上的反操作:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem</span><br><span class="line">$ certbot delete --cert-name example.com</span><br></pre></td></tr></table></figure><p>至此,整个网站升级到 HTTPS 就完成了。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ol><li>如果加上 HTTPS 之后😊锁不够绿的话,检查下站点加载的资源(比如 js、css、照片等)是不是有 HTTP 的,有的话就会导致小锁变为灰色。</li><li>在我们使用 HTTP 的时候,打开网页总会遇到第三方偷偷加的一些脚本广告,很是烦人,升级 HTTPS 后他们就无从下手了,欧耶。</li></ol>]]></content>
<summary type="html">
<h2 id="Let’s-Encrypt-简介"><a href="#Let’s-Encrypt-简介" class="headerlink" title="Let’s Encrypt 简介"></a>Let’s Encrypt 简介</h2><p>Let’s Encrypt 是国外一个公共的免费SSL项目,由 Linux 基金会托管,它的来头不小,由 Mozilla、思科、Akamai、IdenTrust 和 EFF 等组织发起,目的就是向网站自动签发和管理免费证书,以便加速互联网由 HTTP 过渡到 HTTPS,目前 Facebook 等大公司开始加入赞助行列。</p><p>Let’s Encrypt 已经得了 IdenTrust 的交叉签名,这意味着其证书现在已经可以被 Mozilla、Google、Microsoft 和 Apple 等主流的浏览器所信任,你只需要在 Web 服务器证书链中配置交叉签名,浏览器客户端会自动处理好其它的一切,Let’s Encrypt 安装简单,使用非常方便。</p><h2 id="Certbot-简介"><a href="#Certbot-简介" class="headerlink" title="Certbot 简介"></a>Certbot 简介</h2><p><a href="https://certbot.eff.org/" target="_blank" rel="noopener">Certbot</a> 为 Let’s Encrypt 项目发布了一个官方的客户端 Certbot ,利用它可以完全自动化的获取、部署和更新安全证书,并且 Certbot 是支持所有 Unix 内核的操作系统。</p><h2 id="安装-Certbot-客户端"><a href="#安装-Certbot-客户端" class="headerlink" title="安装 Certbot 客户端"></a>安装 Certbot 客户端</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ yum install certbot <span class="comment"># centos</span></span><br><span class="line">$ <span class="comment"># apt install certbot # ubuntu</span></span><br></pre></td></tr></table></figure>
</summary>
<category term="back-end" scheme="https://sanonz.github.io/categories/back-end/"/>
<category term="Http" scheme="https://sanonz.github.io/tags/Http/"/>
<category term="Centos" scheme="https://sanonz.github.io/tags/Centos/"/>
</entry>
<entry>
<title>Ubuntu 搭建 php、mysql、nginx 开发环境</title>
<link href="https://sanonz.github.io/2017/ubuntu-install-nginx-php-mysql/"/>
<id>https://sanonz.github.io/2017/ubuntu-install-nginx-php-mysql/</id>
<published>2017-11-14T03:26:20.000Z</published>
<updated>2024-01-24T02:42:46.279Z</updated>
<content type="html"><![CDATA[<h2 id="php-安装"><a href="#php-安装" class="headerlink" title="php 安装"></a>php 安装</h2><p>进入 php 官方站点现在所需版本 <a href="http://php.net/downloads.php" target="_blank" rel="noopener">http://php.net/downloads.php</a>,提取下载地址使用 <code>wget</code> 进行下载:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> /usr/<span class="built_in">local</span>/</span><br><span class="line">$ wget http://cn.php.net/distributions/php-7.1.11.tar.gz</span><br></pre></td></tr></table></figure><p>解压文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ tar zxf php-7.1.11.tar.gz php-7.1.11-src</span><br><span class="line">$ <span class="built_in">cd</span> ./php-7.1.11-src</span><br></pre></td></tr></table></figure><p>安装依赖库</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ apt install make build-essential libxml2-dev</span><br></pre></td></tr></table></figure><a id="more"></a><p>具体的配置选项列表使用 <code>./configure --help</code> 或者查看官方文档 <a href="http://php.net/manual/zh/configure.about.php" target="_blank" rel="noopener">http://php.net/manual/zh/configure.about.php</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">$ ./configure \</span><br><span class="line">--prefix=/usr/<span class="built_in">local</span>/php-7.1.11 \</span><br><span class="line">--with-curl \</span><br><span class="line">--with-zlib \</span><br><span class="line">--with-iconv \</span><br><span class="line">--with-xmlrpc \</span><br><span class="line">--with-openssl \</span><br><span class="line">--with-pcre-jit \</span><br><span class="line">--with-pdo-mysql=shared,mysqlnd \</span><br><span class="line">--without-pear \</span><br><span class="line">--<span class="built_in">enable</span>-xml \</span><br><span class="line">--<span class="built_in">enable</span>-fpm \</span><br><span class="line">--<span class="built_in">disable</span>-dom \</span><br><span class="line">--<span class="built_in">enable</span>-shared \</span><br><span class="line">--<span class="built_in">enable</span>-sockets \</span><br><span class="line">--<span class="built_in">enable</span>-mbstring \</span><br><span class="line">--<span class="built_in">disable</span>-rpath \</span><br><span class="line">--<span class="built_in">disable</span>-fileinfo</span><br></pre></td></tr></table></figure><p>如果提示 <code>Cannot find OpenSSL's <evp.h></code> 执行以下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ apt install libssl-dev</span><br></pre></td></tr></table></figure><p>如果提示 <code>Cannot find OpenSSL's libraries</code> 执行以下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ find / -name libssl.so</span><br><span class="line">/usr/lib/x86_64-linux-gnu/libssl.so</span><br><span class="line">$ ln -s /usr/lib/x86_64-linux-gnu/libssl.so /usr/lib</span><br></pre></td></tr></table></figure><p>创建配置文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cp ./lib/php.ini-development ./lib/php.ini</span><br><span class="line">$ cp ./etc/php-fpm.conf.default ./etc/php-fpm.conf</span><br></pre></td></tr></table></figure><h2 id="mysql-安装"><a href="#mysql-安装" class="headerlink" title="mysql 安装"></a>mysql 安装</h2><p>安装前可以使用以下命令查看当前的 mysql 版本:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ apt show mysql-server</span><br><span class="line">Package: mysql-server</span><br><span class="line">Version: 5.7.20-0ubuntu0.16.04.1</span><br><span class="line">Priority: optional</span><br><span class="line">Section: database</span><br><span class="line">Source: mysql-5.7</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>执行以下命令安装 mysql:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt install mysql-server</span><br></pre></td></tr></table></figure><p>安装过程中需要输入密码:</p><img src="/2017/ubuntu-install-nginx-php-mysql/mysql-install-new-password.png"><p>然后在重复输入密码:</p><img src="/2017/ubuntu-install-nginx-php-mysql/mysql-install-repeat-password.png"><p>设置完成后等待自动安装即可,安装完成 mysql 默认就启动了,可以执行以下命令查看,只要看到出来一坨代码就说明已经启动成功。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ps -ef | grep mysqld</span><br></pre></td></tr></table></figure><p>可以使用以下命令控制 mysql 服务:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ service mysql stop</span><br><span class="line">$ service mysql start</span><br><span class="line">$ service mysql restart</span><br></pre></td></tr></table></figure><p>使用以下命令进入 mysql shell 界面,按提示输入密码,可以使用 <code>show databases</code> 查看数据库列表,用 <code>exit</code> 命令退出 shell 界面:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">$ mysql -uroot -p</span><br><span class="line">Enter password:</span><br><span class="line">Welcome to the MySQL monitor. Commands end with ; or \g.</span><br><span class="line">Your MySQL connection id is 7</span><br><span class="line">Server version: 5.7.20-0ubuntu0.16.04.1 (Ubuntu)</span><br><span class="line"></span><br><span class="line">Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.</span><br><span class="line"></span><br><span class="line">Oracle is a registered trademark of Oracle Corporation and/or its</span><br><span class="line">affiliates. Other names may be trademarks of their respective</span><br><span class="line">owners.</span><br><span class="line"></span><br><span class="line">Type <span class="string">'help;'</span> or <span class="string">'\h'</span> <span class="keyword">for</span> <span class="built_in">help</span>. Type <span class="string">'\c'</span> to clear the current input statement.</span><br><span class="line"></span><br><span class="line">mysql> show databases;</span><br><span class="line">+--------------------+</span><br><span class="line">| Database |</span><br><span class="line">+--------------------+</span><br><span class="line">| information_schema |</span><br><span class="line">| mysql |</span><br><span class="line">| performance_schema |</span><br><span class="line">| sys |</span><br><span class="line">+--------------------+</span><br><span class="line">4 rows <span class="keyword">in</span> <span class="built_in">set</span> (0.02 sec)</span><br><span class="line"></span><br><span class="line">mysql> <span class="built_in">exit</span>;</span><br><span class="line">Bye</span><br></pre></td></tr></table></figure><h2 id="nginx-安装"><a href="#nginx-安装" class="headerlink" title="nginx 安装"></a>nginx 安装</h2><p>执行以下命令安装 nginx</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt install nginx</span><br></pre></td></tr></table></figure><p>先启动 nginx,然后访问 ip 查看是否安装成功。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ service nginx start</span><br></pre></td></tr></table></figure><p>编辑 nginx 配置文件,添加 php 解析</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> {</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> <span class="regexp">~ \.php$</span> {</span><br><span class="line"> <span class="attribute">fastcgi_pass</span> <span class="number">127.0.0.1:9000</span>;</span><br><span class="line"> <span class="attribute">fastcgi_index</span> index.php;</span><br><span class="line"> <span class="attribute">fastcgi_param</span> SCRIPT_FILENAME <span class="variable">$document_root</span><span class="variable">$fastcgi_script_name</span>;</span><br><span class="line"> <span class="attribute">include</span> fastcgi_params;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>新建 php 文件并访问</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"></span><br><span class="line">phpinfo();</span><br></pre></td></tr></table></figure><p>完毕</p>]]></content>
<summary type="html">
<h2 id="php-安装"><a href="#php-安装" class="headerlink" title="php 安装"></a>php 安装</h2><p>进入 php 官方站点现在所需版本 <a href="http://php.net/downloads.php" target="_blank" rel="noopener">http://php.net/downloads.php</a>,提取下载地址使用 <code>wget</code> 进行下载:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> /usr/<span class="built_in">local</span>/</span><br><span class="line">$ wget http://cn.php.net/distributions/php-7.1.11.tar.gz</span><br></pre></td></tr></table></figure><p>解压文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ tar zxf php-7.1.11.tar.gz php-7.1.11-src</span><br><span class="line">$ <span class="built_in">cd</span> ./php-7.1.11-src</span><br></pre></td></tr></table></figure><p>安装依赖库</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ apt install make build-essential libxml2-dev</span><br></pre></td></tr></table></figure>
</summary>
<category term="back-end" scheme="https://sanonz.github.io/categories/back-end/"/>
<category term="PHP" scheme="https://sanonz.github.io/tags/PHP/"/>
<category term="MySQL" scheme="https://sanonz.github.io/tags/MySQL/"/>
<category term="Nginx" scheme="https://sanonz.github.io/tags/Nginx/"/>
<category term="Ubuntu" scheme="https://sanonz.github.io/tags/Ubuntu/"/>
</entry>
<entry>
<title>WebGL 三维教程 - 绘制矩形和点(二)</title>
<link href="https://sanonz.github.io/2017/webgl-draw-rectangle-and-point/"/>
<id>https://sanonz.github.io/2017/webgl-draw-rectangle-and-point/</id>
<published>2017-11-03T11:29:20.000Z</published>
<updated>2024-01-24T02:42:46.279Z</updated>
<content type="html"><![CDATA[<h2 id="Canvas-简介"><a href="#Canvas-简介" class="headerlink" title="Canvas 简介"></a>Canvas 简介</h2><p>在 HTML5 出现之前,如果想要在网页上显示图像,只能使用 HTML 提供的 <code><img></code> 标签。用这个标签虽然简单,但是只能显示静态的图片,不能进行实时绘制和渲染,所以后来出现了一些第三方的解决方案,比如 Flash Player 等。</p><p>Canvas 中文译为“画布”,作为画布我们可以使用 <code>HTMLCanvasElement.getContext(contextType)</code> 获取 Canvas 的上下文,然后使用上下文提供的 API 在画布区域绘制图案,接下来分别介绍怎么使用二维和三维的方法绘制矩形和点。</p><p>contextType 的取值如下:</p><ol><li><code>"2d"</code> 建立一个 <code>CanvasRenderingContext2D</code> 对象,代表一个二维渲染上下文。</li><li><code>"webgl"</code> 或 <code>"experimental-webgl"</code> 这将创建一个 <code>WebGLRenderingContext</code> 代表三维渲染上下文对象 (OpenGL ES 2.0)。</li></ol><a id="more"></a><h2 id="二维绘制矩形"><a href="#二维绘制矩形" class="headerlink" title="二维绘制矩形"></a>二维绘制矩形</h2><p>首先要写一个 canvas 元素,然后获取二维渲染的上下文:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">canvas</span> <span class="attr">id</span>=<span class="string">"gl"</span> <span class="attr">width</span>=<span class="string">"300"</span> <span class="attr">height</span>=<span class="string">"300"</span>></span><span class="tag"></<span class="name">canvas</span>></span></span><br></pre></td></tr></table></figure><p>绘制二维矩形很简单,只需要调用上下文的 <code>ctx.fillRect(x, y, width, height)</code> 方法即可,语法如下:</p><table><thead><tr><th>参数</th><th>表述</th></tr></thead><tbody><tr><td>x</td><td>矩形起始点的 x 轴坐标</td></tr><tr><td>y</td><td>矩形起始点的 y 轴坐标</td></tr><tr><td>width</td><td>矩形的宽度</td></tr><tr><td>height</td><td>矩形的高度</td></tr></tbody></table><p>在调用之前首先使用 <code>ctx.fillStyle</code> 设置要填充的颜色,这里我们填充的是蓝色,具体绘制矩形的代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ctx = <span class="built_in">document</span>.getElementById(<span class="string">'gl'</span>).getContext(<span class="string">'2d'</span>);</span><br><span class="line">ctx.fillStyle = <span class="string">'rgba(0, 0, 255, 1.0)'</span>;</span><br><span class="line">ctx.fillRect(<span class="number">0</span>, <span class="number">0</span>, <span class="number">300</span>, <span class="number">300</span>);</span><br></pre></td></tr></table></figure><p>运行效果为一个蓝色的矩形 <a href="https://stackblitz.com/edit/canvas-draw-rectangle" target="_blank" rel="noopener">点击运行</a>:</p><img src="/2017/webgl-draw-rectangle-and-point/[email protected]" width="320"><h2 id="三维绘制矩形"><a href="#三维绘制矩形" class="headerlink" title="三维绘制矩形"></a>三维绘制矩形</h2><p>和二维绘制矩形一样,在三维绘制一个矩形也是很简单的,首先要获取 <code>WebGLRenderingContext</code> 的对象,其次是调用 <code>gl.clearColor(red, green, blue, alpha)</code> 设置背景色,语法如下:</p><table><thead><tr><th>参数</th><th>表述</th></tr></thead><tbody><tr><td>red</td><td>指定清除缓冲时的红色值。默认值:0</td></tr><tr><td>green</td><td>指定清除缓冲时的绿色值。默认值:0</td></tr><tr><td>blue</td><td>指定清除缓冲时的蓝色值。默认值:0</td></tr><tr><td>alpha</td><td>指定清除缓冲时的不透明度。默认值:0</td></tr></tbody></table><p>下面列出的是一些常用的颜色:</p><table><thead><tr><th>色值</th><th>描述</th></tr></thead><tbody><tr><td>(1.0, 0.0, 0.0, 1.0)</td><td>红色</td></tr><tr><td>(0.0, 1.0, 0.0, 1.0)</td><td>绿色</td></tr><tr><td>(0.0, 0.0, 1.0, 1.0)</td><td>蓝色</td></tr><tr><td>(1.0, 1.0, 0.0, 1.0)</td><td>黄色</td></tr><tr><td>(1.0, 0.0, 1.0, 1.0)</td><td>紫色</td></tr><tr><td>(0.0, 1.0, 1.0, 1.0)</td><td>青色</td></tr><tr><td>(1.0, 1.0, 1.0, 1.0)</td><td>白色</td></tr></tbody></table><p>然后在调用 <code>gl.clear(mask)</code> 用背景色清空 canvas 绘图区域,<code>mask</code> 参数为指定待清空的缓冲区,可以使用位操作符 <code>OR(|)</code> 来指定多个缓冲区,可以取的常量如下:</p><table><thead><tr><th>常量</th><th>表述</th></tr></thead><tbody><tr><td>gl.COLOR_BUFFER_BIT</td><td>指定颜色缓冲区</td></tr><tr><td>gl.DEPTH_BUFFER_BIT</td><td>指定深度缓冲区</td></tr><tr><td>gl.STENCIL_BUFFER_BIT</td><td>指定模版缓冲区</td></tr></tbody></table><p>这里我们设置的背景色为黑色,最后的代码如下,运行后我们想要的矩形就出来了,这个也是最短的 WebGL 程序。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ctx = <span class="built_in">document</span>.getElementById(<span class="string">'gl'</span>).getContext(<span class="string">'webgl'</span>);</span><br><span class="line">gl.clearColor(<span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">1.0</span>);</span><br><span class="line">gl.clear(<span class="keyword">this</span>.gl.COLOR_BUFFER_BIT);</span><br></pre></td></tr></table></figure><p>运行效果为一个黑色矩形 <a href="https://stackblitz.com/edit/webgl-draw-rectangle" target="_blank" rel="noopener">点击运行</a>:</p><img src="/2017/webgl-draw-rectangle-and-point/[email protected]"><h2 id="三维绘制点"><a href="#三维绘制点" class="headerlink" title="三维绘制点"></a>三维绘制点</h2><p>前边我们分别使用三行代码分别绘制了二维与三维的矩形,我们知道,在二维世界坐标是 <code>(x, y)</code>,在三维世界比二维多了一个 <code>z</code> (深度),坐标也就是 <code>(x, y, z)</code>,然后推测根据二维的代码多加一个 <code>z</code> 坐标应该就能画三维中的矩形,修改代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ctx = <span class="built_in">document</span>.getElementById(<span class="string">'gl'</span>).getContext(<span class="string">'webgl'</span>);</span><br><span class="line">gl.drawColor(<span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">1.0</span>);</span><br><span class="line">gl.drawPoint(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">10</span>); <span class="comment">// 点的位置和大小</span></span><br></pre></td></tr></table></figure><p>然而事情并没有那么简单,当我们运行代码发现出错了,实际的三维绘图要比二维复杂的多,WebGL 依赖于一种新的称为 <em>着色器</em> (shader) 的绘图机制,上一篇 <a href="/2017/webgl-introduction/" title="WebGL 三维教程 - WebGL 介绍(一)">WebGL 三维教程 - WebGL 介绍(一)</a> 我们简单介绍了 WebGL,其中有提及到 GLSL (OpenGL Shading Language),那么 GLSL 到底是什么呢?GLSL 为着色语言,用来编写 shader 脚本,着色器分为 <em>顶点着色器</em> (Vertex shader) 与 <em>片元着色器</em> (Fragment shader) 两部分,着色器提供了灵活而强大的绘制二维或三维图形的方法,正是因为它的强大,因此也带来了使用上的复杂度,接下来的教程中,我们将一步步去深入研究和理解它。</p><h3 id="三维世界的坐标系统"><a href="#三维世界的坐标系统" class="headerlink" title="三维世界的坐标系统"></a>三维世界的坐标系统</h3><p>WebGL 中的坐标是由 (x, y, z) 组成,下图描述了 WebGL 坐标系。z 轴表示深度,正值 z 表示对象在屏幕/查看器附近,而负值 z 表示该对象不在屏幕上。同样,x 的正值表示对象是屏幕右侧,负值表示对象是左侧。同样,y 的正值和负值表示对象位于屏幕顶部或底部的底部。</p><img src="/2017/webgl-draw-rectangle-and-point/coordinate.png"><h3 id="顶点与片元着色器介绍"><a href="#顶点与片元着色器介绍" class="headerlink" title="顶点与片元着色器介绍"></a>顶点与片元着色器介绍</h3><ul><li>顶点你着色器是用来描述顶点特征(如位置、颜色等)的程序。<em>顶点</em> 是指二维或三维空间中的一个点,将这些顶点连接起来,可以形成线或三角形,WebGL 中最常用的是三角形。</li><li>片元着色器是进行逐片元处理过程的程序,如处理光照,片元史 WebGL 术语,可以理解为像素。</li></ul><h3 id="创建着色器步骤"><a href="#创建着色器步骤" class="headerlink" title="创建着色器步骤"></a>创建着色器步骤</h3><p>(一)、使用 <code>gl.createProgram()</code> 方法用于创建和初始化一个 <code>WebGLProgram</code> 对象。<br>(二)、使用 <code>gl.createShader(type)</code> 创建一个 <code>WebGLShader</code> 着色器对象。</p><table><thead><tr><th>参数</th><th>表述</th></tr></thead><tbody><tr><td>type</td><td>gl.VERTEX_SHADER 为顶点着色器,gl.FRAGMENT_SHADER 为片元着色器</td></tr></tbody></table><p>(三)、使用 <code>gl.shaderSource(shader, source)</code> 挂接 <code>source</code> (GLSL) 源代码到 <code>shader</code> 上。</p><table><thead><tr><th>参数</th><th>表述</th></tr></thead><tbody><tr><td>shader</td><td>包含顶点着色器和片元着色器的 <code>WebGLShader</code> 对象</td></tr><tr><td>source</td><td>一坨字符串,也就是着色器 (GLSL) 语言代码</td></tr></tbody></table><p>(四)、使用 <code>gl.compileShader(shader)</code> 编译一个 GLSL 着色器,使其成为为二进制数据,然后就可以被 <code>WebGLProgram</code> 对象所使用。</p><table><thead><tr><th>参数</th><th>表述</th></tr></thead><tbody><tr><td>shader</td><td>一个片元或顶点着色器</td></tr></tbody></table><p>(五)、使用 <code>gl.attachShader(program, shader)</code> 方法把着色器 <code>shader</code> 添加到 <code>program</code>。</p><table><thead><tr><th>参数</th><th>表述</th></tr></thead><tbody><tr><td>program</td><td>包含顶点着色器和片元着色器的 <code>WebGLProgram</code> 对象</td></tr><tr><td>shader</td><td>一个类型为片段或者顶点的 <code>WebGLShader</code> 对象</td></tr></tbody></table><p>(六)、使用 <code>gl.linkProgram(program)</code> 和 <code>gl.useProgram(program)</code> 分别链接和使用 <code>WebGLProgram</code> 对象。</p><p>综上六个步骤就可以创建一个顶点着色器或片元着色器,为了方便这里把创建着色器封装成了一个 <code>createShader(type, source)</code> 方法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"></span><br><span class="line"> createShader(type, source) {</span><br><span class="line"> <span class="comment">// 创建一个`WebGLShader`着色器对象,`type`为`gl.VERTEX_SHADER`(顶点着色器)或`gl.FRAGMENT_SHADER`片元着色器</span></span><br><span class="line"> <span class="keyword">const</span> shader = <span class="keyword">this</span>.gl.createShader(type);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 挂接`source`(GLSL)源代码到`shader`上</span></span><br><span class="line"> <span class="keyword">this</span>.gl.shaderSource(shader, source);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 编译`shader`(GLSL)为二进制文件,以便被`WebGLProgram`使用</span></span><br><span class="line"> <span class="keyword">this</span>.gl.compileShader(shader);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加一个片元着色器或者顶点着色器</span></span><br><span class="line"> <span class="keyword">this</span>.gl.attachShader(<span class="keyword">this</span>.shaderProgram, shader);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> shader;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="初始化顶点和片元着色器"><a href="#初始化顶点和片元着色器" class="headerlink" title="初始化顶点和片元着色器"></a>初始化顶点和片元着色器</h3><p>接下来在添加一个 <code>constructor()</code> 和 <code>initShader()</code> 方法,用来获取 <code>WebGLRenderingContext</code> 对象和初始化顶点着色器和片元着色器:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>() {</span><br><span class="line"> <span class="keyword">this</span>.gl = self.gl = <span class="built_in">document</span>.getElementById(<span class="string">'gl'</span>).getContext(<span class="string">'webgl'</span>);</span><br><span class="line"> <span class="keyword">this</span>.gl.viewport(<span class="number">0</span>, <span class="number">0</span>, <span class="keyword">this</span>.gl.drawingBufferWidth, <span class="keyword">this</span>.gl.drawingBufferHeight);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> initShader() {</span><br><span class="line"> <span class="comment">// 创建一个`WebGLProgram`</span></span><br><span class="line"> <span class="keyword">this</span>.shaderProgram = <span class="keyword">this</span>.gl.createProgram();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建顶点着色器</span></span><br><span class="line"> <span class="keyword">this</span>.createShader(<span class="keyword">this</span>.gl.VERTEX_SHADER, <span class="string">`</span></span><br><span class="line"><span class="string"> attribute vec4 a_Position;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> void main() {</span></span><br><span class="line"><span class="string"> gl_Position = a_Position;</span></span><br><span class="line"><span class="string"> gl_PointSize = 10.0;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> `</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建片元着色器</span></span><br><span class="line"> <span class="keyword">this</span>.createShader(<span class="keyword">this</span>.gl.FRAGMENT_SHADER, <span class="string">`</span></span><br><span class="line"><span class="string"> void main() {</span></span><br><span class="line"><span class="string"> gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> `</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 分别链接和使用</span></span><br><span class="line"> <span class="keyword">this</span>.gl.linkProgram(<span class="keyword">this</span>.shaderProgram);</span><br><span class="line"> <span class="keyword">this</span>.gl.useProgram(<span class="keyword">this</span>.shaderProgram);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上边程序中第 16-21 行编写了顶点着色器,26-28 行编写了片元着色器,从这两块的程序中可以看出,GLSL 语言是跟 C 语言非常类似的,都必须包含一个 <code>main()</code> 函数,<code>main()</code> 前边的关键字 <code>void</code> 是表示这个函数不会有返回值,还有不能为 <code>main()</code> 函数指定参数。</p><p>在顶点着色器和中使用了:<code>gl_Position</code>、<code>gl_Position</code> 和片元着色器中的: <code>gl_FragColor</code>,这些分别为内置的变量,我们把相应的值赋值给相应的内置变量就可以得到相应的渲染结果,它们的功能如下:</p><table><thead><tr><th>类型</th><th>表述</th></tr></thead><tbody><tr><td>gl_Position</td><td>表示顶点位置</td></tr><tr><td>gl_Position</td><td>表示点的尺寸</td></tr><tr><td>gl_FragColor</td><td>指定片元颜色(RGBA)</td></tr></tbody></table><p>并且 GLSL 语言为强类型语言,如果把第 20 行的 <code>gl_PointSize = 10.0</code> 改为 <code>gl_PointSize = 10</code> 就会导致程序报错,上边使用到的类型介绍如下:</p><table><thead><tr><th>类型</th><th>表述</th></tr></thead><tbody><tr><td>float</td><td>表示浮点数</td></tr><tr><td>vec4</td><td>表示由四个浮点数组成的矢量,另外三种:vec1、vec2、vec3</td></tr></tbody></table><p>其中 <code>vec4</code> 在这里有两种身份,它既能声明变量类型: <code>attribute vec4 a_Position</code>,又能作为 <em>构造函数</em> (Constructor Functions) 使用:<code>gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0)</code>,构造函数的名称和创建的变量的名称是一致的。最前边的关键 <code>attribute</code> 被称为 <em>储存限定符</em> (Storage Qualifier),<code>attribute</code> 必须声明为全局变量,数据将从着色器外部传给该变量。</p><p>着色器初始化完成后就可以开始画点了,步骤如下:</p><p>(一)、使用 <code>gl.getAttribLocation(program, name)</code> 方法获取 <code>attribute</code> 变量的储存位置。</p><table><thead><tr><th>类型</th><th>表述</th></tr></thead><tbody><tr><td>program</td><td>包含顶点着色器和片元着色器的 <code>WebGLProgram</code> 对象</td></tr><tr><td>name</td><td>着色器 <code>attribute</code> 变量的名称</td></tr></tbody></table><p>(二)、使用 <code>gl.vertexAttrib3f(index, v0, v1, v2)</code> 方法向 <code>attribute</code> 变量赋值。</p><table><thead><tr><th>类型</th><th>表述</th></tr></thead><tbody><tr><td>index</td><td><code>attribute</code> 的储存位置</td></tr><tr><td>v0</td><td>指定填充 <code>attribute</code> 变量第一个分量的值</td></tr><tr><td>v1</td><td>指定填充 <code>attribute</code> 变量第二个分量的值</td></tr><tr><td>v2</td><td>指定填充 <code>attribute</code> 变量第三个分量的值</td></tr></tbody></table><p>(三)、使用 <code>gl.drawArrays(mode, first, count)</code> 方法进行绘制。</p><table><thead><tr><th>类型</th><th>表述</th></tr></thead><tbody><tr><td>mode</td><td>绘制的方式,可以选择的常量:gl.POINTS、gl.LINES、gl.LINE_STRIP、gl.LINE_LOOP、gl.TRIANGLES、gl.TRIANGLE_STRIP、gl.TRIANGLE_FAN</td></tr><tr><td>first</td><td>指定从哪个顶点开始绘制</td></tr><tr><td>count</td><td>指定绘制需要多少个顶点</td></tr></tbody></table><p><code>mode</code> 可以绘制的基本图形解析:</p><table><thead><tr><th>mode</th><th>图形</th><th>表述</th></tr></thead><tbody><tr><td>gl.POINTS</td><td>点</td><td>一系列点,绘制在 v0、v1、v2 … 处</td></tr><tr><td>gl.LINES</td><td>线段</td><td>一系列单独的线段,绘制在 (v0, v1)、(v2, v3)、(v4, v5) … 处,如果点的个数是奇数,最后一个点将被忽略</td></tr><tr><td>gl.LINE_STRIP</td><td>线条</td><td>一系列连接的线段,被绘制在(v0,v1)、(v1,v2)、(v2,v3) … 处,第 1 个点是第1条线段的起点,第 2 个点是第 1 条线段的终点和第 2 条线段的起点 … 第 i(i>1) 个点是第 i-1 条线段的终点和第 i 条线段的起点,以此类推。最后一个点是最后一条线段的终点</td></tr><tr><td>gl.LINE_LOOP</td><td>回路</td><td>一系列连接的线段。与gl.LINE_STRIP 绘制的线条相比,增加了一条从最后一个点到第1个点的线段。因此,线段被绘制在 (v0, v1)、(v1, v2) … (vn, v0) 处,其中 vn 是最后一个点</td></tr><tr><td>gl.TRIANGLES</td><td>三角形</td><td>一系列单独的三角形,绘制在 (v0, v1, v2)、(v3, v4, v5) … 处,如果点的个数不是 3 的整数倍,最后剩下的一或两个点将被忽略</td></tr><tr><td>gl.TRIANGLE_STRIP</td><td>三角带</td><td>一系列条带状的三角形,前三个点构成了第 1 个三角形,从第 2 个点开始的三个点构成了第 2 个三角形(该三角形与前一个三角形共享一条边),以此类推。这些三角形被绘制在 (v0, v1, v2)、(v2, v1, v3)(v2, v3, v4) … 处</td></tr><tr><td>gl.TRIANGLE_FAN</td><td>三角扇</td><td>一系列三角形组成的类似于扇形的图形。前三个点构成了第 1 个三角形,接下来的一个点和前一个三角形的最后一条边组成接下来的一个三角形。这些三角形被绘制在 (v0, v1, v2)、(v0, v2, v3)、(v0, v3, v4) … 处</td></tr></tbody></table><h3 id="绘制三维世界中的点"><a href="#绘制三维世界中的点" class="headerlink" title="绘制三维世界中的点"></a>绘制三维世界中的点</h3><p>添加一个 <code>drawPoint()</code> 方法进行画点,在运行前我们在 <code>constructor()</code> 方法中分别调用 <code>this.initShader()</code> 和 <code>this.drawPoint()</code> 方法,最终代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>() {</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.initShader();</span><br><span class="line"> <span class="keyword">this</span>.drawPoint();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> drawPoint() {</span><br><span class="line"> <span class="keyword">const</span> a_Position = <span class="keyword">this</span>.gl.getAttribLocation(<span class="keyword">this</span>.shaderProgram, <span class="string">'a_Position'</span>);</span><br><span class="line"> <span class="keyword">this</span>.gl.vertexAttrib4f(a_Position, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">1.0</span>);</span><br><span class="line"> <span class="keyword">this</span>.gl.drawArrays(<span class="keyword">this</span>.gl.POINTS, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>预览效果 <a href="https://stackblitz.com/edit/webgl-draw-point" target="_blank" rel="noopener">点击运行</a>:</p><img src="/2017/webgl-draw-rectangle-and-point/[email protected]" width="316"><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本文中出现了一些 WebGL 的一些概念,对于初学者来说,你不需要记住本文中的所有东西,在后续的教程中将一步步深入研究这些概念,再加上经常练习才能真正理解,所以,现在还不用太严肃,现在只需要享受阅读,等以后再回头看也不迟。</p>]]></content>
<summary type="html">
<h2 id="Canvas-简介"><a href="#Canvas-简介" class="headerlink" title="Canvas 简介"></a>Canvas 简介</h2><p>在 HTML5 出现之前,如果想要在网页上显示图像,只能使用 HTML 提供的 <code>&lt;img&gt;</code> 标签。用这个标签虽然简单,但是只能显示静态的图片,不能进行实时绘制和渲染,所以后来出现了一些第三方的解决方案,比如 Flash Player 等。</p><p>Canvas 中文译为“画布”,作为画布我们可以使用 <code>HTMLCanvasElement.getContext(contextType)</code> 获取 Canvas 的上下文,然后使用上下文提供的 API 在画布区域绘制图案,接下来分别介绍怎么使用二维和三维的方法绘制矩形和点。</p><p>contextType 的取值如下:</p><ol><li><code>&quot;2d&quot;</code> 建立一个 <code>CanvasRenderingContext2D</code> 对象,代表一个二维渲染上下文。</li><li><code>&quot;webgl&quot;</code> 或 <code>&quot;experimental-webgl&quot;</code> 这将创建一个 <code>WebGLRenderingContext</code> 代表三维渲染上下文对象 (OpenGL ES 2.0)。</li></ol>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
<category term="3D" scheme="https://sanonz.github.io/tags/3D/"/>
<category term="WebGL" scheme="https://sanonz.github.io/tags/WebGL/"/>
<category term="Canvas" scheme="https://sanonz.github.io/tags/Canvas/"/>
</entry>
<entry>
<title>WebGL 三维教程 - WebGL 介绍(一)</title>
<link href="https://sanonz.github.io/2017/webgl-introduction/"/>
<id>https://sanonz.github.io/2017/webgl-introduction/</id>
<published>2017-10-24T12:25:23.000Z</published>
<updated>2024-01-24T02:42:46.279Z</updated>
<content type="html"><![CDATA[<h2 id="WebGL-概述"><a href="#WebGL-概述" class="headerlink" title="WebGL 概述"></a>WebGL 概述</h2><p>WebGL 是一项在网页上绘制和渲染复杂三维图形(3D 图形),并允许用户与之进行交互的技术,在传统上只有高配置的计算机或专用的游戏机才能渲染三维图形,而现在随着个人计算机与浏览器的性能越来越强,使用便捷通用的 Web 技术创建和渲染三维图形已经成为可能。HTML5 作为目前最新的 HTML 标准,扩展了传统 HTML 的特征,如二维图形、网络传输、本地数据储存等,HTML5 时代的到来使浏览器正在迅速地从简单的展示工具转变为复杂的应用平台,人们希望网页不仅仅由二维图组成,所以 WebGL就顺着时代的召唤到来了。</p><h2 id="WebGL-的起源"><a href="#WebGL-的起源" class="headerlink" title="WebGL 的起源"></a>WebGL 的起源</h2><p>在个人计算机上使用最广泛的两种三维图形渲染技术使 Direct3D 和 OpenGL。Direct3D 是微软DirectX 技术的一部分,是一套由微软控制的编程接口(API),主要用在 Windows 平台;而 OpenGL 由于其开放和免费的特征,在多种平台上都有广泛地使用,后来 Windows 对 OpenGL 也提供了良好的支撑,开发者也可以使用它来代替 Direct3D。</p><p>OpenGL 最初是由 SGI(Silicon Graphics Inc)开发,并在 1992 年发布为开源标准。多年以来,OpenGL 发展了数个版本,并对三维图形开发、软件产品开发、甚至电影制作产生了深远的影响。虽然 WebGL 根植于 OpenGL,但它实际上是从 OpenGL 的一个特殊版本 OpenGL ES 派生出来的,OpenGL ES 主要专用于嵌入式计算机、智能手机、家用用洗等设备。OpenGL ES 于 2003~2004 年被首次提出,并在 2007 年(ES 2.0)进行了两次升级,WebGL 是基于 OpenGL ES 2.0 的。这几年采用 OpenGL ES 技术的电子设备的数量大幅度增长,尤其是移动端设备。OpenGL ES 成功被这些设备采用的部分原因是,它在添加新特性的同时从 OpenGL 中移除了许多陈旧的旧特性,这使它在保持轻量级的同时,仍具有足够的能力来渲染出精美的三维图形。</p><a id="more"></a><h2 id="WebGL-的优势"><a href="#WebGL-的优势" class="headerlink" title="WebGL 的优势"></a>WebGL 的优势</h2><p>这些年随着 HTML 的发展,网页由最初简单的静态页面变得越来越复杂,从引入 JavaScript 等脚本语言,HTML 开始提供一些动态的内容,并具有一定的交互性。现在出现了更强大的 HTML5,它可以使用 canvas 或 svg 标签在网页上绘制二维图形,以呈现更丰富的内容如:在线的在线作图 <a href="https://processon.com/" target="_blank" rel="noopener">ProcessOn</a>、原型设计 <a href="https://modao.cc/" target="_blank" rel="noopener">墨刀</a> 以及提供一整套的在线办公软件的 <a href="https://www.zoho.com/" target="_blank" rel="noopener">Zohu</a> 等工具。</p><p>WebGL 则走的更远,它允许 JavaScript 在网页上显示和操作三维图形。有了 WebGL 技术,开发三维的客户端界面、运行三维的网页游戏,对互联网上的海量数据进行三维可视化都成了可能,无需考虑平台问题、无需繁琐的安装过程、无需经堂备份自己的文件到云端来实现共享。虽然 WebGL 强大到令人惊叹,但使用这项技术进行开发却异常简单,让我们一步步探索吧。</p><h2 id="WebGL-程序结构"><a href="#WebGL-程序结构" class="headerlink" title="WebGL 程序结构"></a>WebGL 程序结构</h2><p>在写 WebGL 页面时需要三种语言:HTML5/(CSS)、JavaScript和 GLSL ES(着色器语言),因为通常 GLSL ES 是以字符串的形式在 JavaScript 中编写的,实际上在写 WebGL 页面时结构还是和平常一样。所以,虽然 WebGL 页面更加复杂了一些,但是仍然保持着和传统页面一样的风格。</p>]]></content>
<summary type="html">
<h2 id="WebGL-概述"><a href="#WebGL-概述" class="headerlink" title="WebGL 概述"></a>WebGL 概述</h2><p>WebGL 是一项在网页上绘制和渲染复杂三维图形(3D 图形),并允许用户与之进行交互的技术,在传统上只有高配置的计算机或专用的游戏机才能渲染三维图形,而现在随着个人计算机与浏览器的性能越来越强,使用便捷通用的 Web 技术创建和渲染三维图形已经成为可能。HTML5 作为目前最新的 HTML 标准,扩展了传统 HTML 的特征,如二维图形、网络传输、本地数据储存等,HTML5 时代的到来使浏览器正在迅速地从简单的展示工具转变为复杂的应用平台,人们希望网页不仅仅由二维图组成,所以 WebGL就顺着时代的召唤到来了。</p><h2 id="WebGL-的起源"><a href="#WebGL-的起源" class="headerlink" title="WebGL 的起源"></a>WebGL 的起源</h2><p>在个人计算机上使用最广泛的两种三维图形渲染技术使 Direct3D 和 OpenGL。Direct3D 是微软DirectX 技术的一部分,是一套由微软控制的编程接口(API),主要用在 Windows 平台;而 OpenGL 由于其开放和免费的特征,在多种平台上都有广泛地使用,后来 Windows 对 OpenGL 也提供了良好的支撑,开发者也可以使用它来代替 Direct3D。</p><p>OpenGL 最初是由 SGI(Silicon Graphics Inc)开发,并在 1992 年发布为开源标准。多年以来,OpenGL 发展了数个版本,并对三维图形开发、软件产品开发、甚至电影制作产生了深远的影响。虽然 WebGL 根植于 OpenGL,但它实际上是从 OpenGL 的一个特殊版本 OpenGL ES 派生出来的,OpenGL ES 主要专用于嵌入式计算机、智能手机、家用用洗等设备。OpenGL ES 于 2003~2004 年被首次提出,并在 2007 年(ES 2.0)进行了两次升级,WebGL 是基于 OpenGL ES 2.0 的。这几年采用 OpenGL ES 技术的电子设备的数量大幅度增长,尤其是移动端设备。OpenGL ES 成功被这些设备采用的部分原因是,它在添加新特性的同时从 OpenGL 中移除了许多陈旧的旧特性,这使它在保持轻量级的同时,仍具有足够的能力来渲染出精美的三维图形。</p>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
<category term="3D" scheme="https://sanonz.github.io/tags/3D/"/>
<category term="WebGL" scheme="https://sanonz.github.io/tags/WebGL/"/>
<category term="Canvas" scheme="https://sanonz.github.io/tags/Canvas/"/>
</entry>
<entry>
<title>Hexo 搭建静态博客与 hexo-theme-concise 主题使用教程</title>
<link href="https://sanonz.github.io/2017/hello-world/"/>
<id>https://sanonz.github.io/2017/hello-world/</id>
<published>2017-10-20T15:01:31.000Z</published>
<updated>2024-01-24T02:42:46.275Z</updated>
<content type="html"><![CDATA[<h3 id="什么是-Hexo?"><a href="#什么是-Hexo?" class="headerlink" title="什么是 Hexo?"></a>什么是 Hexo?</h3><p>Hexo 是一款基于Node.js的一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页,你可以把生成的静态网页上传到 Web 服务器上,这里我们选用的是 GitHub 做 Web 服务器,你没看错,全球最大的同性恋交友网站(🙈逃……),然后就可以访问到博客站点,因为每个页面都是静态的,所以访问速度是非常快的,用户体验非常好。</p><h3 id="什么是-Markdown"><a href="#什么是-Markdown" class="headerlink" title="什么是 Markdown"></a>什么是 Markdown</h3><p><a href="http://wowubuntu.com/markdown/" target="_blank" rel="noopener">Markdown</a> 是一种用来写作的轻量级「标记语言」,后缀格式通常为:.md 或 .markdown,用简洁的语法来描述排版格式,可以让我们更专心的写作,因为 Markdown 是纯文本格式,所以可以实现跨平台,不管你切换到什么设备都可以查阅及写作,并且可以轻松的转换为HTML、PDF、电子书等。目前市场上还有大量在线编辑器,支持实时预览,让你充分享受 Markdown 带来的愉悦感。</p><a id="more"></a><h3 id="安装前提"><a href="#安装前提" class="headerlink" title="安装前提"></a>安装前提</h3><p>安装 Hexo 相当简单。然而在安装前,您必须检查电脑中是否已安装下列应用程序:</p><ul><li><a href="http://git-scm.com/" target="_blank" rel="noopener">Git</a></li><li><a href="http://nodejs.org/" target="_blank" rel="noopener">Nodejs</a></li></ul><h3 id="安装与使用"><a href="#安装与使用" class="headerlink" title="安装与使用"></a>安装与使用</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># 设置 npm 为国内镜像提升安装速度,如果你已经设置过了请忽略</span></span><br><span class="line">$ npm config <span class="built_in">set</span> registry https://registry.taobao.org/</span><br><span class="line">$</span><br><span class="line">$ <span class="comment"># 安装 Hexo</span></span><br><span class="line">$ npm install -g hexo-cli</span><br></pre></td></tr></table></figure><blockquote><p>如果报错 <code>certificate has expired</code> 说明 <code>registry.taobao.org</code> 域名证书于 2014-01-22 已经到期,请换成新的 <code>registry.npmmirror.com</code></p></blockquote><p>安装完成后执行以下命令新建一个站点</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># 新建站点</span></span><br><span class="line">$ hexo init blog-markdown</span><br><span class="line">$</span><br><span class="line">$ <span class="comment"># 切换到站点下</span></span><br><span class="line">$ <span class="built_in">cd</span> blog-markdown</span><br><span class="line">$</span><br><span class="line">$ <span class="comment"># 添加一篇文章</span></span><br><span class="line">$ hexo new welcome</span><br></pre></td></tr></table></figure><p>文章默认会被添加到 ./source/_posts 目录下,进入到此目录然后使用心意的编辑器打开 welcome.md 文件</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: welcome</span><br><span class="line">date: 2017-10-20 23:01:31</span><br><span class="line">tags:</span><br><span class="line">---</span><br></pre></td></tr></table></figure><!-- more --><p>编辑为</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: 我发布的第一篇文章</span><br><span class="line">date: 2017-10-20 23:01:31</span><br><span class="line">tags:</span><br><span class="line"><span class="bullet">- </span>生活</span><br><span class="line"><span class="bullet">- </span>诗和远方</span><br><span class="line">---</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">人生不只眼前的苟且,还有诗和远方。</span><br></pre></td></tr></table></figure><p>生成静态文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># 可以使用简写方式:hexo g</span></span><br><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure><p>执行以下命令,在浏览器中打开:<a href="http://localhost:4000/" target="_blank" rel="noopener">http://localhost:4000/</a> 进行预览</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> <span class="comment"># 简写:hexo s</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> hexo server</span></span><br><span class="line">INFO Start processing</span><br><span class="line">INFO Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.</span><br></pre></td></tr></table></figure><h3 id="登陆到-GitHub"><a href="#登陆到-GitHub" class="headerlink" title="登陆到 GitHub"></a>登陆到 GitHub</h3><p>登陆地址:<a href="https://github.com/" target="_blank" rel="noopener">https://github.com/</a>,没有账号注册一个,登陆成功之后点击顶部导航条的➕号,并选择 <a href="https://github.com/new" target="_blank" rel="noopener"><code>New repository</code></a>,如下图</p><img src="/2017/hello-world/[email protected]"><p>填写 Repository name 一项,比如填写 <code>sanonz</code> 然后就可以通过:<a href="https://sanonz.github.io">https://sanonz.github.io</a> 来访问(访问之前根目录要有默认的 index.html 才能正常打开),填写完成后点击 Create repository 按钮提交创建,如下图</p><img src="/2017/hello-world/[email protected]"><p>提交成功后复制红色框住的仓库地址</p><img src="/2017/hello-world/[email protected]"><h3 id="部署到-GitHub"><a href="#部署到-GitHub" class="headerlink" title="部署到 GitHub"></a>部署到 GitHub</h3><p>修改 _config.yml 中的参数</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">deploy:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">git</span></span><br></pre></td></tr></table></figure><p>安装 <a href="https://github.com/hexojs/hexo-deployer-git" target="_blank" rel="noopener">hexo-deployer-git</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-deployer-git</span><br></pre></td></tr></table></figure><p>修改 _config.yml 配置,把刚刚复制的仓库地址粘贴到 repo: 后边</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">deploy:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line"> <span class="attr">repo:</span> <span class="string">https://github.com/Sanonz/your-blog.github.io.git</span></span><br><span class="line"> <span class="attr">branch:</span> <span class="string">master</span></span><br><span class="line"> <span class="comment"># message: [message]</span></span><br></pre></td></tr></table></figure><table><thead><tr><th>参数</th><th>描述</th></tr></thead><tbody><tr><td>repo</td><td>库(Repository)地址</td></tr><tr><td>branch</td><td>分支名称。如果您使用的是 GitHub 或 GitCafe 的话,程序会尝试自动检测。</td></tr><tr><td>message</td><td>自定义提交信息 (默认为 Site updated: {{ now('YYYY-MM-DD HH:mm:ss') }} )</td></tr></tbody></table><p>Hexo 一键部署到 GitHub</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>部署成功后查看站点:<a href="https://your-blog.github.io" target="_blank" rel="noopener">https://your-blog.github.io</a></p><h3 id="使用-hexo-theme-concise-主题"><a href="#使用-hexo-theme-concise-主题" class="headerlink" title="使用 hexo-theme-concise 主题"></a>使用 hexo-theme-concise 主题</h3><p><a href="https://github.com/Sanonz/hexo-theme-concise" target="_blank" rel="noopener">Concise</a> 主题为博主开发的简约主题,也就是本站所用的主题,如果您喜欢的话可以按以下教程切换为本站所使用的主题</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> [email protected]:sanonz/hexo-theme-concise.git themes/concise</span><br></pre></td></tr></table></figure><p>修改 _config.yml 配置</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">theme:</span> <span class="string">concise</span></span><br></pre></td></tr></table></figure><p>因为这个主题使用了 less 编译,Hexo 默认的编译器为 <a href="https://github.com/hexojs/hexo-renderer-stylus" target="_blank" rel="noopener">hexo-renderer-stylus</a> 要切换为 <a href="https://github.com/hexojs/hexo-renderer-less" target="_blank" rel="noopener">hexo-renderer-less</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install hexo-renderer-less --save</span><br></pre></td></tr></table></figure><p>如果你不需要 <code>hexo-renderer-stylus</code> 可以把它卸载掉</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm uninstall hexo-renderer-stylus --save</span><br></pre></td></tr></table></figure><p>清除缓存数据</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ hexo clean</span><br><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p>刷新页面查看新主题效果,Concise 主题的具体配置到 <a href="https://github.com/Sanonz/hexo-theme-concise/blob/master/README.md" target="_blank" rel="noopener">这里查看</a>,如果在使用过程遇到问题欢迎提 <a href="https://github.com/Sanonz/hexo-theme-concise/issues" target="_blank" rel="noopener">Issues</a>,最后如果喜欢的话给个 Star 😍</p>]]></content>
<summary type="html">
<h3 id="什么是-Hexo?"><a href="#什么是-Hexo?" class="headerlink" title="什么是 Hexo?"></a>什么是 Hexo?</h3><p>Hexo 是一款基于Node.js的一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页,你可以把生成的静态网页上传到 Web 服务器上,这里我们选用的是 GitHub 做 Web 服务器,你没看错,全球最大的同性恋交友网站(🙈逃……),然后就可以访问到博客站点,因为每个页面都是静态的,所以访问速度是非常快的,用户体验非常好。</p><h3 id="什么是-Markdown"><a href="#什么是-Markdown" class="headerlink" title="什么是 Markdown"></a>什么是 Markdown</h3><p><a href="http://wowubuntu.com/markdown/" target="_blank" rel="noopener">Markdown</a> 是一种用来写作的轻量级「标记语言」,后缀格式通常为:.md 或 .markdown,用简洁的语法来描述排版格式,可以让我们更专心的写作,因为 Markdown 是纯文本格式,所以可以实现跨平台,不管你切换到什么设备都可以查阅及写作,并且可以轻松的转换为HTML、PDF、电子书等。目前市场上还有大量在线编辑器,支持实时预览,让你充分享受 Markdown 带来的愉悦感。</p>
</summary>
<category term="front-end" scheme="https://sanonz.github.io/categories/front-end/"/>
<category term="Hexo" scheme="https://sanonz.github.io/tags/Hexo/"/>
<category term="Nodejs" scheme="https://sanonz.github.io/tags/Nodejs/"/>
<category term="JavaScript" scheme="https://sanonz.github.io/tags/JavaScript/"/>
</entry>
</feed>