-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrss2.xml
502 lines (247 loc) · 306 KB
/
rss2.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
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>西蒙的实验室</title>
<link>https://flyrk.github.io/</link>
<atom:link href="https://flyrk.github.io/rss2.xml" rel="self" type="application/rss+xml"/>
<description>打开思维,深度思考</description>
<pubDate>Sat, 19 Mar 2022 08:29:19 GMT</pubDate>
<generator>http://hexo.io/</generator>
<item>
<title>你必须了解的Flutter原理</title>
<link>https://flyrk.github.io/2022/03/19/flutter-principle-analysis/</link>
<guid>https://flyrk.github.io/2022/03/19/flutter-principle-analysis/</guid>
<pubDate>Sat, 19 Mar 2022 06:30:27 GMT</pubDate>
<description><p>本文旨在记录分析Flutter的渲染原理和设计理念,参考了一些文档和博客,方便Flutter开发者学习交流。</p></description>
<content:encoded><![CDATA[<p>本文旨在记录分析Flutter的渲染原理和设计理念,参考了一些文档和博客,方便Flutter开发者学习交流。</p><span id="more"></span><h1 id="什么是Flutter"><a href="#什么是Flutter" class="headerlink" title="什么是Flutter"></a>什么是Flutter</h1><p>Flutter 是一个跨平台的 UI 工具集,帮助开发者通过一套代码库高效构建多平台精美应用。</p><h2 id="框架简介"><a href="#框架简介" class="headerlink" title="框架简介"></a>框架简介</h2><h3 id="嵌入层"><a href="#嵌入层" class="headerlink" title="嵌入层"></a>嵌入层</h3><p>对于底层操作系统而言,Flutter 应用程序的包装方式与其他原生应用相同。在每一个平台上,会包含一个特定的嵌入层,从而提供一个程序入口,程序由此可以与底层操作系统进行协调,访问诸如 surface 渲染、辅助功能和输入等服务,并且管理事件循环队列。该嵌入层采用了适合当前平台的语言编写,例如 Android 使用的是 Java 和 C++, iOS 和 macOS 使用的是 Objective-C 和 Objective-C++,Windows 和 Linux 使用的是 C++。 Flutter 代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。 Flutter 本身包含了各个常见平台的嵌入层。</p><h3 id="引擎层"><a href="#引擎层" class="headerlink" title="引擎层"></a>引擎层</h3><p>Flutter 引擎 是 Flutter 的核心,它主要使用 C++ 编写,并提供了 Flutter 应用所需的原语。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter 核心 API 的底层实现,包括图形(通过 Skia)、文本布局、文件及网络 IO、辅助功能支持、插件架构和 Dart 运行环境及编译环境的工具链。<br>引擎将底层 C++ 代码包装成 Dart 代码,通过 dart:ui 暴露给 Flutter 框架层。该库暴露了最底层的原语,包括用于驱动输入、图形、和文本渲染的子系统的类。</p><h3 id="框架层"><a href="#框架层" class="headerlink" title="框架层"></a>框架层</h3><p>通常,开发者可以通过 Flutter 框架层 与 Flutter 交互,该框架提供了以 Dart 语言编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:</p><p>基础的 foundational 类及一些基层之上的构建块服务,如 animation、 painting 和 gestures,它们可以提供上层常用的抽象。</p><p>渲染层:用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。</p><p>widget层: 是一种组合的抽象。每一个渲染层中的渲染对象,都在 widgets 层中有一个对应的类。此外,widgets 层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。</p><p>Material 和 Cupertino 库提供了全面的 widgets 层的原语组合,这套组合分别实现了 Material 和 iOS 设计规范。</p><p><img src="/../images/flutter-framework.png" alt="框架简介"></p><h1 id="渲染原理"><a href="#渲染原理" class="headerlink" title="渲染原理"></a>渲染原理</h1><h2 id="渲染管道"><a href="#渲染管道" class="headerlink" title="渲染管道"></a>渲染管道</h2><p><img src="/../images/render-pipeline.png" alt="管道图"></p><h2 id="视图树"><a href="#视图树" class="headerlink" title="视图树"></a>视图树</h2><p><img src="/../images/widget-type.png" alt="Widget&Element&RenderObject"></p><h2 id="创建树"><a href="#创建树" class="headerlink" title="创建树"></a>创建树</h2><ol><li>创建widget树</li><li>调用runApp(rootWidget),将rootWidget传给rootElement,做为rootElement的子节点,生成Element树,由Element树生成Render树。</li></ol><p><img src="/../images/render-tree.png" alt="渲染树"></p><ul><li>Widget:存放渲染内容、视图布局信息,widget的属性最好都是immutable</li><li>Element:存放上下文,通过Element遍历视图树,Element同时持有Widget和RenderObject</li><li>RenderObject:根据Widget的布局属性进行layout,paint Widget传人的内容</li></ul><h2 id="更新树"><a href="#更新树" class="headerlink" title="更新树"></a>更新树</h2><h3 id="为什么widget都是immutable?"><a href="#为什么widget都是immutable?" class="headerlink" title="为什么widget都是immutable?"></a>为什么widget都是immutable?</h3><p>flutter界面开发是一种响应式编程,主张simple is fast,flutter设计的初衷希望数据变更时发送通知到对应的可变更节点(可能是一个StatefullWidget子节点,也可以是rootWidget),由上到下重新create widget树进行刷新,这种思路比较简单,不用关心数据变更会影响到哪些节点。</p><h3 id="widget重新创建,element树和renderObject树是否也重新创建?"><a href="#widget重新创建,element树和renderObject树是否也重新创建?" class="headerlink" title="widget重新创建,element树和renderObject树是否也重新创建?"></a>widget重新创建,element树和renderObject树是否也重新创建?</h3><p>widget只是一个配置数据结构,创建是非常轻量的,加上flutter团队对widget的创建/销毁做了优化,不用担心整个widget树重新创建所带来的性能问题,但是renderobject就不一样了,renderobject涉及到layout、paint等复杂操作,是一个真正渲染的view,整个view 树重新创建开销就比较大,所以答案是否定的。</p><h3 id="树的更新规则"><a href="#树的更新规则" class="headerlink" title="树的更新规则"></a>树的更新规则</h3><ol><li>找到widget对应的element节点,设置element为dirty,触发drawframe, drawframe会调用element的performRebuild()进行树重建</li><li>widget.build() == null, deactive element.child,删除子树,流程结束</li><li>element.child.widget == NULL, mount 的新子树,流程结束</li><li>element.child.widget == widget.build() 无需重建,否则进入流程5</li><li>Widget.canUpdate(element.child.widget, newWidget) == true,更新child的slot,element.child.update(newWidget)(如果child还有子节点,则递归上面的流程进行子树更新),流程结束,否则转6</li><li>Widget.canUpdate(element.child.widget, newWidget) != true(widget的classtype 或者 key 不相等),deactivew element.child,mount 新子树</li></ol><p>注意事项:</p><ol><li>element.child.widget == widget.build(),不会触发子树的update,当触发update的时候,如果没有生效,要注意widget是否使用旧widget,没有new widget,导致update流程走到该widget就停止了</li><li>子树的深度变化,会引起子树重建,如果子树是一个复杂度很高的树,可以使用GlobalKey做为子树widget的key。GlobalKey具有缓存功能</li></ol><h3 id="如何触发树更新"><a href="#如何触发树更新" class="headerlink" title="如何触发树更新"></a>如何触发树更新</h3><ol><li>全局更新:调用runApp(rootWidget),一般flutter启动时调用后不再会调用</li><li>局部子树更新, 将该子树做StatefullWidget的一个子widget,并创建对应的State类实例,通过调用state.setState() 触发该子树的刷新</li></ol><h1 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h1><p>widget是immutable的,发生变化的时候需要重建,所以谈不上状态。StatefulWidget 中的状态保持其实是通过State类来实现的。State拥有一套自己的生命周期。</p><table><thead><tr><th>名称</th><th>状态</th></tr></thead><tbody><tr><td>initState</td><td>插入渲染树时调用,只调用一次</td></tr><tr><td>didChangeDependencies</td><td>state依赖的对象发生变化时调用</td></tr><tr><td>didUpdateWidget</td><td>组件状态改变时候调用,可能会调用多次</td></tr><tr><td>build</td><td>构建Widget时调用</td></tr><tr><td>deactivate</td><td>当移除渲染树的时候调用</td></tr><tr><td>dispose</td><td>组件即将销毁时调用</td></tr><tr><td>reassemble</td><td>hot reload调用</td></tr></tbody></table><p><img src="/../images/flutter-life-render.png" alt="生命周期图"></p><p>几个注意点</p><ul><li>didChangeDependencies有两种情况会被调用。<ul><li>创建时候在initState 之后被调用</li><li>在依赖的InheritedWidget发生变化的时候会被调用</li></ul></li><li>正常的退出流程中会执行deactivate然后执行dispose。但是也会出现deactivate以后不执行dispose,直接加入树中的另一个节点的情况。</li><li>这里的状态改变包括两种可能:1.通过setState内容改变 2.父节点的state状态改变,导致孩子节点的同步变化。</li><li>A页面push一个新的页面B,A页面的widget树中的所有state会依次调用deactivate(), didUpdateWidget(newWidget)、build()(这里怀疑是bug,A页面push一个新页面,理论上并没有将A页面进行remove操作),当然从功能上,没有看出来有什么异常</li><li>当ListView中的item滚动出可显示区域的时候,item会被从树中remove掉,此item子树中所有的state都会被dispose,state记录的数据都会销毁,item滚动回可显示区域时,会重新创建全新的state、element、renderobject</li><li>使用hot reload功能时,要特别注意state实例是没有重新创建的,如果该state中存在一下复杂的资源更新需要重新加载才能生效,那么需要在reassemble()添加处理,不然当你使用hot reload时候可能会出现一些意想不到的结果,例如,要将显示本地文件的内容到屏幕上,当你开发过程中,替换了文件中的内容,但是hot reload没有触发重新读取文件内容,页面显示还是原来的旧内容</li></ul><h1 id="APP生命周期"><a href="#APP生命周期" class="headerlink" title="APP生命周期"></a>APP生命周期</h1><p>需要通过WidgetsBindingObserver的didChangeAppLifecycleState 来获取。通过该接口可以获取是生命周期在AppLifecycleState类中。常用状态包含如下几个:</p><table><thead><tr><th>名称</th><th>状态</th></tr></thead><tbody><tr><td>resumed</td><td>可见并能响应用户的输入</td></tr><tr><td>inactive</td><td>处在并不活动状态,无法处理用户响应</td></tr><tr><td>paused</td><td>不可见并不能响应用户的输入,但是在后台继续活动中</td></tr></tbody></table><h1 id="数据流转"><a href="#数据流转" class="headerlink" title="数据流转"></a>数据流转</h1><h2 id="从上往下"><a href="#从上往下" class="headerlink" title="从上往下"></a>从上往下</h2><p>数据从根往下传数据,常规做法是一层层往下,当深度变大,数据的传输变的困难,flutter提供InheritedWidget用于子节点向祖先节点获取数据的机制,如下例子:</p><figure class="highlight dart"><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="class"><span class="keyword">class</span> <span class="title">FrogColor</span> <span class="keyword">extends</span> <span class="title">InheritedWidget</span> </span>{</span><br><span class="line"> <span class="keyword">const</span> FrogColor({</span><br><span class="line"> Key key,</span><br><span class="line"> <span class="meta">@required</span> <span class="keyword">this</span>.color,</span><br><span class="line"> <span class="meta">@required</span> Widget child,</span><br><span class="line"> }) : <span class="keyword">assert</span>(color != <span class="keyword">null</span>),</span><br><span class="line"> <span class="keyword">assert</span>(child != <span class="keyword">null</span>),</span><br><span class="line"> <span class="keyword">super</span>(key: key, child: child);</span><br><span class="line"> <span class="keyword">final</span> Color color;</span><br><span class="line"> <span class="keyword">static</span> FrogColor of(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> context.inheritFromWidgetOfExactType(FrogColor);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="built_in">bool</span> updateShouldNotify(FrogColor old) => color != old.color;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>child及其以下的节点可以通过调用下面的接口读取color数据<code>FrogColor.of(context).color</code></p><p>说明:BuildContext 就是Element的一个接口类</p><p><code>context.inheritFromWidgetOfExactType(FrogColor)</code>其实是通过context/element往上遍历树,查找到第一个FrogColor的祖先节点,取该节点的widget对象。</p><h2 id="从下往上"><a href="#从下往上" class="headerlink" title="从下往上"></a>从下往上</h2><p>子节点状态变更,向上上报通过发送通知的方式</p><ul><li>定义通知类,继承至Notification</li><li>父节点使用NotificationListener 进行监听捕获通知</li><li>子节点有数据变更调用下面接口进行数据上报<code>Notification(data).dispatch(context)</code></li></ul><h1 id="Widget"><a href="#Widget" class="headerlink" title="Widget"></a>Widget</h1><h2 id="StatefulWidget"><a href="#StatefulWidget" class="headerlink" title="StatefulWidget"></a>StatefulWidget</h2><p>有状态组件,一般形态:</p><figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyWidgetRoute</span> <span class="keyword">extends</span> <span class="title">StatefulWidget</span> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> State<StatefulWidget> createState() {</span><br><span class="line"> <span class="keyword">return</span> _MyWidgetRouteState();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_MyWidgetRouteState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">MyWidgetRoute</span>> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> initState() {</span><br><span class="line"> <span class="keyword">super</span>.initState();</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> Scaffold(</span><br><span class="line"> <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><h2 id="RouteObserver"><a href="#RouteObserver" class="headerlink" title="RouteObserver"></a>RouteObserver</h2><p>路由观察器,用来监听路由变化。</p><figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 注册RouteObserver.</span></span><br><span class="line"><span class="keyword">final</span> RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();</span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line"> runApp(MaterialApp(</span><br><span class="line"> home: Container(),</span><br><span class="line"> navigatorObservers: [routeObserver],</span><br><span class="line"> ));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在页面组件使用</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RouteAwareWidget</span> <span class="keyword">extends</span> <span class="title">StatefulWidget</span> </span>{</span><br><span class="line"> State<RouteAwareWidget> createState() => RouteAwareWidgetState();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Implement RouteAware in a widget's state and subscribe it to the RouteObserver.</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RouteAwareWidgetState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">RouteAwareWidget</span>> <span class="title">with</span> <span class="title">RouteAware</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> didChangeDependencies() {</span><br><span class="line"> <span class="keyword">super</span>.didChangeDependencies();</span><br><span class="line"> routeObserver.subscribe(<span class="keyword">this</span>, ModalRoute.of(context));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> dispose() {</span><br><span class="line"> routeObserver.unsubscribe(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">super</span>.dispose();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> didPush() {</span><br><span class="line"> <span class="comment">// Route was pushed onto navigator and is now topmost route.</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> didPopNext() {</span><br><span class="line"> <span class="comment">// Covering route was popped off the navigator.</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) => Container();</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="StreamBuilder"><a href="#StreamBuilder" class="headerlink" title="StreamBuilder"></a>StreamBuilder</h2><p>流媒体</p><h2 id="Container"><a href="#Container" class="headerlink" title="Container"></a>Container</h2><p>用来包裹widget,类似于div,可以设置背景色、大小之类的。举例:</p><figure class="highlight dart"><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">Container(</span><br><span class="line"> decoration: BoxDecoration( <span class="comment">// 盒子模型</span></span><br><span class="line"> color: <span class="keyword">const</span> Color(<span class="number">0xff7c94b6</span>),</span><br><span class="line"> border: Border.all(</span><br><span class="line"> color: Colors.black,</span><br><span class="line"> width: <span class="number">8</span>,</span><br><span class="line"> ),</span><br><span class="line"> borderRadius: BorderRadius.circular(<span class="number">12</span>),</span><br><span class="line"> )</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h2 id="Stack"><a href="#Stack" class="headerlink" title="Stack"></a>Stack</h2><p>用于样式堆叠</p><h3 id="Positioned"><a href="#Positioned" class="headerlink" title="Positioned"></a>Positioned</h3><p>用于Stack的child样式定位。类似于<code>position: fixed;</code>举例:</p><figure class="highlight dart"><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">Stack(</span><br><span class="line"> fit: StackFit.expand,</span><br><span class="line"> alignment: Alignment.center,</span><br><span class="line"> children: [</span><br><span class="line"> Positioned(</span><br><span class="line"> child: Column(),</span><br><span class="line"> top: MediaQuery.of(context).vewPadding.top + <span class="number">100</span>, <span class="comment">// 离顶部100px</span></span><br><span class="line"> ),</span><br><span class="line"> Positioned(</span><br><span class="line"> child: Column(),</span><br><span class="line"> bottom: MediaQuery.of(context).viewPadding.bottom + <span class="number">100</span>, <span class="comment">// 离底部100px</span></span><br><span class="line"> ),</span><br><span class="line"> Positioned.fill( <span class="comment">// 创建top、right、bottom、left默认为0的widget</span></span><br><span class="line"> child: Row(),</span><br><span class="line"> )</span><br><span class="line"> ]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h2 id="Column"><a href="#Column" class="headerlink" title="Column"></a>Column</h2><p>垂直排列child,相当于<code>justify-content: column;</code></p><h2 id="Row"><a href="#Row" class="headerlink" title="Row"></a>Row</h2><p>水平排列child,相当于<code>justify-content: row;</code></p><h2 id="SizedBox"><a href="#SizedBox" class="headerlink" title="SizedBox"></a>SizedBox</h2><p>创建一个长宽固定的box</p><figure class="highlight dart"><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">SizedBox(</span><br><span class="line"> width: <span class="number">200</span>,</span><br><span class="line"> height: <span class="number">50</span>,</span><br><span class="line"> child: Button()</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">SizedBox( <span class="comment">// 长宽自动撑满父元素</span></span><br><span class="line"> width: <span class="built_in">double</span>.infinity,</span><br><span class="line"> height: <span class="built_in">double</span>.infinity,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h2 id="Text"><a href="#Text" class="headerlink" title="Text"></a>Text</h2><p>设置文本</p><figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line">Text(</span><br><span class="line"> <span class="string">'title'</span>,</span><br><span class="line"> textAlign: TextAlign.center,</span><br><span class="line"> overflow: TextOverflow.ellipsis,</span><br><span class="line"> style: TextStyle(</span><br><span class="line"> color: Colors.black.withOpacity(<span class="number">0.9</span>),</span><br><span class="line"> fontSize: <span class="number">17</span>,</span><br><span class="line"> fontWeight: FontWeight.w500</span><br><span class="line"> ),</span><br><span class="line">),</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建能包含不同样式的inline span</span></span><br><span class="line">Text.rich(</span><br><span class="line"> TextSpan(</span><br><span class="line"> text: <span class="string">'Hello'</span>, <span class="comment">// default text style</span></span><br><span class="line"> children: <TextSpan>[</span><br><span class="line"> TextSpan(text: <span class="string">' beautiful '</span>, style: TextStyle(fontStyle: FontStyle.italic)),</span><br><span class="line"> TextSpan(text: <span class="string">'world'</span>, style: TextStyle(fontWeight: FontWeight.bold)),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>持续更新。。。</p><blockquote><p>参考资料<br><a href="https://www.yuque.com/xytech/flutter/tge705">深入了解Flutter界面开发</a><br><a href="https://docs.flutter.dev/">Flutter Doc</a></p></blockquote>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF/">移动端</category>
<category domain="https://flyrk.github.io/tags/flutter/">flutter</category>
<category domain="https://flyrk.github.io/tags/dart/">dart</category>
<comments>https://flyrk.github.io/2022/03/19/flutter-principle-analysis/#disqus_thread</comments>
</item>
<item>
<title>clarity-js源码分析系列(三)之元素变化</title>
<link>https://flyrk.github.io/2022/03/18/clarity-source_analysis-3/</link>
<guid>https://flyrk.github.io/2022/03/18/clarity-source_analysis-3/</guid>
<pubDate>Fri, 18 Mar 2022 07:47:06 GMT</pubDate>
<description><p>页面监控需要尽可能多地收集数据,这样才有可能还原出当时用户操作的真实场景。</p>
<p>还原用户场景常见的有几种方法:逐帧截屏、记录DOM元素及其变化、录制视频。</p>
<p>目前主流的用户行为监控方案都是用的记录DOM元素变化,主要优点是对用户基本上无感知,并且生成的数据量相比另外几种偏小,对性能的影响也不大。本篇我们就来深入分析下clarity在这块做了什么。</p></description>
<content:encoded><![CDATA[<p>页面监控需要尽可能多地收集数据,这样才有可能还原出当时用户操作的真实场景。</p><p>还原用户场景常见的有几种方法:逐帧截屏、记录DOM元素及其变化、录制视频。</p><p>目前主流的用户行为监控方案都是用的记录DOM元素变化,主要优点是对用户基本上无感知,并且生成的数据量相比另外几种偏小,对性能的影响也不大。本篇我们就来深入分析下clarity在这块做了什么。</p><span id="more"></span><h1 id="遍历元素"><a href="#遍历元素" class="headerlink" title="遍历元素"></a>遍历元素</h1><p>在初始化的时候,会对所有元素进行遍历,绑定监听事件。浏览器支持一种方法:<code>MutationObserver</code>,相当于以观察者的角色对元素的属性、字符、子元素变化进行监听。文档可以参考<a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">这里</a>。</p><p>首先,在遍历之前,会先对css相关的元素进行一个hook,比如<code><style></code>和<code><link></code>。因为当使用<code>CSSStyleSheet.insertRule</code>来添加css时不会触发DOM变化,所以需要在调用<code>insertRule</code>API的时候把node元素添加到延时队列里面去,以便之后统一监听。</p><p>接下来,从document开始遍历,这里会把任务都塞到一个task队列里。这个队列的作用是异步执行任务,并且按照优先级高低来排序,这里mutation的任务是最高级。</p><p>遍历DOM树,对每个元素进行处理。这里要注意的是,只有<code>document</code>、<code>shadowRoot</code>、<code>iframe</code>才用MutationObserver去观察,其他元素只更新DOM树信息。</p><p>因为<code>document</code>、<code>shadowRoot</code>、<code>iframe</code>这三种类型的元素每个都相当于一个独立的document隔离环境,需要分别去监听,iframe只支持同源。</p><h1 id="观察元素"><a href="#观察元素" class="headerlink" title="观察元素"></a>观察元素</h1><p>那么对于每个元素是怎么观察的呢?这里同样也是把观察元素的函数加到任务队列里执行。</p><p>首先我们知道,<code>observe</code>执行函数会返回一个<code>MutationRecord[]</code>数组,对于每个<code>mutation</code>,会有三种变化类型:<code>Atrributes</code>、<code>CharacterData</code>、<code>ChildList</code>,分别代表着属性、字符数据、子元素的变化。对于这三种变化,分别再去递归遍历更新DOM树信息。</p><p>这里还有一个特殊处理,对于每个<code>mutation</code>,用<code>parent.selector</code>+<code>target.selector</code>+<code>target.attrributeName</code>+<code>target.addedNodes</code>+<code>target.removeNodes</code>来作为唯一的<code>Key</code>。clarity对于每个正在交互操作的元素会更新一个激活态,表示当前是否有交互操作。对于当前mutation如果没有激活态,就先不进行处理,除非积累到一定程度(同一个mutation执行10次)后再去移除这个mutation需要干掉的元素。</p><p>我想这样做的目的是因为一般我们更关注在进行交互的时候使DOM发生的变化,如果没有交互的话,我们没必要重复去更新同一个mutation,而是等它积攒到一定次数后统一进行一次的变化处理,节省一些函数操作。</p><h1 id="更新元素"><a href="#更新元素" class="headerlink" title="更新元素"></a>更新元素</h1><p>对于每个元素,我们会去进行一个id查找,只会在原有的DOM树基础上进行更新或者添加删除。等到所有元素的变化都更新记录完后,会把该次更新所有的相关信息push到数据队列里,然后在upload里统一上报。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>利用了<code>MutationObserver</code>,我们可以很方便的对DOM元素的变化进行更新,为了减少冗余更新信息的记录,我们会对所有交互行为记录一个激活态,非激活态的更新元素我们不会更新的那么频繁,每次<code>observe</code>观察到的mutation全部更新完后,我们再把数据存入队列里,统一上报,这样就达到对DOM元素变化更新记录的效果。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/">源码分析</category>
<category domain="https://flyrk.github.io/tags/%E5%89%8D%E7%AB%AF%E7%9B%91%E6%8E%A7/">前端监控</category>
<category domain="https://flyrk.github.io/tags/%E5%BD%95%E5%88%B6%E5%9B%9E%E6%94%BE/">录制回放</category>
<comments>https://flyrk.github.io/2022/03/18/clarity-source_analysis-3/#disqus_thread</comments>
</item>
<item>
<title>clarity-js源码分析系列(二)之数据处理</title>
<link>https://flyrk.github.io/2022/02/26/clarity-source-analysis-2/</link>
<guid>https://flyrk.github.io/2022/02/26/clarity-source-analysis-2/</guid>
<pubDate>Sat, 26 Feb 2022 12:20:47 GMT</pubDate>
<description><p>上一篇我们大概介绍了clarity各个模块的作用,本篇我想着重来了解下<code>clarity</code>在数据存储、日志上报、性能优化方面做的事情,并试着理清clarity整个的架构。</p></description>
<content:encoded><![CDATA[<p>上一篇我们大概介绍了clarity各个模块的作用,本篇我想着重来了解下<code>clarity</code>在数据存储、日志上报、性能优化方面做的事情,并试着理清clarity整个的架构。</p><span id="more"></span><h1 id="数据存储"><a href="#数据存储" class="headerlink" title="数据存储"></a>数据存储</h1><p>对于前端监控,特别是需要录制回放功能的时候,数据就是生命。没有了监控数据,一切都免谈。</p><p>但是,为了尽可能收集用户的行为特征,和方便之后的行为分析和日志定位,肯定需要监控许多数据,光我一下子想到的就有这些方面:DOM元素信息、鼠标交互信息、页面状态、用户停留时长等等。</p><p>面对这么多的数据,首先想到的就是怎么去存储。我以为这么多数据,clarity肯定会用indexDB去存储在本地,但是通过源码发现,并没有。那么clarity是怎么做的呢?</p><h2 id="队列缓存"><a href="#队列缓存" class="headerlink" title="队列缓存"></a>队列缓存</h2><p>clarity直接用js数组来缓存数据对象,每当有新的数据加入,通过<code>encode</code>函数统一分发处理,然后塞到数组队列里去:</p><figure class="highlight typescript"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">queue</span>(<span class="params">tokens: Token[], transmit: <span class="built_in">boolean</span> = <span class="literal">true</span></span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="keyword">if</span> (active) {</span><br><span class="line"> <span class="keyword">let</span> now = <span class="title function_">time</span>();</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">type</span> = tokens.<span class="property">length</span> > <span class="number">1</span> ? tokens[<span class="number">1</span>] : <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">let</span> event = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(tokens);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span> (<span class="keyword">type</span>) {</span><br><span class="line"> <span class="keyword">case</span> <span class="title class_">Event</span>.<span class="property">Discover</span>:</span><br><span class="line"> discoverBytes += event.<span class="property">length</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="title class_">Event</span>.<span class="property">Box</span>:</span><br><span class="line"> <span class="keyword">case</span> <span class="title class_">Event</span>.<span class="property">Mutation</span>:</span><br><span class="line"> playbackBytes += event.<span class="property">length</span>;</span><br><span class="line"> playback.<span class="title function_">push</span>(event);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="attr">default</span>:</span><br><span class="line"> analysis.<span class="title function_">push</span>(event);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> metric.<span class="title function_">count</span>(<span class="title class_">Metric</span>.<span class="property">EventCount</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> gap = <span class="title function_">delay</span>();</span><br><span class="line"> <span class="keyword">if</span> (now - queuedTime > (gap * <span class="number">2</span>)) {</span><br><span class="line"> <span class="built_in">clearTimeout</span>(timeout);</span><br><span class="line"> timeout = <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (transmit && timeout === <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">type</span> !== <span class="title class_">Event</span>.<span class="property">Ping</span>) { ping.<span class="title function_">reset</span>(); }</span><br><span class="line"> timeout = <span class="built_in">setTimeout</span>(upload, gap);</span><br><span class="line"> queuedTime = now;</span><br><span class="line"> limit.<span class="title function_">check</span>(playbackBytes);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>根据这段代码,我们可以知道<code>queue</code>主要做了两件事:</p><p>1、根据数据类型分类塞到不同的队列,供之后上传使用。</p><p>2、部分数据利用<code>setTimeout</code>延时上报。</p><p>注意的是,大部分数据在延时100~3000ms后都会立即上报,也是为了及时释放数据,不至于堆积得太多。除了某些场景,比如<code>metric</code>模块相关的性能指标,需要一直关注,会在最后结束的时候统一再上传。</p><h2 id="数据检测"><a href="#数据检测" class="headerlink" title="数据检测"></a>数据检测</h2><p>这里还进行了数据的检测:<code>limit.check(playbackBytes)</code>。主要是进行三种检测:</p><p>1、payload不能超过128个,也就是<code>envelope</code>对象的个数。</p><p>2、页面实例开启不能超过2个小时,也就是说最多能监测在页面停留两个小时的数据。</p><p>3、数据大小不能超过10MB。</p><p>如果不满足其中任何一种限制,则立即停止clarity实例。</p><p>经过这些限制,有效地保证了数据的大小,不至于内存过高,导致页面崩溃或者是上传过多不必要的数据。</p><h1 id="日志上报"><a href="#日志上报" class="headerlink" title="日志上报"></a>日志上报</h1><h2 id="上报时机"><a href="#上报时机" class="headerlink" title="上报时机"></a>上报时机</h2><p>日志上报的时机有两个:</p><p>1、在数据刚进入队列时,延时几百毫秒立即上报。</p><p>2、在实例<code>stop</code>的时候上报,比如<code>pagehide</code>。</p><h2 id="数据压缩"><a href="#数据压缩" class="headerlink" title="数据压缩"></a>数据压缩</h2><p>上报的操作其实就是把当前收集到的所有数据集中起来,打包成一个<code>payload</code>。由于数据会很大,需要对<code>payload</code>进行压缩操作:</p><figure class="highlight typescript"><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 class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">async</span> <span class="keyword">function</span>(<span class="params">input: <span class="built_in">string</span></span>): <span class="title class_">Promise</span><<span class="title class_">Uint8Array</span>> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (supported) {</span><br><span class="line"> <span class="keyword">const</span> stream = <span class="keyword">new</span> <span class="title class_">ReadableStream</span>({<span class="keyword">async</span> <span class="title function_">start</span>(<span class="params">controller</span>) {</span><br><span class="line"> controller.<span class="title function_">enqueue</span>(input);</span><br><span class="line"> controller.<span class="title function_">close</span>();</span><br><span class="line"> }}).<span class="title function_">pipeThrough</span>(<span class="keyword">new</span> <span class="title class_">TextEncoderStream</span>()).<span class="title function_">pipeThrough</span>(<span class="keyword">new</span> <span class="variable language_">window</span>[<span class="title class_">Constant</span>.<span class="property">CompressionStream</span>](<span class="string">"gzip"</span>));</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Uint8Array</span>(<span class="keyword">await</span> <span class="title function_">read</span>(stream));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> { <span class="comment">/* do nothing */</span> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过源码得知这里的压缩原理:创建<code>ReadableStream</code>管道,将数据字符串塞进去,通过<code>TextEncoderStream</code>和<code>CompressionStream</code>对其进行<code>gzip</code>压缩。</p><h2 id="上报方式"><a href="#上报方式" class="headerlink" title="上报方式"></a>上报方式</h2><p>数据压缩完毕,下一步就是发起上报请求。常用的前端监控上报可以通过三种方式上报:</p><p>1、<code>(new Image()).src = ${uploadUrl}</code>,这里一般适合数据小,并且需要跨域的请求。</p><p>2、<code>ajax</code>请求,这个也是最常见的上报方式。</p><p>3、<code>sendBeacon</code>,一般用在<code>unload</code>或者页面关闭的时候,用于发送统计数据。因为在页面关闭时,正常的<code>ajax</code>异步请求经常会出现丢失情况,而同步请求又会阻塞页面的关闭跳转,有了<code>sendBeacon</code>,会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。不过要要注意的是,这里使用的是<code>POST</code>请求,而且无法设置<code>HTTP headers</code>,所以不能用压缩的数据,只能用字符串。</p><p>这里<code>clarity</code>使用了三种策略:</p><p>1、正常上报使用<code>ajax</code>请求,并且优先上报压缩过的数据(考虑到CompressionStream的浏览器兼容性,如果不支持则用原始字符串数据)。</p><p>2、如果是在页面关闭或者停止<code>clarity</code>实例时,优先用<code>sendBeacon</code>,如果不支持则用回<code>ajax</code>。</p><p>3、支持配置<code>upload</code>参数,利用用户自定义的<code>upload</code>函数来上传。</p><p>当然,这里的上报还做了保护机制,支持上报失败自动重试,并且在首次上报后还会更新<code>session</code>,保持会话状态。</p><h1 id="性能优化"><a href="#性能优化" class="headerlink" title="性能优化"></a>性能优化</h1><p>对于一个前端监控SDK来说,性能非常重要。因为我们的目的是监控页面,但是不能影响到用户的正常操作,而是以一个观察者的角色在旁边看着。如果监控代码影响到页面性能,甚至引发页面卡顿、或者出bug,阻塞用户正常页面操作,那就得不偿失了。</p><p>为了检测clarity的性能,我在我的博客加上了<code>claritySDK</code>,看看性能表现如何。</p><p>通过几天的观察,目前性能还算良好,每次交互完都会立即上报。不过因为博客页面比较简单,没有什么复杂的交互,主要是点击和滚动页面,每次上报的数据一般都是几十B,内存损耗比较小。</p><p>但同样的,通过Chrome本身的性能指标监控,发现每次交互完JS的堆内存都会暴涨一波,等上报完内存就会释放。虽然说内存及时释放了,但是如果是更复杂的页面,需要大量的交互操作,比如编辑器,那内存可能会居高不下,所以这方面的性能还有待提高。</p><p>从SDK代码本身来说,没有什么高耗时的函数运算,其风险主要还是在内存爆栈上。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p><code>clarity</code>总体上还是从以下几个方面去做监控:</p><p>页面信息采集 -> 事件监听 -> 数据分类 -> 批量上报。</p><p>其中我认为做的比较好的部分是页面信息采集和数据分类,尽可能多的去收集页面信息,并且记录元素对象和id,同时采用了一些数据压缩方式,来保证数据的完整性和可用性。</p><p>但同时,对于DOM结构复杂或者交互复杂的页面,<code>clarity</code>会占用大量的内存,由于是采用纯数组和对象存储,导致JS堆栈在某些时刻会飙升,非常有可能影响到页面性能,这方面的性能损耗还需进一步评测。</p><p>总的来说,在数据采集端<code>clarity</code>对数据的收集还是比较完备,不足的是性能优化方面,但对于大部分普通HTML页面,没有多少交互来说也能够支持。而<code>clarity</code>的模块分类和数据收集方式也是值得我们好好学习的。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/">源码分析</category>
<category domain="https://flyrk.github.io/tags/%E5%89%8D%E7%AB%AF%E7%9B%91%E6%8E%A7/">前端监控</category>
<category domain="https://flyrk.github.io/tags/%E5%BD%95%E5%88%B6%E5%9B%9E%E6%94%BE/">录制回放</category>
<comments>https://flyrk.github.io/2022/02/26/clarity-source-analysis-2/#disqus_thread</comments>
</item>
<item>
<title>clarity-js源码分析系列(一)之代码模块</title>
<link>https://flyrk.github.io/2022/02/24/clarity-source-analysis-1/</link>
<guid>https://flyrk.github.io/2022/02/24/clarity-source-analysis-1/</guid>
<pubDate>Thu, 24 Feb 2022 07:20:47 GMT</pubDate>
<description><p>前端监控一直是前端不可或缺的一部分,这里我调研了微软的<a href="https://github.com/microsoft/clarity">clarity</a>,它们主要是针对用户的行为监控进行录制回放,并且能生成热力图分析。为了彻底搞清楚其中的原理,对<code>clarity-js</code>进行了源码分析。</p>
<p>话不多说,直接开始!</p></description>
<content:encoded><![CDATA[<p>前端监控一直是前端不可或缺的一部分,这里我调研了微软的<a href="https://github.com/microsoft/clarity">clarity</a>,它们主要是针对用户的行为监控进行录制回放,并且能生成热力图分析。为了彻底搞清楚其中的原理,对<code>clarity-js</code>进行了源码分析。</p><p>话不多说,直接开始!</p><span id="more"></span><h1 id="整体代码结构"><a href="#整体代码结构" class="headerlink" title="整体代码结构"></a>整体代码结构</h1><figure class="highlight plaintext"><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">clarity-js</span><br><span class="line"> -- src</span><br><span class="line"> -- core</span><br><span class="line"> -- data</span><br><span class="line"> -- diagnostic</span><br><span class="line"> -- interaction</span><br><span class="line"> -- layout</span><br><span class="line"> -- performance</span><br><span class="line"> clarity.ts</span><br><span class="line"> global.ts</span><br><span class="line"> index.ts</span><br></pre></td></tr></table></figure><p>下面围绕入口文件<code>index.ts</code>开始逐步分析。</p><h1 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h1><h2 id="index-ts"><a href="#index-ts" class="headerlink" title="index.ts"></a>index.ts</h2><p>入口文件,主要导出三个对象:<code>export { clarity, version, helper };</code>。</p><p>这里我们主要关注<code>clarity</code>对象。</p><p><code>clarity</code>关键导出四个方法:<code>start、pause、resume、stop</code>,从字面上也能猜出他们分别代表的功能:开始、暂停、继续、停止。</p><p>先来看源码:</p><figure class="highlight typescript"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">start</span>(<span class="params">config: Config = <span class="literal">null</span></span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="comment">// 先检查浏览器是否支持相关api</span></span><br><span class="line"> <span class="comment">// 保证不会多次执行start</span></span><br><span class="line"> <span class="keyword">if</span> (core.<span class="title function_">check</span>()) {</span><br><span class="line"> core.<span class="title function_">config</span>(config);</span><br><span class="line"> core.<span class="title function_">start</span>();</span><br><span class="line"> data.<span class="title function_">start</span>();</span><br><span class="line"> modules.<span class="title function_">forEach</span>(<span class="function"><span class="params">x</span> =></span> <span class="title function_">measure</span>(x.<span class="property">start</span>)());</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">function</span> <span class="title function_">pause</span>(<span class="params"></span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="keyword">if</span> (core.<span class="title function_">active</span>()) {</span><br><span class="line"> data.<span class="title function_">event</span>(<span class="title class_">Constant</span>.<span class="property">Clarity</span>, <span class="title class_">Constant</span>.<span class="property">Pause</span>);</span><br><span class="line"> task.<span class="title function_">pause</span>();</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 class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">resume</span>(<span class="params"></span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="keyword">if</span> (core.<span class="title function_">active</span>()) {</span><br><span class="line"> task.<span class="title function_">resume</span>();</span><br><span class="line"> data.<span class="title function_">event</span>(<span class="title class_">Constant</span>.<span class="property">Clarity</span>, <span class="title class_">Constant</span>.<span class="property">Resume</span>);</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">function</span> <span class="title function_">stop</span>(<span class="params"></span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="keyword">if</span> (core.<span class="title function_">active</span>()) {</span><br><span class="line"> <span class="comment">// 以与modules初始化相反的顺序去停止</span></span><br><span class="line"> modules.<span class="title function_">slice</span>().<span class="title function_">reverse</span>().<span class="title function_">forEach</span>(<span class="function"><span class="params">x</span> =></span> <span class="title function_">measure</span>(x.<span class="property">stop</span>)());</span><br><span class="line"> data.<span class="title function_">stop</span>();</span><br><span class="line"> core.<span class="title function_">stop</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从源码很容易看出,这里主要就是针对整个监控流程的一个生命周期操作。主要是对<code>core、data、modules</code>这几个对象进行操作,其实最关键的部分就是初始化,我们来分模块看下。</p><h2 id="core"><a href="#core" class="headerlink" title="core"></a>core</h2><h3 id="core-config"><a href="#core-config" class="headerlink" title="core.config"></a>core.config</h3><p>说白了就是支持自定义配置项,具体配置内容先不深入讲,之后用到的时候再讨论。这里给出<code>Config</code>实例:</p><figure class="highlight typescript"><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">export</span> <span class="keyword">interface</span> <span class="title class_">Config</span> {</span><br><span class="line"> projectId?: <span class="built_in">string</span>;</span><br><span class="line"> delay?: <span class="built_in">number</span>;</span><br><span class="line"> lean?: <span class="built_in">boolean</span>;</span><br><span class="line"> track?: <span class="built_in">boolean</span>;</span><br><span class="line"> content?: <span class="built_in">boolean</span>;</span><br><span class="line"> mask?: <span class="built_in">string</span>[];</span><br><span class="line"> unmask?: <span class="built_in">string</span>[];</span><br><span class="line"> regions?: <span class="title class_">Region</span>[];</span><br><span class="line"> metrics?: <span class="title class_">Metric</span>[];</span><br><span class="line"> dimensions?: <span class="title class_">Dimension</span>[];</span><br><span class="line"> cookies?: <span class="built_in">string</span>[];</span><br><span class="line"> report?: <span class="built_in">string</span>;</span><br><span class="line"> upload?: <span class="built_in">string</span> | <span class="title class_">UploadCallback</span>;</span><br><span class="line"> fallback?: <span class="built_in">string</span>;</span><br><span class="line"> upgrade?: <span class="function">(<span class="params">key: <span class="built_in">string</span></span>) =></span> <span class="built_in">void</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="core-start"><a href="#core-start" class="headerlink" title="core.start"></a>core.start</h3><p>初始化操作:</p><figure class="highlight typescript"><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">export</span> <span class="keyword">function</span> <span class="title function_">start</span>(<span class="params"></span>): <span class="built_in">void</span> {</span><br><span class="line"> status = <span class="literal">true</span>;</span><br><span class="line"> time.<span class="title function_">start</span>(); <span class="comment">// 时间打点开始</span></span><br><span class="line"> task.<span class="title function_">reset</span>(); <span class="comment">// 重置任务队列</span></span><br><span class="line"> event.<span class="title function_">reset</span>(); <span class="comment">// 移除所有事件绑定</span></span><br><span class="line"> report.<span class="title function_">reset</span>(); <span class="comment">// 清除缓存的上报数据</span></span><br><span class="line"> history.<span class="title function_">start</span>(); <span class="comment">// 开始记录url的history state</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里其他的方法都好理解,关键来看看最后的<code>history.start</code>。</p><p>总共做了两件事:</p><p>1、绑定<code>window.popstate</code>事件</p><div id="flowchart-0" class="flow-chart"></div><p>2、代理<code>history.pushState</code>和<code>history.replaceState</code>事件</p><div id="flowchart-1" class="flow-chart"></div>这样就能确保当url地址发生变化时,能及时重启clarity实例,保证跟踪到每个页面的状态。<h2 id="data"><a href="#data" class="headerlink" title="data"></a>data</h2><figure class="highlight typescript"><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="keyword">export</span> <span class="keyword">function</span> <span class="title function_">start</span>(<span class="params"></span>): <span class="built_in">void</span> {</span><br><span class="line"> metric.<span class="title function_">start</span>(); <span class="comment">// 初始化所有与性能相关的信息</span></span><br><span class="line"> modules.<span class="title function_">forEach</span>(<span class="function"><span class="params">x</span> =></span> <span class="title function_">measure</span>(x.<span class="property">start</span>)()); <span class="comment">// 初始化数据,并且测量耗时</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的主要目的就是初始化所有数据,包括了一系列需要记录的信息:如页面浏览器、页面来源、userid、页面长宽、鼠标指针等等。数据非常繁多,同时也支持自定义,总之是尽可能地去收集页面数据,方便之后的日志分析。</p><p>但看到这里同时引入一个问题:这么庞大的数据是怎么保存和上传分析的呢?别急,之后会拿来专门分析。</p><h2 id="modules"><a href="#modules" class="headerlink" title="modules"></a>modules</h2><p>这里加载了一些模块,然后进行初始化。模块包括:</p><p><code>diagnostic, layout, interaction, performance</code></p><p>那么这些模块在初始化时又做了些什么呢,来看看他们的操作。</p><h3 id="diagnostic"><a href="#diagnostic" class="headerlink" title="diagnostic"></a>diagnostic</h3><p>通过代码发现,主要做了两件事:</p><p>1、绑定<code>window.error</code>事件,记录一些错误堆栈和相关信息。</p><p>2、初始化历史缓存,用来之后打log</p><h3 id="layout"><a href="#layout" class="headerlink" title="layout"></a>layout</h3><p>这个模块跟页面元素的变化息息相关,又细分了很多模块。</p><p>首先看源码:</p><figure class="highlight typescript"><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">export</span> <span class="keyword">function</span> <span class="title function_">start</span>(<span class="params"></span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="comment">// 这里的执行顺序非常重要</span></span><br><span class="line"> doc.<span class="title function_">start</span>();</span><br><span class="line"> region.<span class="title function_">start</span>();</span><br><span class="line"> dom.<span class="title function_">start</span>();</span><br><span class="line"> mutation.<span class="title function_">start</span>();</span><br><span class="line"> discover.<span class="title function_">start</span>();</span><br><span class="line"> box.<span class="title function_">start</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过源码分析,可以得到各个模块的大概作用:</p><p><strong>doc</strong>:记录整个页面的最大宽度和高度。</p><p><strong>region</strong>:利用了<code>IntersectionObserver</code>,来观察元素的变化,记录元素的交互状态,方便之后的数据重放与还原。</p><p><strong>dom</strong>:遍历所有元素,记录需要遮罩的元素和监听记录所有元素的属性、状态、性能变化。</p><p><strong>mutation</strong>:利用<code>MutationObserver</code>,监听DOM树和CSS的变化。</p><p><strong>discover</strong>:记录dom和region变化函数的耗时。</p><p><strong>box</strong>:利用<code>ResizeObserver</code>监听元素size的变化。</p><h3 id="interaction"><a href="#interaction" class="headerlink" title="interaction"></a>interaction</h3><p>这个模块主要是做一些跟交互有关的操作,先看代码:</p><figure class="highlight typescript"><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">export</span> <span class="keyword">function</span> <span class="title function_">start</span>(<span class="params"></span>): <span class="built_in">void</span> {</span><br><span class="line"> timeline.<span class="title function_">start</span>();</span><br><span class="line"> click.<span class="title function_">start</span>();</span><br><span class="line"> clipboard.<span class="title function_">start</span>();</span><br><span class="line"> pointer.<span class="title function_">start</span>();</span><br><span class="line"> input.<span class="title function_">start</span>();</span><br><span class="line"> resize.<span class="title function_">start</span>();</span><br><span class="line"> visibility.<span class="title function_">start</span>();</span><br><span class="line"> scroll.<span class="title function_">start</span>();</span><br><span class="line"> selection.<span class="title function_">start</span>();</span><br><span class="line"> submit.<span class="title function_">start</span>();</span><br><span class="line"> unload.<span class="title function_">start</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>分别做了以下事情:</p><p><strong>timeline</strong>:记录跟踪click事件的时间线。</p><p><strong>click</strong>:监听点击事件,记录点击元素相关信息。这里要着重看下记录了哪些信息,来看这段关键代码。</p><figure class="highlight typescript"><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">if</span> (x !== <span class="literal">null</span> && y !== <span class="literal">null</span>) {</span><br><span class="line"> state.<span class="title function_">push</span>({</span><br><span class="line"> <span class="attr">time</span>: <span class="title function_">time</span>(), event, <span class="attr">data</span>: {</span><br><span class="line"> <span class="attr">target</span>: t, <span class="comment">// 当前元素</span></span><br><span class="line"> x, <span class="comment">// pageX</span></span><br><span class="line"> y, <span class="comment">// pageY</span></span><br><span class="line"> eX, <span class="comment">// 点击时相对元素坐标X</span></span><br><span class="line"> eY, <span class="comment">// 点击时相对元素坐标 Y</span></span><br><span class="line"> <span class="attr">button</span>: evt.<span class="property">button</span>, <span class="comment">// 点击按钮元素</span></span><br><span class="line"> <span class="attr">reaction</span>: <span class="title function_">reaction</span>(t), <span class="comment">// 是否是点击无交互元素,比如纯文本,或者非"input", "textarea", "radio", "button", "canvas"元素</span></span><br><span class="line"> <span class="attr">context</span>: <span class="title function_">context</span>(a), <span class="comment">// link标签a元素的target类型,比如:blank、parent、top</span></span><br><span class="line"> <span class="attr">text</span>: <span class="title function_">text</span>(t), <span class="comment">// 点击文本,截取前25个非空字符</span></span><br><span class="line"> <span class="attr">link</span>: a ? a.<span class="property">href</span> : <span class="literal">null</span>, <span class="comment">// 跳转链接</span></span><br><span class="line"> <span class="attr">hash</span>: <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="title function_">schedule</span>(encode.<span class="title function_">bind</span>(<span class="variable language_">this</span>, event));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样一来,就能相对完整地记录点击的元素信息,方便之后还原。</p><p><strong>clipboard</strong>:监听<code>cut、copy、paste</code>事件,并记录相应的event对象。</p><p><strong>pointer</strong>:监听所有跟鼠标指针交互相关的事件:<code>mousedown、mouseup、mousemove、mousewheel、dblclick、touchstart、touchend、touchmove、touchcancel</code>,并记录指针位置。</p><p><strong>input</strong>:监听<code>input</code>事件,包括<code>value、attr、placeholder</code>等方面的隐私处理,主要记录value。</p><p><strong>resize</strong>:监听<code>window.resize</code>事件,记录window视窗变化。</p><p><strong>visibility</strong>:监听<code>visibilitychange</code>事件,记录<code>document.visibilityState</code>。</p><p><strong>scroll</strong>:监听元素的<code>scroll</code>事件,记录当前滚动元素和滚动位置。</p><p><strong>selection</strong>:监听元素<code>selectstart、selectionchange</code>事件,记录选区起始和结束锚点和元素。</p><p><strong>submit</strong>:监听元素<code>submit</code>事件,记录当前元素。</p><p><strong>unload</strong>:监听<code>window.pagehide</code>事件,记录事件,停止clarity实例。</p><h3 id="performance"><a href="#performance" class="headerlink" title="performance"></a>performance</h3><p>这里的模块很容易理解,就是记录页面的各种性能,主要包括以下两部分:</p><p><strong>navigation</strong>:利用<code>PerformanceNavigationTiming</code>记录页面首屏性能指标,包括:DNS解析时间、请求时间、DOM解析时间、重定向时间等等。</p><p><strong>observer</strong>:利用<code>PerformanceObserver</code>观测页面性能指标,包括:浏览器、资源、长任务、首次输入延迟、累积布局偏移、最大内容绘制。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>到此,我们分析了clarity的代码结构,和初始化时各个模块的分工。</p><p>下一篇,我将着重分析关键的数据存储和上报方式,并且回顾整个系统架构,整体分析clarity的设计理念。<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/flowchart/1.6.5/flowchart.min.js"></script><textarea id="flowchart-0-code" style="display: none">st=>start: 绑定window.popstate事件cond=>condition: url是否发生变化?sub1=>subroutine: 停止当前clarity实例sub2=>subroutine: 250ms后重新开启clarity实例e=>end: 结束st->condcond(yes)->sub1->sub2->econd(no)->e</textarea><textarea id="flowchart-0-options" style="display: none">{"scale":1,"line-width":2,"line-length":50,"text-margin":10,"font-size":12}</textarea><script> var code = document.getElementById("flowchart-0-code").value; var options = JSON.parse(decodeURIComponent(document.getElementById("flowchart-0-options").value)); var diagram = flowchart.parse(code); diagram.drawSVG("flowchart-0", options);</script><textarea id="flowchart-1-code" style="display: none">st=>start: 代理pushState、replaceState事件op1=>operation: 正常执行pushState、replaceState事件cond1=>condition: 调用堆栈是否小于20?cond2=>condition: url是否发生变化?sub1=>subroutine: 停止当前clarity实例sub2=>subroutine: 250ms后重新开启clarity实例e=>end: 结束st->cond1cond1(yes)->op1->cond2cond1(no)->econd2(yes)->sub1->sub2->econd2(no)->e</textarea><textarea id="flowchart-1-options" style="display: none">{"scale":1,"line-width":2,"line-length":50,"text-margin":10,"font-size":12}</textarea><script> var code = document.getElementById("flowchart-1-code").value; var options = JSON.parse(decodeURIComponent(document.getElementById("flowchart-1-options").value)); var diagram = flowchart.parse(code); diagram.drawSVG("flowchart-1", options);</script></p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/">源码分析</category>
<category domain="https://flyrk.github.io/tags/%E5%89%8D%E7%AB%AF%E7%9B%91%E6%8E%A7/">前端监控</category>
<category domain="https://flyrk.github.io/tags/%E5%BD%95%E5%88%B6%E5%9B%9E%E6%94%BE/">录制回放</category>
<comments>https://flyrk.github.io/2022/02/24/clarity-source-analysis-1/#disqus_thread</comments>
</item>
<item>
<title>git命令手册(持续更新)</title>
<link>https://flyrk.github.io/2020/01/25/git-commend-list/</link>
<guid>https://flyrk.github.io/2020/01/25/git-commend-list/</guid>
<pubDate>Sat, 25 Jan 2020 08:47:41 GMT</pubDate>
<description><p>本篇不定时更新git实践技巧,方便查阅git命令。</p></description>
<content:encoded><![CDATA[<p>本篇不定时更新git实践技巧,方便查阅git命令。</p><span id="more"></span><h1 id="分支相关操作"><a href="#分支相关操作" class="headerlink" title="分支相关操作"></a>分支相关操作</h1><h2 id="git-checkout"><a href="#git-checkout" class="headerlink" title="git checkout"></a>git checkout</h2><p>切换分支,例如:<code>git checkout -b ${branch_name}</code></p><h2 id="git-show"><a href="#git-show" class="headerlink" title="git show"></a>git show</h2><p>显示各种类型的对象。可以是blobs,树,标签和提交。</p><h2 id="git-remote"><a href="#git-remote" class="headerlink" title="git remote"></a>git remote</h2><p>对本地远程增删改查</p><p>git remote -v 获取远程分支信息</p><h2 id="git-fetch"><a href="#git-fetch" class="headerlink" title="git fetch"></a>git fetch</h2><p>从远端拉取代码,默认拉取<code>origin</code>。</p><h2 id="git-merge"><a href="#git-merge" class="headerlink" title="git merge"></a>git merge</h2><p>合并分支操作,例如:<code>git merge master</code>,把<code>master</code>分支的内容合并到当前分支。</p><h2 id="git-pull"><a href="#git-pull" class="headerlink" title="git pull"></a>git pull</h2><p>拉取代码,用得最多的命令。pull = fetch + merge</p><h2 id="git-push"><a href="#git-push" class="headerlink" title="git push"></a>git push</h2><p>推送代码到远程分支。</p><h1 id="修改相关操作"><a href="#修改相关操作" class="headerlink" title="修改相关操作"></a>修改相关操作</h1><h2 id="git-add"><a href="#git-add" class="headerlink" title="git add"></a>git add</h2><p>添加变更,最常用的命令:<code>git add .</code>,添加当前所有变更。</p><h2 id="git-status"><a href="#git-status" class="headerlink" title="git status"></a>git status</h2><p>查看当前git状态。</p><h2 id="git-diff"><a href="#git-diff" class="headerlink" title="git diff"></a>git diff</h2><p>对比两个分支的差异。</p><h2 id="git-stash"><a href="#git-stash" class="headerlink" title="git stash"></a>git stash</h2><p>缓存当前变更,一般在要切换到别的分支,而当前分支的变更又不想提交时用。<code>git stash</code>,然后恢复的时候:<code>git stash apply</code>。</p><h1 id="提交相关操作"><a href="#提交相关操作" class="headerlink" title="提交相关操作"></a>提交相关操作</h1><h2 id="git-commit"><a href="#git-commit" class="headerlink" title="git commit"></a>git commit</h2><p>提交变更,最常用的命令:<code>git commit -m "commit message"</code>。</p><h2 id="git-revert"><a href="#git-revert" class="headerlink" title="git revert"></a>git revert</h2><p>撤销某次版本,<code>git revert -n 版本号</code></p><h2 id="git-log"><a href="#git-log" class="headerlink" title="git log"></a>git log</h2><p>查看git提交信息。</p><h1 id="奇淫技巧"><a href="#奇淫技巧" class="headerlink" title="奇淫技巧"></a>奇淫技巧</h1><ul><li>git blame 查看文件每一行修改记录</li><li>git reflog 查看操作记录</li><li>git rebase -i HEAD-n 修改已提交的前n个commit操作</li><li>更新子模块url:<code>git submodule sync --recursive</code></li></ul>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E5%B7%A5%E5%85%B7/">工具</category>
<category domain="https://flyrk.github.io/tags/git/">git</category>
<comments>https://flyrk.github.io/2020/01/25/git-commend-list/#disqus_thread</comments>
</item>
<item>
<title>我是如何当上程序员的(二)</title>
<link>https://flyrk.github.io/2019/08/25/how-can-i-be-a-programmer-2/</link>
<guid>https://flyrk.github.io/2019/08/25/how-can-i-be-a-programmer-2/</guid>
<pubDate>Sun, 25 Aug 2019 14:32:40 GMT</pubDate>
<description><p>接上文,决定了要当一个前端开发工程师,那么应该怎么做呢?</p></description>
<content:encoded><![CDATA[<p>接上文,决定了要当一个前端开发工程师,那么应该怎么做呢?</p><span id="more"></span><h1 id="找工作"><a href="#找工作" class="headerlink" title="找工作"></a>找工作</h1><p>人生最怕没有目标,一旦有了目标,生活就有了动力。</p><p>在确定了自己要走前端开发这条路后,我反而心态放松了许多,因为自己有了明确的目标,虽然还是很迷茫。因为彼时的我才刚入门前端开发半年时间,期间完全靠自己在网上找资料自学,身边也没有其他小伙伴一起,可以说那个时候的我真的是一个人在战斗。</p><p>接下来的日子,我开始边学习边找暑假实习,每天基本上只要没课就泡在图书馆了,那个时候的我对前端知识饥渴到只要看到书名和Web、JavaScript沾边的,就一定要去借来翻一翻,觉得不合适的就放回去继续下一本。每天从图书馆回来看到室友在打游戏开黑,自己心里也会有抑制不住想加入他们的冲动。但是理性告诉我不行,我和他们的目标不一样,要找到心仪的工作,我必须努力先找到实习,加强自己的经验。</p><p>然而,事与愿违,那段时间成了我最痛苦的一段时间。</p><p>因为之前完全没有任何项目经验,都是自己小打小闹,一个人摸索。去实习面试的时候别人一问到项目经验就懵逼了,再加上自己内向的性格,有时候语言表达不够清楚,刚开始面试的几家公司是屡战屡败。</p><p>接二连三的面试失败让我萌生了想要放弃的念头,怀疑自己到底能不能找到实习,找不到怎么办?到时候校招没有实习经验别人更加不会要。但是,还好我及时调整了心态,当时也是去知乎、v2ex等论坛翻看各种前人经验和鸡汤来鼓励自己。我当时就是有一种不信邪的劲,为什么别人能找到实习我不可以,我又不比别人差。</p><p>调整了心态后,我开始把面试当成打怪升级的过程,每一次面试后都进行总结,把问到的知识点都记下来,回去一个个消化。基础不好就补基础,没有项目经验就去看别人的项目怎么写。由于时间有限,短时间我不可能去生造一个项目出来。于是我采取了两步走策略:主要是看各种前端开发面经和面试题,一个一个地看,强化自己的知识点;同时,根据网上推荐的前端书籍去图书馆一本一本地借阅,慢慢补基础。</p><p>就这样,我的实习面试慢慢变得越来越好,不再是一面挂、二面挂。腾讯的暑期实习面试更是走到了最后,HR面都面完了。正当我觉得腾讯的面试稳了(因为HR面基本上不会挂人),老天爷给我开了一个玩笑。</p><p>因为觉得腾讯稳了,所以后来的面试我也就比较随意,也没有再去投更多的公司,现在看来是个严重的决策失误。一直等到五月底,腾讯实习面试结果都没出来,那个时候我才知道,我凉了。在那个时间点,好的公司暑假实习基本上早就招完了,有的甚至已经开始实习了。我只能匆忙投了几家小公司,甚至去学校的实习双选会碰碰运气,然而由于心态的变化,最后都渺无音讯。</p><p>一般人这个时候肯定慌的不行,我也不例外。我无数次地问自己,自己的选择到底做对了吗?我是不是该和大多数人一样去考研,以我的成绩,要去考肯定能考上的。但是,我读研到底是为了什么?我问自己。首先,我不喜欢读本专业,如果要读研的话我肯定要转计算机专业,但是现在学专业课已经来不及了,很有可能考不上。再者,即使读了计算机专业,但是我想当前端开发的话,读研对我的提升真的比工作大吗?我得出的结论是,工作对我的技能提升更快。我并不是说读研不好,没有用,而是在我看来,如果读研不能更有效地帮助我提升自己,只是为了一纸文凭的话,还不如尽早工作。我不想让读研成为自己能力不行、逃避工作的借口。</p><p>但是,这个时间找实习已经来不及了,我该怎么办?我仔细总结了到目前为止自己的优劣势,发现自己最大的缺陷就是缺乏实际项目经验,找实习的目的也是为了增加经验,那么,我为啥不自己写项目呢?虽然可能体验不到公司那种多人协同开发,但是总比没有经验好。于是我开始在GitHub上找项目,去YouTube上找教程,自己从零开始模仿着写。慢慢地,我开始沉下心来。</p><p>到了暑假,我每天给自己安排学习计划,上午看JavaScript书,中午看会CSS、HTML,下午写项目,晚上去牛客网刷会题,对知识进行总结。就这样有条不紊地进行着,我反而没那么着急了。</p><p>后来的事就开始顺理成章,我的项目经验慢慢丰富起来,简历上能说的东西也多了。到了正式校招,我陆续投了几家公司,没想到这次运气眷顾了我,我拿到了网易杭研院的提前批offer,于是我在网易开始了正式的程序员生涯。后来机缘巧合下又加入了微信。</p><h1 id="回顾"><a href="#回顾" class="headerlink" title="回顾"></a>回顾</h1><p>回顾我是如何一步一步当上程序员,和那些拿ACM奖拿到手软、大厂实习经历丰富的大牛们来说,可能显得有点平平无奇。但我想表达的是,想当程序员并且进一家还算不错的公司其实没那么难,纵观我的经历,没有竞赛拿奖经历,没有工作室经验,没有大厂实习光环,最后一样拿到了大厂offer。</p><p>其实关键在于,你有多想当程序员,你有多热爱你的事业,你有多大的决心。如果只是单纯的认为程序员工资高,觉得程序员很容易当,那我劝你还是迟早放弃这个念头。</p><p>金融、销售等等行业工资一样高,做得好不会比程序员差。相比较之下,程序员反而显得更加枯燥,它需要一个人长时间坐在电脑面前思考、敲键盘,对人的精神和身体都是极大的考验,所以没有一定的毅力和热爱,很容易就会放弃。</p><p>但我不是说当程序员不好,而是在当程序员之前得想清楚,自己的目标是什么,自己到底有多大的决心。不管是为了梦想也好、钱也好,都必须有个明确的目标,并且持之以恒地坚持下去。</p><p>程序员是个需要终身学习的职业,或者说没有哪个行业不需要学习。所以,当你一旦确定要当程序员,最好的方法就是,确定目标、制定计划、行动起来!</p><p>最后,附上自己当时校招找工作时看的部分书单:</p><ul><li>《JavaScript高级程序设计》——必读</li><li>《你不知道的JS》——强烈推荐</li><li>《CSS禅意花园》——学习CSS很有用</li><li>《JavaScript设计模式》——对代码质量提升很大</li><li>《高性能网站建设指南》——了解网站优化</li><li>《响应式Web设计全流程解析》——响应式布局、网页设计</li></ul>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/">经验分享</category>
<category domain="https://flyrk.github.io/tags/%E8%81%8C%E4%B8%9A%E8%A7%84%E5%88%92/">职业规划</category>
<comments>https://flyrk.github.io/2019/08/25/how-can-i-be-a-programmer-2/#disqus_thread</comments>
</item>
<item>
<title>我是如何当上程序员的(一)</title>
<link>https://flyrk.github.io/2019/08/18/how-can-i-be-a-programmer-1/</link>
<guid>https://flyrk.github.io/2019/08/18/how-can-i-be-a-programmer-1/</guid>
<pubDate>Sun, 18 Aug 2019 13:28:46 GMT</pubDate>
<description><p>今天来聊聊我是如何当上程序员的,有时候也在想,我一个学电子的,大学每天都是和硬件、电路板打交道,是怎么走上程序员这条路的呢?</p></description>
<content:encoded><![CDATA[<p>今天来聊聊我是如何当上程序员的,有时候也在想,我一个学电子的,大学每天都是和硬件、电路板打交道,是怎么走上程序员这条路的呢?</p><span id="more"></span><h1 id="初识编程"><a href="#初识编程" class="headerlink" title="初识编程"></a>初识编程</h1><p>说起来,我和编程也算是有过一段缘分。小学六年级的时候,刚转到新的学校,人生地不熟的,正好学校新成立一个信息学竞赛培训班,看我入学考试数学成绩不错,就把我拉进去了。</p><p>那是我第一次接触计算机编程,之前倒是玩过不少网络游戏,但是对编程和代码完全一窍不通。刚进去机房,老师叫我们打开Visual Basic软件,映入眼帘的是蓝色的背景,回车符不停的闪烁,一连串的英文界面看得我眼花缭乱。从那时起,就打开了计算机编程新世界的大门。</p><p>那段时间,六十几个人的班,老师每节课都会教一些基础的编程知识,现在依稀还记得什么条件语句、循环语句,do while、viod main等等。每节课都有新内容,每节课也都有小伙伴学不下去而离开。我当时纯粹是因为好奇,而且自己逻辑思维能力还比较强,再加上每节课下课还会有几分钟时间和小伙伴打几局CS1.6,所以就一直坚持下来了。</p><p>后来,上了初中,编程水平慢慢熟练了,还参加过几次比赛,但其实都是基础的语法考察,涉及到了一点算法,当时用的是Pascal语言,写法约束超级多,个人感觉比C++还难写。那段信息学竞赛的日子现在回想起来还挺难忘的。每个晚自习早早写完作业后就去机房写代码,周末寒暑假也要去机房,经常趁老师没来打几局红警、CS,想想还挺欢乐的。</p><p>上了初三,因为学业更加忙的原因,再加上自己的兴趣也慢慢减弱(主要是因为没有完整的寒暑假),就退出了信息学竞赛培训班,我与编程的故事也就暂时告一段落。</p><h1 id="再学编程"><a href="#再学编程" class="headerlink" title="再学编程"></a>再学编程</h1><p>转眼到了高三毕业,填志愿的时候在想自己将来应该干点什么好呢?想到自己业余爱好是体育,但这个时候去搞体育肯定不现实了,又想到自己以前不是学过编程吗?刚好那个时候计算机专业算是比较热门的,于是就想填个计算机专业不错的学校。但是阴差阳错,当时以为电子信息工程也是学计算机的(信息两个字蒙蔽了我。。。),于是抱了个电子科技大学,第一志愿填的电子信息工程,第二志愿填的计算机技术,结果,入了电子的坑。</p><p>进了大学,头两个月的课程学习让我感觉到,自己不是学电子的料。所有的课下来,只有C语言这门课我学的最好。当时没有按照老师给的谭浩强那本C语言教材来学,而是自己买了本“C Primer Plus”,自己开始自学编程,最后期末考试考了个100分,从那时起,心里就开始觉得,欸,好像编程还蛮不错的?</p><p>接下来的日子,我开始给自己定下了目标,虽然不是计算机专业,但是我可以自学编程,我开始搜罗所有和计算机有关的知识,想着未来要做什么。大一大二那段时间,我报名了学校的ACM集训队,打过几场校内比赛;还在网上边看教程边自学python,想着能写点小游戏、爬虫。后来又接触了计算机安全,梦想着做一名黑客,再后来又去图书馆借了一大堆游戏编程,自己还用C++写了几个小游戏。</p><p>那是我第一次完全靠自己的兴趣在做事,没有人逼我,完全是自学,那样纯粹学习的日子真的挺美好,让人怀念。</p><h1 id="接触前端"><a href="#接触前端" class="headerlink" title="接触前端"></a>接触前端</h1><p>后来,我接触到了前端开发这个职业,也就是我现在在做的工作。</p><p>那是在大二下学期的时候,当时刚用C++写完几个小游戏,就觉得用C++写起图形界面实在是太费劲了,更多的时候都是在面对控制台命令行,有没有什么语言能让我快速写出好看的界面,并且能实现炫酷的游戏效果就好了。这时,我突然看到了前端开发这个词。立即在知乎搜了半天有关话题,发现是写网页的,而且很容易就能看到自己想要的效果,只需要HTML、CSS,甚至都不需要什么JS逻辑,浏览器刷新下就能看到炫酷的静态网页,这不就是我想要的吗?!</p><p>于是,我开始入坑前端,正应了那句话:一入前端深似海。真正接触前端,才发现没自己想的那么简单,前端三剑客不用说了,那时正好刚开始兴起框架,React、Vue、Angular开始慢慢流行,还有Bootstrap,jQuery。学完了静态网页,发现还有CSS3动画,Canvas。然后又发现没那么简单,还有叫单页应用的玩意,用Ajax通信,可以不用刷新网页就获取新的数据,还有前端路由跳转,等等。</p><p>才发现,前端要学的东西实在是太多了,每个星期都在更新新的框架、工具库,那真是前端百花齐放的时代,但是对于一个新手来说,真的会显得手足无措。还好,我这个人有个优点,乐于接受一切新事物,虽然也会感到迷茫,但是我喜欢挑战,我到各大网站搜各种教程资料,自己不知道学什么就去学习别人的经验。我也不是什么新知识都学,因为知道自己肯定学不过来,而是先从基础开始,大三整个一年,我基本就是宿舍、教学楼、图书馆三点一线,去图书馆翻看一切有关前端开发的书籍,当然虽然借了很多本,但其实真正看完的也不是很多,因为我都是有选择地看,翻了一回发现不感兴趣或者自己暂时还没有达到那个水准看不懂,就放下看别的。所以,整个大三一年可以说我主要的经历都是放在学习前端上了。</p><p>大三那个寒假,我也坚定了自己的看法,放弃考研,去找前端开发工作。</p><p>当时父母家人都认为我疯了,居然不去考研,但我知道,我的目标是什么。如果去考研,我也肯定是想考计算机专业,但是前端开发这个职业很特殊,大学里没有老师专门教这个,所以我决定干脆开始找工作,去实际中锻炼自己。</p><p>后来的事实证明,有自己的目标很重要,至少现在我没有后悔当时的决定。</p><p>未完待续…</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/">经验分享</category>
<category domain="https://flyrk.github.io/tags/%E8%81%8C%E4%B8%9A%E8%A7%84%E5%88%92/">职业规划</category>
<comments>https://flyrk.github.io/2019/08/18/how-can-i-be-a-programmer-1/#disqus_thread</comments>
</item>
<item>
<title>写作能带给我什么</title>
<link>https://flyrk.github.io/2019/08/11/half-year-summary/</link>
<guid>https://flyrk.github.io/2019/08/11/half-year-summary/</guid>
<pubDate>Sun, 11 Aug 2019 13:01:33 GMT</pubDate>
<description><p>差不多有半年没更过博客了,自己差点都快把它忘了,最近偶尔翻到自己以前写的文章,突然又有了写的冲动。虽然文笔依然很烂,但还是想把写博客捡起来,有空写点什么。</p>
<p>这半年来,其实生活上经历的挺多的,生活的城市从杭州又回到了广州,工作、生活都发生了巨大的变化,自己也忙于</description>
<content:encoded><![CDATA[<p>差不多有半年没更过博客了,自己差点都快把它忘了,最近偶尔翻到自己以前写的文章,突然又有了写的冲动。虽然文笔依然很烂,但还是想把写博客捡起来,有空写点什么。</p><p>这半年来,其实生活上经历的挺多的,生活的城市从杭州又回到了广州,工作、生活都发生了巨大的变化,自己也忙于处理各种事情,但这不应该成为我没有更博的借口。最近这个月,感觉生活总算是稍微稳定下来了,可以安心去做点事,于是就想把写作这块捡起来。</p><p>说实话,身为理科生,自己的写作水平的确不怎么样,女票还为此嘲笑过我好几次。但是,我还是想写点什么,微博也好知乎也好,还是自己的博客,目的不是为了吸引关注博眼球,(目前自己也没那个本事),而是觉得,写作这件事,其实是很值得去做的。每次写作时,不管是写什么内容,都是一个自我思考的过程,在写作中不断反思、不断总结,对自己的思维是一个很好的锻炼,语言组织表达能力也是一个大大的提升,要是能够引起大家的讨论和交流,那是再好不过了。</p><p>以前大家总觉得写作是件很麻烦的事,只有作家、专业写手才能干得来,但是现在,有了微博、有了知乎、有了微信公众号,让自媒体兴起了,让大家有了发声的渠道。我们每一个人都可以表达我们的观点,并且借助平台让它传播开来,大家一起讨论,碰撞知识的火花。</p><p>所以,我要开始写起来,不管文笔多烂,写得再不好,至少,我能表达出自己的想法,这就够了。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E9%9A%8F%E7%AC%94/">随笔</category>
<category domain="https://flyrk.github.io/tags/%E6%80%9D%E8%80%83%E6%84%9F%E6%82%9F/">思考感悟</category>
<comments>https://flyrk.github.io/2019/08/11/half-year-summary/#disqus_thread</comments>
</item>
<item>
<title>有意思的leetcode算法题——巧用数学知识</title>
<link>https://flyrk.github.io/2019/02/27/leetcode-sum-of-even-numbers-after-queries-solution/</link>
<guid>https://flyrk.github.io/2019/02/27/leetcode-sum-of-even-numbers-after-queries-solution/</guid>
<pubDate>Wed, 27 Feb 2019 04:42:43 GMT</pubDate>
<description><p>最近在leetcode上刷算法题,发现了一道比较有意思的题目,虽然不难,但要想尽可能的降低时间复杂度达到最优解,还是要有点技巧的,我们来看看。</p></description>
<content:encoded><![CDATA[<p>最近在leetcode上刷算法题,发现了一道比较有意思的题目,虽然不难,但要想尽可能的降低时间复杂度达到最优解,还是要有点技巧的,我们来看看。</p><span id="more"></span><h1 id="题干"><a href="#题干" class="headerlink" title="题干"></a>题干</h1><p><a href="https://leetcode.com/problems/sum-of-even-numbers-after-queries">sum-of-even-numbers-after-queries</a>。这道题的意思是说,给定一个包含一系列数字的数组A,和一个queries数组,对于<code>queries[i]</code>,设定<code>val=queries[i][0]</code>,<code>index=queries[i][1]</code>,使得<code>A[index]+=val</code>,求<code>ans</code>数组,使得<code>ans[i]</code>为经过<code>queries[i]</code>之后的A数组所有偶数数字之和。</p><h1 id="初步模拟"><a href="#初步模拟" class="headerlink" title="初步模拟"></a>初步模拟</h1><p>初看完题目,我马上想到这不就是个简单的模拟题吗?照着题干的意思写不就行了,再一看数据范围不超过10000,嗯,O(n^2)先试试看,应该不会超时。于是很快的就把代码写出来了:</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><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">number[]</span>} <span class="variable">A</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> {<span class="type">number[][]</span>} <span class="variable">queries</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> {<span class="type">number[]</span>}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> sumEvenAfterQueries = <span class="keyword">function</span>(<span class="params">A, queries</span>) {</span><br><span class="line"> <span class="keyword">var</span> ans = [], val = <span class="literal">null</span>, index = <span class="number">0</span>, evenSum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = queries.<span class="property">length</span>; i < l; i++) {</span><br><span class="line"> val = queries[i][<span class="number">0</span>];</span><br><span class="line"> index = queries[i][<span class="number">1</span>];</span><br><span class="line"> A[index] += val;</span><br><span class="line"> evenSum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> j = <span class="number">0</span>, l2 = A.<span class="property">length</span>; j < l2; j++) {</span><br><span class="line"> <span class="keyword">if</span> (A[j] % <span class="number">2</span> === <span class="number">0</span>) {</span><br><span class="line"> evenSum += A[j]; </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ans.<span class="title function_">push</span>(evenSum);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ans;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>写完提交,成功!emmmm,很简单嘛,下一题…..</p><p>等等,这时间好像有点多啊,8216ms,差一点就超时了。不行,肯定有什么更简便的方法。</p><h1 id="数学妙用"><a href="#数学妙用" class="headerlink" title="数学妙用"></a>数学妙用</h1><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><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">var</span> sumEvenAfterQueries = <span class="keyword">function</span>(<span class="params">A, queries</span>) {</span><br><span class="line"> <span class="keyword">var</span> ans = [], val = <span class="literal">null</span>, index = <span class="number">0</span>, tmpSum = <span class="number">0</span>, evenSum = <span class="number">0</span>;</span><br><span class="line"> evenSum = A.<span class="title function_">reduce</span>(<span class="function">(<span class="params">p, n</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> n % <span class="number">2</span> === <span class="number">0</span> ? p + n : p;</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line"> queries.<span class="title function_">forEach</span>(<span class="function">(<span class="params">query</span>) =></span> {</span><br><span class="line"> val = query[<span class="number">0</span>];</span><br><span class="line"> index = query[<span class="number">1</span>];</span><br><span class="line"> tmpSum = A[index] + val;</span><br><span class="line"> tmpSum % <span class="number">2</span> === <span class="number">0</span> ?</span><br><span class="line"> (A[index] % <span class="number">2</span> === <span class="number">0</span> ? evenSum += val : evenSum += tmpSum)</span><br><span class="line"> : (A[index] % <span class="number">2</span> === <span class="number">0</span> ? evenSum -= A[index] : <span class="literal">null</span>)</span><br><span class="line"> A[index] = tmpSum;</span><br><span class="line"> ans.<span class="title function_">push</span>(evenSum);</span><br><span class="line"> })</span><br><span class="line"> <span class="keyword">return</span> ans;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>写完一提交居然只用了20ms左右!!!复杂度也只有O(n),怎么做到的呢?</p><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></pre></td><td class="code"><pre><span class="line">evenSum = A.<span class="title function_">reduce</span>(<span class="function">(<span class="params">p, n</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> n % <span class="number">2</span> === <span class="number">0</span> ? p + n : p;</span><br><span class="line">}, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><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">tmpSum % <span class="number">2</span> === <span class="number">0</span> ?</span><br><span class="line"> (A[index] % <span class="number">2</span> === <span class="number">0</span> ? evenSum += val : evenSum += tmpSum)</span><br><span class="line"> : (A[index] % <span class="number">2</span> === <span class="number">0</span> ? evenSum -= A[index] : <span class="literal">null</span>)</span><br><span class="line"> A[index] = tmpSum;</span><br><span class="line"> ans.<span class="title function_">push</span>(evenSum);</span><br></pre></td></tr></table></figure><p>接着这段最关键的代码,我们对每次加完后的数字<code>tmpSum</code>进行判断,如果是偶数的话,分两种情况:<code>A[index]</code>是偶数,则代表之前的evenSum已经把<code>A[index]</code>加进去了,所以我们只用加上新的<code>val</code>,反之我们把<code>A[index]</code>和<code>val</code>都给加上;如果是奇数,我们则需要判断之前的<code>A[index]</code>是不是偶数,是的话需要把它给减去。</p><p>经过这样的处理最后得到的就是每一轮所有偶数的和,最后完美的解决了问题,时间复杂度也只有O(n)。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>虽然这道题很简单,但我还是想记录下来,因为它代表了一种思维方法,以后做算法题或者写业务代码时都应该多问问自己,还有没有最优解?还能不能继续优化?我们很多时候都是做完结果对了就万事大吉,等到问题出现的时候才去想怎么去解决。而大多时候的问题都是有更好的解决办法的,不怕你做不到,就怕你想不到。平时做事时多拓宽自己的思路,我们在真正遇到问题的时候才不会害怕。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E7%AE%97%E6%B3%95/">算法</category>
<category domain="https://flyrk.github.io/tags/leetcode/">leetcode</category>
<comments>https://flyrk.github.io/2019/02/27/leetcode-sum-of-even-numbers-after-queries-solution/#disqus_thread</comments>
</item>
<item>
<title>快速实现标签条切换效果</title>
<link>https://flyrk.github.io/2019/02/15/fast-achieve-tabpane-effect/</link>
<guid>https://flyrk.github.io/2019/02/15/fast-achieve-tabpane-effect/</guid>
<pubDate>Fri, 15 Feb 2019 02:57:57 GMT</pubDate>
<description><p>现在的单页应用中,我们经常需要通过切换不同的标签条显示相应的内容,一般想到的方法就是通过DOM操作给标签条添加点击事件然后加载对应内容,那么,有没有方法可以基本靠HTML和CSS就能快速实现标签条切换效果呢?我们来看一看。</p></description>
<content:encoded><![CDATA[<p>现在的单页应用中,我们经常需要通过切换不同的标签条显示相应的内容,一般想到的方法就是通过DOM操作给标签条添加点击事件然后加载对应内容,那么,有没有方法可以基本靠HTML和CSS就能快速实现标签条切换效果呢?我们来看一看。</p><span id="more"></span><h1 id="HTML结构"><a href="#HTML结构" class="headerlink" title="HTML结构"></a>HTML结构</h1><p>这里我先简单的把HTML结构展示出来,然后慢慢解释:</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'pane-container'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'pane-item'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'content'</span> <span class="attr">id</span>=<span class="string">'tab1'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>Tab1 Content<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span> <span class="attr">class</span>=<span class="string">'pane-btn'</span> <span class="attr">href</span>=<span class="string">'#tab1'</span>></span>Tab1<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'pane-item'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'content'</span> <span class="attr">id</span>=<span class="string">'tab2'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>Tab2 Content<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span> <span class="attr">class</span>=<span class="string">'pane-btn'</span> <span class="attr">href</span>=<span class="string">'#tab2'</span>></span>Tab2<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'pane-item'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'content'</span> <span class="attr">id</span>=<span class="string">'tab3'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>Tab3 Content<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span> <span class="attr">class</span>=<span class="string">'pane-btn'</span> <span class="attr">href</span>=<span class="string">'#tab3'</span>></span>Tab3<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>我们可以看到,每一个标签条都是一个<code>a</code>标签,对应的内容为<code>.content</code>块。这里我为什么要用<code>a</code>标签来表示标签条呢?</p><p>原因就是,<code>a</code>标签的<code>href</code>属性可以设置<code>href='#myid'</code>,这样点击就能跳转到当前页面id为myid的元素。有人会说,但这里我们不需要跳转啊,别急,这样设置的目的是为了方便之后的CSS设置。</p><h1 id="关键的CSS操作"><a href="#关键的CSS操作" class="headerlink" title="关键的CSS操作"></a>关键的CSS操作</h1><h2 id="target伪类"><a href="#target伪类" class="headerlink" title=":target伪类"></a>:target伪类</h2><p>这里我们用到了一个关键的CSS选择器:<code>:target</code>伪类。<code>E:target</code>伪类选择的是匹配到URL的E元素,就是说如果我们设置了E元素的id为<code>eId</code>,并且当前URL后面有<code>#eId</code>,则E元素就被CSS选择器选中了,我们就可以设置E元素的CSS属性!来看代码:</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.content</span> {</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">28px</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#EDEDED</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">2px</span>;</span><br><span class="line"> <span class="attribute">text-align</span>: left;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.content</span><span class="selector-pseudo">:target</span> {</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们要选中的元素是class为<code>content</code>的元素,也就是要展示的内容。先对<code>content</code>设置<code>display:none;</code>,然后通过<code>.content:target</code>选择到当前选中的<code>content</code>元素,再让其可见,就达到了内容切换的目的!怎么样,是不是很简单?</p><h2 id="修改tab选中样式"><a href="#修改tab选中样式" class="headerlink" title="修改tab选中样式"></a>修改tab选中样式</h2><p>当然为了更加直观一点,我们还需要对选中的标签进行一点修饰,代表当前选中的是哪个标签,其主要也是运用了<code>:target</code>和兄弟选择器:<code>E~F</code>。这里要注意的是,<code>~</code>匹配的是E后面所有兄弟元素F,也就是说F的位置必须在E后面,我们来看代码:</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.pane-btn</span><span class="selector-pseudo">:hover</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#F1F1F1</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.content</span><span class="selector-pseudo">:target</span> ~ <span class="selector-class">.pane-btn</span> {</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">1px</span> solid <span class="number">#6EAED8</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样就能比较明显的显示当前选中的标签。</p><h2 id="完整CSS代码"><a href="#完整CSS代码" class="headerlink" title="完整CSS代码"></a>完整CSS代码</h2><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><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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.pane-container</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.pane-btn</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#fff</span>;</span><br><span class="line"> <span class="attribute">border-style</span>: none;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">5px</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">24px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">2px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.pane-btn</span><span class="selector-pseudo">:hover</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#F1F1F1</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-tag">a</span> {</span><br><span class="line"> <span class="attribute">text-decoration</span>: none;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#000</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.content</span><span class="selector-pseudo">:target</span> {</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.content</span><span class="selector-pseudo">:target</span> ~ <span class="selector-class">.pane-btn</span> {</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">1px</span> solid <span class="number">#6EAED8</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.content</span> {</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">28px</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#EDEDED</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">2px</span>;</span><br><span class="line"> <span class="attribute">text-align</span>: left;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>你以为这样就OK了?还差最后一步。经过上面的HTML和CSS设置,我们虽然可以在标签之间进行切换并且显示对应内容,但是在页面初次加载进来后是没有显示任何内容的,因为此时没有任何标签被点击!所以我们还得用JS小小的设置一下:</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">var</span> tabPaneOne = <span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(<span class="string">'pane-btn'</span>)[<span class="number">0</span>]; <span class="comment">// 这里默认显示第一个标签</span></span><br><span class="line">tabPaneOne.<span class="title function_">click</span>();</span><br></pre></td></tr></table></figure><p>这样就能完美的显示啦!完整效果请看<a href="https://codepen.io/flyrk/pen/aXaBOj" target="_blank">demo</a>。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>利用<code>:target</code>伪类,我们很好的实现了点击标签切换内容的效果。虽然这只是很简单的效果,但是在实际开发过程中我们也可以运用起来,在其基础上再加一些动画都是Ok的。这样实现的好处是会少很多DOM操作,从性能和代码简洁角度来说也是很大的一个提升。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/CSS%E7%9B%B8%E5%85%B3/">CSS相关</category>
<category domain="https://flyrk.github.io/tags/CSS%E6%8A%80%E5%B7%A7/">CSS技巧</category>
<comments>https://flyrk.github.io/2019/02/15/fast-achieve-tabpane-effect/#disqus_thread</comments>
</item>
<item>
<title>NodeJS常用的一些文件操作技巧</title>
<link>https://flyrk.github.io/2019/01/29/nodeJS-file-system-operation/</link>
<guid>https://flyrk.github.io/2019/01/29/nodeJS-file-system-operation/</guid>
<pubDate>Tue, 29 Jan 2019 10:20:04 GMT</pubDate>
<description><p>在使用NodeJS的时候,我们经常会遇到对文件进行查找、删除、增加内容等操作,还好NodeJS内置了<code>fs</code>模块,<code>fs</code>的API提供了一系列方法,通过它我们可以实现基本的文件操作,接下来我们就来见识见识。</p></description>
<content:encoded><![CDATA[<p>在使用NodeJS的时候,我们经常会遇到对文件进行查找、删除、增加内容等操作,还好NodeJS内置了<code>fs</code>模块,<code>fs</code>的API提供了一系列方法,通过它我们可以实现基本的文件操作,接下来我们就来见识见识。</p><span id="more"></span><blockquote><p>说明:<br>本文全部使用ES6语法<br>提到的每个方法都有异步和同步的区别,同步则在方法后面加上<code>Sync</code></p></blockquote><h1 id="文件夹操作"><a href="#文件夹操作" class="headerlink" title="文件夹操作"></a>文件夹操作</h1><h2 id="读取文件夹内容"><a href="#读取文件夹内容" class="headerlink" title="读取文件夹内容"></a>读取文件夹内容</h2><p>很多时候我们得到一个文件夹路径,如果需要获取文件夹里有哪些文件,我们可以利用<code>fs.readdir()</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><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">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> dest = <span class="string">'.../work/myFiles'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步读取文件夹内容</span></span><br><span class="line">fs.<span class="title function_">readdir</span>(dest, <span class="function">(<span class="params">err, files</span>) =></span> { <span class="comment">// callback函数</span></span><br><span class="line"> <span class="keyword">if</span> (!err) {</span><br><span class="line"> files.<span class="title function_">forEach</span>(<span class="function"><span class="params">file</span> =></span> <span class="variable language_">console</span>.<span class="title function_">log</span>(file)); <span class="comment">// 返回的files是一个包含目录中所有文件名的数组</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步读取文件夹内容</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> files = fs.<span class="title function_">readdirSync</span>(dest);</span><br><span class="line"> files.<span class="title function_">forEach</span>(<span class="function"><span class="params">file</span> =></span> <span class="variable language_">console</span>.<span class="title function_">log</span>(file));</span><br><span class="line">} <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="创建文件夹"><a href="#创建文件夹" class="headerlink" title="创建文件夹"></a>创建文件夹</h2><p>创建文件夹利用的API是<code>fs.mkdir()</code>,这个操作我们其实很熟悉了,在bash里我们经常用<code>mkdir filedirectionName</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 异步创建文件夹</span></span><br><span class="line">fs.<span class="title function_">mkdir</span>(dest, { <span class="attr">recursive</span>: <span class="literal">true</span> }, <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步创建文件夹</span></span><br><span class="line">fs.<span class="title function_">mkdirSync</span>(dest, { <span class="attr">recursive</span>: <span class="literal">true</span> });</span><br></pre></td></tr></table></figure><p>这里我们看到<code>{ recursive: true }</code>这个参数,设置为true代表应该创建父文件夹。比如我们设置<code>recursive</code>为true后,要创建<code>/tmp/a/app</code>目录,则无论是否存在<code>/tmp/a</code>目录都会新建,如果不设置<code>recursive</code>,则没有父文件夹的情况下新建会不成功。</p><p><code>fs.mkdirSync()</code>返回<code>undefined</code>。</p><h2 id="确认某个文件夹是否存在"><a href="#确认某个文件夹是否存在" class="headerlink" title="确认某个文件夹是否存在"></a>确认某个文件夹是否存在</h2><p>之前我们在创建文件夹的时候可以通过设置<code>recursive</code>为true来创建父文件夹不存在的情况下的文件夹,但是有的时候我们得到一个文件夹路径,我们并不知道它的父文件夹是否存在,甚至不知道它父文件夹的父文件夹是否存在…所以,我们不想关心那么多,我们就想知道这个文件夹是否存在,怎么办呢?</p><p>这里我用到了<code>fs-extra</code>模块,它是在<code>fs</code>的基础上进行了一些扩展,有兴趣的可以去<a href="https://github.com/jprichardson/node-fs-extra">Github</a>查看。其中有一个API:<code>ensurDir()</code>,它可以用来查找文件夹是否存在,如果不存在,则新建该文件夹,且会自动把父文件夹也新建了(如果父文件夹不存在)。说白了,就是能确保你想要查找的文件夹存在,因为我们一般发现某个文件夹不存在的话肯定会想要新建这个文件夹,这个API则帮我们把操作都实现了:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs-extra'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步</span></span><br><span class="line">fs.<span class="title function_">ensureDir</span>(dest, <span class="function"><span class="params">err</span> =></span> <span class="variable language_">console</span>.<span class="title function_">log</span>(err));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> fs.<span class="title function_">ensureDirSync</span>(dest);</span><br><span class="line">} <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="文件操作"><a href="#文件操作" class="headerlink" title="文件操作"></a>文件操作</h1><h2 id="读取文件内容"><a href="#读取文件内容" class="headerlink" title="读取文件内容"></a>读取文件内容</h2><p>之前介绍了如何读取文件夹内容,但如果我们想要读取文件内容又如何操作呢?这里有两种方法。</p><h3 id="fs-readFile"><a href="#fs-readFile" class="headerlink" title="fs.readFile"></a>fs.readFile</h3><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><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">fs.<span class="title function_">readFile</span>(dest, {<span class="attr">encoding</span>: <span class="string">'utf8'</span>}, <span class="function">(<span class="params">err, data</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(data);</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> data = fs.<span class="title function_">readFileSync</span>(dest, {<span class="attr">encoding</span>: <span class="string">'utf8'</span>});</span><br><span class="line">} <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们options可以设置参数:<code>encoding</code>,代表返回的data内容字符串编码方式,如果不指定的话,则返回的data格式为buffer。</p><h3 id="fs-readJson"><a href="#fs-readJson" class="headerlink" title="fs.readJson"></a>fs.readJson</h3><p>除了上面那个方法,我们还可以使用<code>readJson</code>方法,当然这个是<code>fs-extra</code>里的扩展方法。因为我们现在存储数据很多时候都用JSON文件来存储,利用这个方法可以很方便的读取JSON文件,返回的是将JSON内容转化为Object的JSON对象,方便我们对数据进行操作。</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> fs = <span class="built_in">require</span>(<span class="string">'fs-extra'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步</span></span><br><span class="line">fs.<span class="title function_">readJson</span>(<span class="string">'./package.json'</span>, <span class="function">(<span class="params">err, packageObj</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(packageObj.<span class="property">version</span>); <span class="comment">// 可以直接对对象的属性进行访问</span></span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> packageObj = fs.<span class="title function_">readJsonSync</span>(<span class="string">'./package.json'</span>);</span><br><span class="line">} <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="写入文件内容"><a href="#写入文件内容" class="headerlink" title="写入文件内容"></a>写入文件内容</h2><p>有了读取文件内容,我们还需要写入文件内容,同样有两种方法,一种是<code>writeFile</code>,另一种是<code>writeJson</code>。前者和<code>readFile</code>差不多,只是第二个参数变成了要写入的内容,具体可参考<a href="http://nodejs.cn/api/fs.html#fs_fs_writefile_file_data_options_callback">文档</a>。</p><p>后者则也是对JSON文件进行的写入,要注意的是,每次写入都会覆盖之前所有的内容,所以我们如果想在原有的基础上新增内容,则需要先读取修改完内容再写入,这里我就只演示异步操作:</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> fs = <span class="built_in">require</span>(<span class="string">'fs-extra'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 读取</span></span><br><span class="line">fs.<span class="title function_">readJson</span>(<span class="string">'./package.json'</span>, <span class="function">(<span class="params">err, packageObj</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> packageObj.<span class="property">newProperty</span> = <span class="string">'new Property'</span>;</span><br><span class="line"> fs.<span class="title function_">writeJson</span>(<span class="string">'./package.json'</span>, packageObj, <span class="function"><span class="params">err</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'success'</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><h1 id="拷贝文件或者文件夹"><a href="#拷贝文件或者文件夹" class="headerlink" title="拷贝文件或者文件夹"></a>拷贝文件或者文件夹</h1><p>在<code>fs-extra</code>模块,提供了<code>copy(src, dest, [options, callback])</code>操作,其中<code>src</code>可以为文件夹也可以为文件。当<code>src</code>为文件夹,则会拷贝文件夹里的所有文件和文件夹,要注意<code>src</code>和<code>dest</code>必须同时为文件夹或者同时为文件,这样才能正确的把<code>src</code>的内容拷贝到<code>dest</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs-extra'</span>);</span><br><span class="line"><span class="keyword">const</span> src = <span class="string">'/tmp/srcfile'</span>;</span><br><span class="line"><span class="keyword">const</span> dest = <span class="string">'/tmp/destfile'</span>;</span><br><span class="line"></span><br><span class="line">fs.<span class="title function_">copy</span>(src, dest, <span class="function"><span class="params">err</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h1 id="获取文件状态"><a href="#获取文件状态" class="headerlink" title="获取文件状态"></a>获取文件状态</h1><p>当我们想要获取某个文件的状态,我们可以用<code>fs.stat(path)</code>、<code>fs.lstat(path)</code>、<code>fs.fstat(path)</code>,它们都返回一个<code>fs.stats</code>类对象。区别在于,<code>fs.lstat</code>的path可以是符号链接,<code>fs.fstat</code>的path是文件描述符<code>fd</code>。</p><p>返回的<code>Stats</code>类包含许多代表文件状态的属性和方法,常用的有:</p><ul><li>stats.isDirectory()。判断是否是文件夹</li><li>stats.isFile()。判断是否是文件</li><li>stats.dev。包含改文件的设备的数字标识符</li><li>stats.size。文件的大小(字节为单位)</li><li>stats.atime。上次访问此文件的时间戳。(要注意是系统本地时间,因此如果复制到别的电脑系统时间不一样可能会造成不可预知的后果,所以慎用)</li><li>stats.mtime。上次修改此文件的时间戳。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这里我只粗略地介绍了几个常用的方法,<code>fs</code>还有很多文件操作,官方文档有更加详细的解释,感兴趣的小伙伴可以戳<a href="http://nodejs.cn/api/fs.html">这里</a>了解更多,帮助自己在使用NodeJS操作文件时更加得心应手。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/nodejs/">nodejs</category>
<comments>https://flyrk.github.io/2019/01/29/nodeJS-file-system-operation/#disqus_thread</comments>
</item>
<item>
<title>如何实现文字输入效果</title>
<link>https://flyrk.github.io/2019/01/23/how-to-achieve-typewriter-effect/</link>
<guid>https://flyrk.github.io/2019/01/23/how-to-achieve-typewriter-effect/</guid>
<pubDate>Wed, 23 Jan 2019 11:01:58 GMT</pubDate>
<description><p>我们看到有些网站的文字会有类似于输入的效果,就好像自动打字一样,虽然这个效果用的地方可能不多,但如果用的好的话可能会有奇效,比如在博客、演讲展示等方面应用。那么,怎么实现呢?</p></description>
<content:encoded><![CDATA[<p>我们看到有些网站的文字会有类似于输入的效果,就好像自动打字一样,虽然这个效果用的地方可能不多,但如果用的好的话可能会有奇效,比如在博客、演讲展示等方面应用。那么,怎么实现呢?</p><span id="more"></span><h2 id="使用setTimeout实现"><a href="#使用setTimeout实现" class="headerlink" title="使用setTimeout实现"></a>使用setTimeout实现</h2><p>第一种方法是使用setTimeout进行文字控制,每一个文字都是一个<code>span</code>,利用DOM操作以一定的间隔将每个文字添加到文档中,同时设置<code>opacity:1</code>使其显示出来。光标闪烁的效果则利用<code>border-left</code>进行animation动画展示。</p><h3 id="HTML结构"><a href="#HTML结构" class="headerlink" title="HTML结构"></a>HTML结构</h3><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">article</span> <span class="attr">class</span>=<span class="string">"input-container"</span>></span></span><br><span class="line"> <span class="comment"><!--这里是需要展示的原始文字,设为display:none;--></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"originWords"</span>></span>Hello world!<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">article</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">article</span> <span class="attr">class</span>=<span class="string">"typewriter-container"</span>></span></span><br><span class="line"> <span class="comment"><!--这里是最终有文字输入效果的文字--></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"typewriter-output"</span>></span><span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">article</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>解释一下,我们首先把原始文字设为<code>display: none;</code>,这样不会占用文档位置,然后我们给之后添加的文字<code>span</code>块指定class为<code>word</code>。</p><h3 id="CSS设置"><a href="#CSS设置" class="headerlink" title="CSS设置"></a>CSS设置</h3><p>先看代码:</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><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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.input-container</span> {</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.typewriter-container</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50%</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.word</span> {</span><br><span class="line"> <span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">transition</span>: opacity <span class="number">2s</span> step-start;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.typewriter-output</span><span class="selector-pseudo">:after</span> {</span><br><span class="line"> <span class="attribute">display</span>: inline-block;</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">vertical-align</span>: -<span class="number">1px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">14px</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">1px</span>;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">2px</span> solid black;</span><br><span class="line"> <span class="attribute">margin-left</span>: <span class="number">2px</span>;</span><br><span class="line"> <span class="attribute">animation</span>: shine <span class="number">1s</span> infinite;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@keyframes</span> shine {</span><br><span class="line"> <span class="number">50%</span> {</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们对<code>word</code>设置<code>transition: opacity 2s step-start;</code>。这里之所以通过<code>opacity</code>来进行transition,是因为transition不支持<code>display</code>,而<code>opacity</code>只需要设置<code>0</code>或<code>1</code>就可以使元素进行显示而不需要重排。</p><p>这里<code>transition-timing-function</code>使用<code>step-start</code>,使其有跳跃的效果,<code>step-start</code>相当于<code>steps(1, jump-start)</code>,<code>steps(n, <jumpterm>)</code>代表transition会停顿n次,每一次的效果为<code><jumpterm></code>,<code>jump-start</code>代表第一个跳跃发生在<code>transition</code>刚开始的时候,更多效果见<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function">文档</a>。</p><p>为什么要使用<code>:after</code>呢?因为我是逐步向<code>typewriter-output</code>添加<code>span</code>块,所以一开始宽度是不确定的,而且光标高度也不好设置,所以我想了个笨方法,使用<code>after</code>伪元素生成一个光标,并用<code>animation</code>控制其<code>border-left</code>的显示。但这里有个bug就是文字显示的间隔不能超过300ms,所以说这样处理还是有些问题,以后可能会有更好的方法。</p><h3 id="JS部分"><a href="#JS部分" class="headerlink" title="JS部分"></a>JS部分</h3><p>其实就是利用<code>setTimout</code>设置时间间隔,通过DOM操作进行添加文字,先看代码:</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> words = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'.originWords'</span>).<span class="property">innerText</span>;</span><br><span class="line"><span class="keyword">var</span> output = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'.typewriter-output'</span>);</span><br><span class="line"><span class="keyword">var</span> word = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">var</span> lastWord = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = words.<span class="property">length</span>; i< l; i++) {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="title function_">writeWord</span>(i), i * <span class="number">200</span>);<span class="comment">// 每隔0.2s输出一个文字</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// 这里使用闭包保存i</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">writeWord</span>(<span class="params">index</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> word = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">'span'</span>);</span><br><span class="line"> word.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">'word'</span>);</span><br><span class="line"> output.<span class="title function_">appendChild</span>(word);</span><br><span class="line"> <span class="keyword">if</span> (!lastWord) {</span><br><span class="line"> lastWord = word;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (lastWord !== word) {</span><br><span class="line"> lastWord.<span class="property">style</span>.<span class="property">opacity</span> = <span class="string">'1'</span>; </span><br><span class="line"> }</span><br><span class="line"> word.<span class="property">innerText</span> = words[index];</span><br><span class="line"> lastWord = word;</span><br><span class="line"> <span class="keyword">if</span> (index === words.<span class="property">length</span> - <span class="number">1</span>) {</span><br><span class="line"> lastWord.<span class="property">style</span>.<span class="property">opacity</span> = <span class="string">'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>代码部分很简单,就是先获取需要展示的文字内容,然后每个字都通过dom操作塞进去,设置<code>opacity:1</code>,再进行一下边界判断,就实现了文字的展示。</p><hr><h2 id="纯animation实现"><a href="#纯animation实现" class="headerlink" title="纯animation实现"></a>纯animation实现</h2><p>这次我们不用<code>setTimeout</code>,所有展示的动画全部用<code>animation</code>来实现。废话不多说,我们来看一下。</p><h3 id="HTML结构-1"><a href="#HTML结构-1" class="headerlink" title="HTML结构"></a>HTML结构</h3><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">h1</span> <span class="attr">id</span>=<span class="string">"typewriter"</span>></span>Hello, My friend.<span class="tag"></<span class="name">h1</span>></span></span><br></pre></td></tr></table></figure><p>这种方法的好处就是不用有一个隐藏的原始文字段落,直接显示想显示的内容。</p><h3 id="CSS设置-1"><a href="#CSS设置-1" class="headerlink" title="CSS设置"></a>CSS设置</h3><p>接下来我们设置CSS:</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><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="selector-tag">body</span> {</span><br><span class="line"> <span class="attribute">background</span>: black;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">h1</span> {</span><br><span class="line"> <span class="attribute">font</span>: bold <span class="number">100%</span> monospace;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">0.1em</span> solid;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">2em</span> <span class="number">1em</span>;</span><br><span class="line"> <span class="attribute">white-space</span>: nowrap;</span><br><span class="line"> <span class="attribute">overflow</span>: hidden;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> typing {</span><br><span class="line"> <span class="selector-tag">from</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> cursor-blink {</span><br><span class="line"> <span class="number">50%</span> {</span><br><span class="line"> <span class="attribute">border-color</span>: transparent;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看起来很简单对不对?挨?说好的动画呢?别急,我们通过JS来设置<code>animation</code>。</p><h3 id="JS部分-1"><a href="#JS部分-1" class="headerlink" title="JS部分"></a>JS部分</h3><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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> typewriter = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'typewriter'</span>);</span><br><span class="line"><span class="keyword">var</span> words = typewriter.<span class="property">innerText</span>.<span class="property">length</span>;</span><br><span class="line"><span class="keyword">if</span> (words) {</span><br><span class="line"> typewriter.<span class="property">style</span>.<span class="property">width</span> = words + <span class="string">'ch'</span>;</span><br><span class="line"> typewriter.<span class="property">style</span>.<span class="property">animation</span> = <span class="string">'typing 3s steps('</span> + words + <span class="string">', end), cursor-blink 0.5s step-end infinite alternate'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们所做的事就是获取文字段落字符串的长度,然后设置其宽度和动画效果。非常简单!</p><p>那么,为什么这样做可以呢?别急,我们来分析分析。</p><p>首先,在获取了文字字符串长度后,我们设置段落的宽度为<code>width: Xch;</code>,这里X代表字符串长度,<code>ch</code>是长度单位,代表数字“0”的宽度,通常也就是一个字符的宽度。</p><p>然后,我们直接设置动画:<br><code>animation: typing 3s steps(X, end), cursor-blink 0.5s step-end infinite alternate;</code><br>一切就搞定了!为啥?通过CSS我们设置了<code>@keyframe</code>typing,表示宽度从0开始,一直到设置的<code>Xch</code>,持续3s,用<code>steps</code>动画函数,分为X步,也就是有多少个字符,就停顿多少下,达到输入的效果。至于光标的效果,我们直接通过动画设定<code>50%</code>的时候<code>border-color</code>为<code>transparent</code>,则每一秒闪烁一次,因为只有<code>border-right</code>设置了宽度,所以完美达到光标的效果。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>两种方法比较起来,第二种方法更加实用简便,推荐大家使用。这只是简单的CSS3 animation动画应用,今后还可以开动脑筋,实现更多炫酷有趣的效果。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/CSS%E7%9B%B8%E5%85%B3/">CSS相关</category>
<category domain="https://flyrk.github.io/tags/CSS%E6%8A%80%E5%B7%A7/">CSS技巧</category>
<comments>https://flyrk.github.io/2019/01/23/how-to-achieve-typewriter-effect/#disqus_thread</comments>
</item>
<item>
<title>如何实现页面不同内容之间的跳转</title>
<link>https://flyrk.github.io/2019/01/12/use-anchor-jump-to-anywhere-in-page/</link>
<guid>https://flyrk.github.io/2019/01/12/use-anchor-jump-to-anywhere-in-page/</guid>
<pubDate>Sat, 12 Jan 2019 09:10:54 GMT</pubDate>
<description><p>我们常常会碰到这样的需求:点击某个标题、某段话,跳转到页面对应的位置,并且浏览器不会刷新,相当于在页面进行内部导航,这在浏览文章、或者是内容比较多的长屏单页应用上有着迫切的需求。那么,如何实现这个简单的功能呢?</p></description>
<content:encoded><![CDATA[<p>我们常常会碰到这样的需求:点击某个标题、某段话,跳转到页面对应的位置,并且浏览器不会刷新,相当于在页面进行内部导航,这在浏览文章、或者是内容比较多的长屏单页应用上有着迫切的需求。那么,如何实现这个简单的功能呢?</p><span id="more"></span><h1 id="利用a标签的href值"><a href="#利用a标签的href值" class="headerlink" title="利用a标签的href值"></a>利用a标签的href值</h1><p>我们知道,<code><a></code>标签的<code>href</code>属性支持绝对路径和相对路径,其实它还支持路由hash。打个比方,我们的浏览器地址是:<code>http://www.example.com</code>,HTML里有这样一个标签:<code><a href="#start">click</a></code>,点击该标签后,浏览器的URL地址后面就会多出一个hash值:<code>http://www.example.com#start</code>,如果HTML里有id为<code>start</code>的元素,则浏览器窗口会自动滚动到该元素所在的位置,也就是使该元素滚动到视窗最上面。</p><p>那么,很简单的我们就可以利用这个属性实现跳转到页面任何位置,只要在想要跳转的地方设置<code>id</code>值,再通过设置<code>a</code>标签的<code>href</code>为<code>#id</code>,我们就可以实现跳转。</p><p>为什么我们可以使用<code>#id</code>实现跳转呢?我们先来看看MDN的定义:</p><blockquote><p><strong>href</strong></p><p>Contains a URL or a URL fragment that the hyperlink points to.</p><p>A URL fragment is a name preceded by a hash mark (<code>#</code>), which specifies an internal target location (an <code>id</code> of an HTML element) within the current document.</p></blockquote><p>什么意思呢?其实<code>href</code>就是给定一个超链接,它的值指向的其实是一个URL或者URL片段,而一个URL片段通常是以<code>#name</code>的形式,代表着当前文档页面内部target的位置(也就是一个HTML元素的id),所以我们就可以利用<code>id</code>来进行页面内部元素之间的跳转。</p><p>那么如果要跳转到页面顶部呢?有的人可能会说:用JS设置<code>scrollTop</code>的值不就好了,这当然可以,之前我也写过跳转到顶部的<a href="https://xmflyrk.com/2017/08/22/scroll-to-top-btn/">文章</a>。但是,如果不用JS呢?很简单,我们直接这样设置:<code><a href="#">click to top</a></code>,点击就能直接跳转到页面顶部了!</p><p>这是为什么呢?我们知道<code>#</code>后面的name值代表的是某个元素的<code>id</code>,但如果没有这个name,那就代表所有元素,也就是整个页面,所以就把页面移动到视窗的最顶部了,是不是很方便呢。但是这样有一点不好,就是没有滚动动画,跳转比较生硬,对于UI动画要求不高的页面用这个非常合适。</p><h1 id="Input和Label的妙用"><a href="#Input和Label的妙用" class="headerlink" title="Input和Label的妙用"></a>Input和Label的妙用</h1><p>那么除了利用<code>a</code>标签,还有其他方法可以在不用JS的情况下实现跳转吗?答案就是利用<code>input</code>和<code>label</code>标签!</p><p>我们先在想要跳转的地方增加一个<code>input</code>标签:</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">id</span>=<span class="string">"anchor"</span>></span>// 这是我们要跳转的位置</span><br><span class="line"> <span class="tag"><<span class="name">article</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span>></span>This is a title<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span></span><br><span class="line"> LoraeraweMefawef wefaewfaw fawe fwae faw</span><br><span class="line"> <span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> // 假设文章很长...</span><br><span class="line"> <span class="tag"></<span class="name">article</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br></pre></td></tr></table></figure><p>然后设置<code>CSS</code>令其不可见:</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-id">#anchor</span> {</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">0</span> transparent;</span><br><span class="line">}</span><br><span class="line"><span class="selector-id">#anchor</span><span class="selector-pseudo">:focus</span> {</span><br><span class="line"> <span class="attribute">outline</span>: <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接下来做什么呢?我们知道<code>label</code>标签有一个<code>for</code>属性,它的值代表着对应<code>input</code>的<code>id</code>,当设置了<code>for</code>值为某个<code>input</code>的<code>id</code>,我们点击<code>label</code>,则会令对应的<code><input></code>变为<code>focus</code>状态,而当某个元素为focus状态时,浏览器窗口会自动使其滚动到视窗内,这样我们就实现了点击跳转功能!</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">id</span>=<span class="string">"anchor"</span>></span>// 这是我们要跳转的位置</span><br><span class="line"> <span class="tag"><<span class="name">article</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span>></span>This is a title<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span></span><br><span class="line"> LoraeraweMefawef wefaewfaw fawe fwae faw</span><br><span class="line"> <span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> // 假设文章很长...</span><br><span class="line"> <span class="tag"></<span class="name">article</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">for</span>=<span class="string">"anchor"</span>></span>点击跳转<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这里我们用到了两种方法实现页面元素跳转,都没有用到JavaScript,在对滚动动画要求不高的条件下,用这两种方法能简单有效的实现跳转需求,值得一试!</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/JS%E6%8A%80%E5%B7%A7/">JS技巧</category>
<comments>https://flyrk.github.io/2019/01/12/use-anchor-jump-to-anywhere-in-page/#disqus_thread</comments>
</item>
<item>
<title>迟来的2018总结</title>
<link>https://flyrk.github.io/2019/01/09/2018-road-to-grow-up/</link>
<guid>https://flyrk.github.io/2019/01/09/2018-road-to-grow-up/</guid>
<pubDate>Wed, 09 Jan 2019 10:40:33 GMT</pubDate>
<description><p>已经2019年了,由于最近一大堆事情,没有时间更博,现在终于有时间好好坐下来总结一下自己的2018了。</p></description>
<content:encoded><![CDATA[<p>已经2019年了,由于最近一大堆事情,没有时间更博,现在终于有时间好好坐下来总结一下自己的2018了。</p><span id="more"></span><p>要是问我生命中最重要的年份有哪些,2018年肯定是其中之一。</p><p>这一年,我结束了学生生涯,正式踏上了工作岗位,成为了社会人。</p><p>这一年,我从成都只身一人来到杭州,没有听父母的意见去传统行业寻求稳定的工作,而是投入到互联网的浪潮当中。</p><p>这一年,我找到了我爱的那个她,希望可以和她一起走下去,我们的路,才刚刚开始。</p><p>这一年…</p><h1 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h1><p>这一年最大的改变,就是我脱离了学生这个群体,走向了社会这个大熔炉。说实话,6月底刚刚毕业离开校园,心里面还是充满了对未来未知的恐惧。从小到大,生活都是按部就班,小学、中学、到大学,你可以清晰地看到前面的路,要做的就是好好学习,往前走就行了。但是,现在不同了,工作以后,前面的路一切都是未知的,走得怎么样,全取决于自己,没有人替我安排。</p><p>工作半年,其实挺困惑的,因为很多时候自己还停留在学生思维,学生时代做事,都有人推着你走,老师会帮你监督,做的好不好,更多的时候取决于你考的好不好,得到的反馈简单又明了。但是工作后才发现,所有的事情都变的不一样了。没有人帮你设定目标,没有人推着你走,即使你什么都不做也不会有人来管你,领导只看最后的结果,用数据说话,只有主动出击,才能收获成果,被动地等待,永远只能在原地打转。</p><p>所以说,作为职场新人,我还有很长的路要走,首先需要改变的就是自己的思维模式,做一个主动出击的人。</p><h1 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h1><p>作为一个刚入门的前端工程师,2018年也是我技术提升最快的一年。这里用一张思维导图记录下自己的前端学习之路。</p><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/front-end-learning.jpg" alt="frontend-learning"></p><p>这只是我总结的一个大概版本,之后还会不断补充。其实里面很多知识我都只是刚刚了解,还需要不断深入。</p><p>2018,对于我来说算是正式入了前端的大门,对整个前端的大架构也有了初步的了解,但是这还只是刚开始,前端要学的东西太多了,我不能停止学习的步伐,虽然工作后可能学习的时间变少了,但不管怎样都要抽时间出来自学,时刻保持好奇心和学习力。而且,前端工程师也是程序员,算法、数据结构、计算机网络等基本知识也需要巩固了解,技术的路上,学无止境。</p><p>2019我需要在巩固基础知识的同时,不断扩展自己的知识面,多写代码,多实践,只有在实践中才能真正学会运用相关知识。talk is cheap, just show the code.</p><h1 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h1><p>2018最幸福的事,就是终于脱离了单身狗的队伍!撒狗粮的事就不多说了,一切才刚刚开始,我希望未来能和她一起携手度过每一天,成为我们彼此眼中那个更好的自己。</p><p>工作后,离开了父母的怀抱,生活中某种意义上成为了真正独立的个体,很多事情都需要自己来操作,我需要慢慢适应新生活的节奏,承担起更多的责任,平衡好工作与生活的时间点。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>碎碎念了这么多,新的一年,新的开始,我希望未来的一年在工作、技术、生活方面都能不断成长蜕变,不停下自己的脚步。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E9%9A%8F%E7%AC%94/">随笔</category>
<category domain="https://flyrk.github.io/tags/%E6%80%9D%E8%80%83%E6%84%9F%E6%82%9F/">思考感悟</category>
<comments>https://flyrk.github.io/2019/01/09/2018-road-to-grow-up/#disqus_thread</comments>
</item>
<item>
<title>实现基于codemirror的markdown编辑器(二)</title>
<link>https://flyrk.github.io/2018/12/20/codemirror-markdown-editor-chapter2/</link>
<guid>https://flyrk.github.io/2018/12/20/codemirror-markdown-editor-chapter2/</guid>
<pubDate>Thu, 20 Dec 2018 10:39:16 GMT</pubDate>
<description><p>前面我们提到了如何加载<code>codemirror</code>组件和一些基本配置,以及如何实现实时预览。接下来才是重头戏,我们需要实现同屏滚动和添加自定义按钮。</p></description>
<content:encoded><![CDATA[<p>前面我们提到了如何加载<code>codemirror</code>组件和一些基本配置,以及如何实现实时预览。接下来才是重头戏,我们需要实现同屏滚动和添加自定义按钮。</p><span id="more"></span><p>这篇我们先来着重讲讲同屏滚动。</p><h1 id="同屏滚动"><a href="#同屏滚动" class="headerlink" title="同屏滚动"></a>同屏滚动</h1><p>我们写文档时,除了想要实时预览,还想要能同步滚动,这样写到哪预览页面就滚动到哪,不用鼠标移来移去,方便不少。但是如何实现呢?我们一步一步来。</p><h2 id="监听事件"><a href="#监听事件" class="headerlink" title="监听事件"></a>监听事件</h2><p>首先,要想滚动,肯定得监听滚动事件<code>onscroll</code>,但是这里我没有直接监听<code>scroll</code>事件,因为我们有两个区域,左边是输入文本框,右边是预览区,我想实现这样的功能:当鼠标移到左边使滚动条滚动时,这个时候先监听左边的滚动事件计算出相应的滚动高度后再修改右边的滚动高度,这样右边是跟着左边的高度滚动而滚动的。相应的,当鼠标移到右边区域使滚动条滚动时,先监听右边的滚动事件计算出滚动高度后再修改左边的滚动高度。这样滚动才能实现对应关系,但是如果直接监听滚动事件,则会出现一个问题,当右边滚动时,左边滚动事件没有移除,则又会触发,计算高度后引发右边滚动,形成一个一直滚动的死循环,最后整个页面滚动位置都是乱的。<br>所以,这里我先监听<code>mouseover</code>和<code>mouseleave</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><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="comment">//...</span></span><br><span class="line">codemirrorScroll = <span class="function">(<span class="params">doc</span>) =></span> <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">onScroll</span> && <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">onScroll</span>(doc, <span class="variable language_">this</span>.<span class="property">editRoot</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">codemirrorScrollHandler = <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">codeMirror</span>.<span class="title function_">on</span>(<span class="string">'scroll'</span>, <span class="variable language_">this</span>.<span class="title function_">codemirrorScroll</span>(<span class="variable language_">this</span>.<span class="property">codeMirror</span>.<span class="property">doc</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">codemirrorRemoveScroll = <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">codeMirror</span>.<span class="title function_">off</span>(<span class="string">'scroll'</span>, <span class="variable language_">this</span>.<span class="title function_">codemirrorScroll</span>(<span class="variable language_">this</span>.<span class="property">codeMirror</span>.<span class="property">doc</span>));</span><br><span class="line">}</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"><span class="title function_">componentDidMount</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">editRoot</span>.<span class="title function_">addEventListener</span>(<span class="string">'mouseover'</span>, <span class="variable language_">this</span>.<span class="property">codemirrorScrollHandler</span>);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">editRoot</span>.<span class="title function_">addEventListener</span>(<span class="string">'mouseleave'</span>, <span class="variable language_">this</span>.<span class="property">codemirrorRemoveScroll</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意到,我在<code>mouseover</code>的时候开始监听<code>scroll</code>事件,<code>mouseleave</code>的时候移除之前的<code>scroll</code>事件,所以这里要用<code>codemirrorScroll</code>函数封装,以便移除时是同一个函数。<br>这里我把<code>scroll</code>事件的处理通过props抛给父组件,我们来看看父组件是怎么实现<code>scroll</code>事件的。</p><p>首先考虑到性能和滚动流畅度问题,使用<code>debounce</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">debounceContentScroll = <span class="title function_">debounce</span>(<span class="variable language_">this</span>.<span class="property">handleScrollContent</span>);</span><br></pre></td></tr></table></figure><p>接下来就是如何去计算当前滚动的位置并使另一边的内容自动滚动到对应高度,这里我用一幅图解释一下:<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/markdown-scroll.png" alt="markdown-scroll"><br>我们主要获取的就是这三个值:<code>child.offsetHeight</code>、<code>parent.offsetHeight</code>、<code>child.scrollTop</code>。所以我们需要在展示内容外面都用<code>div</code>包一层,代表<code>parent</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></pre></td><td class="code"><pre><span class="line">calcScrollScale = <span class="function">(<span class="params">scrollTopMax1, scrollTopMax2</span>) =></span> (scrollTopMax1 / scrollTopMax2);</span><br><span class="line"></span><br><span class="line">calcScrollTopMax = <span class="function">(<span class="params">parent, child</span>) =></span> <span class="title class_">Math</span>.<span class="title function_">abs</span>((child.<span class="property">offsetHeight</span> || child.<span class="property">height</span>) - parent.<span class="property">offsetHeight</span>)</span><br><span class="line"></span><br><span class="line">updateScroll = <span class="function">(<span class="params">scrollTop, target, scrollTopMax1, scrollTopMax2</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> scale = <span class="variable language_">this</span>.<span class="title function_">calcScrollScale</span>(scrollTopMax1, scrollTopMax2);</span><br><span class="line"> target.<span class="property">scrollTop</span> = scrollTop / scale;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">handleScrollContent = <span class="function">(<span class="params">doc, docParent</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> mdPreview = <span class="variable language_">this</span>.<span class="property">mdPreview</span>.<span class="property">current</span>;</span><br><span class="line"> <span class="keyword">const</span> previewContent = <span class="variable language_">this</span>.<span class="property">previewContent</span>.<span class="property">current</span>;</span><br><span class="line"> <span class="keyword">const</span> scrollTopMaxFrom = <span class="variable language_">this</span>.<span class="title function_">calcScrollTopMax</span>(docParent, doc);</span><br><span class="line"> <span class="keyword">const</span> scrollTopMaxTo = <span class="variable language_">this</span>.<span class="title function_">calcScrollTopMax</span>(mdPreview, previewContent);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">updateScroll</span>(doc.<span class="property">scrollTop</span>, mdPreview, scrollTopMaxFrom, scrollTopMaxTo);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里有个很重要的参数:<code>scrollTopMax</code>。<br><code>scrollTopMax = |(child.offsetHeight || child.height) - parent.offsetHeight|</code>。使用<code>child.height</code>是因为codemirror的<code>doc</code>对象内容高度可以直接通过<code>height</code>属性获得。为什么要这样计算?<br>我们算的是当前内容可滚动的最大高度,为什么要计算这个?因为我们知道因为渲染的原因,左边输入的时候是单纯的文本,右边渲染出来有标题、图片等等,实际高度会比左边高得多,通过计算两边相对父容器可滚动的最大高度,再计算这两个的比值:<code>calcScrollScale = (scrollTopMax1, scrollTopMax2) => (scrollTopMax1 / scrollTopMax2);</code>,可以得到一个<code>scrollScale</code>。<br>我们有这样一个公式:<code>left.scrollTop / right.scrollTop = left.scrollTopMax / right.scrollTopMax</code>,那么我们很容易得出右边的滚动高度:<code>right.scrollTop = left.scrollTop / scrollScale</code>。于是就可以使右边滚动到对应内容的高度了!</p><p>有了左边的范例,对右边预览的container同样使用一样的监听事件,这样就可以实现双向同步滚动绑定了!!!</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>实现同步滚动的关键点其实有两个:一个是注意监听事件的变化,先监听<code>mouseover</code>和<code>mouseleave</code>,这样不会出现滚动死循环。另一个是理解公式:<code>left.scrollTop / right.scrollTop = left.scrollTopMax / right.scrollTopMax</code>。理解了这两个关键点,其实同步滚动就很容易了。<br>但要注意的是,这里的<code>debounce</code>设置延迟时间可能还需要好好斟酌,我默认使用20毫秒,实际滚动时还是会有点卡顿,可以适当改变一下数值。<br>还有就是,因为markdown语法原因,段落之间都必须有一个空行,不然渲染出来文本没换行,导致段落不一致。</p><blockquote><p>参考资料:</p><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop">scrollTop</a></li><li><a href="https://juejin.im/post/5a3bb40e5188252b145b38e3">原生JS控制多个滚动条同步跟随滚动</a></li></ul></blockquote>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/%E7%BC%96%E8%BE%91%E5%99%A8/">编辑器</category>
<category domain="https://flyrk.github.io/tags/markdown/">markdown</category>
<comments>https://flyrk.github.io/2018/12/20/codemirror-markdown-editor-chapter2/#disqus_thread</comments>
</item>
<item>
<title>实现基于codemirror的markdown编辑器(一)</title>
<link>https://flyrk.github.io/2018/12/15/codemirror-markdown-editor-chapter1/</link>
<guid>https://flyrk.github.io/2018/12/15/codemirror-markdown-editor-chapter1/</guid>
<pubDate>Sat, 15 Dec 2018 14:23:57 GMT</pubDate>
<description><p>我们知道,要实现一个Markdown编辑器,一般会想要实现这样的功能:实时预览、同步滚动、支持标签按钮添加、markdown语法高亮等等。那么如何去实现一个功能比较完整的markdown编辑器呢?</p>
<p>首先这里我们只讨论web端,其实现在市面上的Markdown编辑器已经很优秀了,有道笔记、印象笔记、Cmd等等,很多都可以拿来直接用,满足基本需求足够了。但是作为前端程序员来说,当然想要实现一个自己定制化的markdown编辑器比较有意思,不过通过查阅资料文档和各种框架比较,发现也不是那么容易的事。</p></description>
<content:encoded><![CDATA[<p>我们知道,要实现一个Markdown编辑器,一般会想要实现这样的功能:实时预览、同步滚动、支持标签按钮添加、markdown语法高亮等等。那么如何去实现一个功能比较完整的markdown编辑器呢?</p><p>首先这里我们只讨论web端,其实现在市面上的Markdown编辑器已经很优秀了,有道笔记、印象笔记、Cmd等等,很多都可以拿来直接用,满足基本需求足够了。但是作为前端程序员来说,当然想要实现一个自己定制化的markdown编辑器比较有意思,不过通过查阅资料文档和各种框架比较,发现也不是那么容易的事。</p><span id="more"></span><h1 id="框架选择"><a href="#框架选择" class="headerlink" title="框架选择"></a>框架选择</h1><p>要实现一个markdown编辑器,首先得找到一个合适的框架,目前市面上优秀的markdown框架五花八门。由于现在自己写的项目一般都使用React,所以理所当然能找到一个比较契合React的Markdown框架是更好的。</p><p>经过前期调研,发现有几款基于React的Markdown框架还不错,<a href="https://zenoamaro.github.io/react-quill/">react-quill</a>、facebook的<a href="https://draftjs.org/docs/getting-started.html">draft</a>、<a href="https://github.com/scniro/react-codemirror2">react-codemirror2</a>,但它们其实都是富文本编辑器,定制的东西已经很完善了,我想要的框架是可支持自定义扩展,定制化自己想要的功能,找来找去,最后决定基于<a href="https://codemirror.net/doc/manual.html#api">CodeMirror</a>来自己实现一个定制化的markdown编辑器。因为CodeMirror其实相当于一个基础的框架,它有很多可配置项,可以自己添加工具栏,添加各种实用功能,这正好满足了我的需求,于是就决定用它了!</p><h1 id="CodeMirror"><a href="#CodeMirror" class="headerlink" title="CodeMirror"></a>CodeMirror</h1><p>现代很多编辑器其实都基于<code>codemirror</code>,为什么要选择它呢?这都得益于它强大的API和配置项,在不失基本功能的同时给予了开发者极大的定制空间,开发者可以基于它开发出各种功能的markdown编辑器。这里我先简单介绍一下codemirror的一些基本操作。想要了解更多有关codemirror,可以去看<a href="https://codemirror.net/doc/manual.html">官方文档</a>。</p><h2 id="如何加载codemirror"><a href="#如何加载codemirror" class="headerlink" title="如何加载codemirror"></a>如何加载codemirror</h2><p>一般我们在项目里npm下载完codemirror包后,在代码里引入<code>codemirror.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="variable constant_">CM</span> <span class="keyword">from</span> <span class="string">'codemirror'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">'codemirror/mode/xml/xml'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'codemirror/mode/markdown/markdown'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'codemirror/addon/edit/continuelist'</span>;</span><br></pre></td></tr></table></figure><p>当然,我们还得在html里引入<code>codemirror.css</code>样式,也可以自己修改成想要的样式。</p><p>然后,在需要加载的地方写html标签,id为<code>codemirror</code>,这里我们用的是JSX:</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></pre></td><td class="code"><pre><span class="line"><div className=<span class="string">"editor-root"</span> ref={<span class="function">(<span class="params">elem</span>) =></span> { <span class="variable language_">this</span>.<span class="property">editRoot</span> = elem; }}></span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"codemirror"</span> <span class="attr">name</span>=<span class="string">{this.props.path}</span> <span class="attr">autoComplete</span>=<span class="string">"off"</span> /></span></span></span><br><span class="line"></div></span><br></pre></td></tr></table></figure><p>我在<code>textarea</code>外面用div包了一层,是为了方便之后的滚动效果。这样,我们就已经可以使用codemirror写一个markdown编辑器了。</p><h2 id="options-可以使用的参数"><a href="#options-可以使用的参数" class="headerlink" title="options 可以使用的参数"></a>options 可以使用的参数</h2><p> CodeMirror函数和它的fromTextArea方法都可以使用一个配置对象作为第二个参数。这个options极为重要,基本就决定了你的编辑器主要的输入语言、样式、内容。这里列一下它几个常用的可选的参数:</p><ul><li>value: string | CodeMirror.Doc<br>编辑器的初始值(文本),可以是字符串或者CodeMirror文档对象(不同于HTML文档对象)。</li><li>mode: string | object<br>通用的或者在CodeMirror中使用的与mode相关联的mime,当不设置这个值的时候,会默认使用第一个载入的mode定义文件。一般地,会使用关联的mime类型来设置这个值;除此之外,也可以使用一个带有name属性的对象来作为值(如:{name: “javascript”, json: true})。可以通过访问CodeMirror.modes和CodeMirror.mimeModes获取定义的mode和MIME。</li><li>lineSeparator: string|null<br>明确指定编辑器使用的行分割符(换行符)。默认(值为null)情况下,文档会被 CRLF(以及单独的CR, LF)分割,单独的LF会在所有的输出中用作换行符(如:getValue)。当指定了换行字符串,行就只会被指定的串分割。</li><li>theme: string<br>配置编辑器的主题样式。要使用主题,必须保证名称为 .cm-s-[name] (name是设置的theme的值)的样式是加载上了的。当然,你也可以一次加载多个主题样式,使用方法和html和使用类一样,如: theme: foo bar,那么此时需要cm-s-foo cm-s-bar这两个样式都已经被加载上了。</li><li>indentUnit: integer<br>缩进单位,值为空格数,默认为2 。</li><li>smartIndent: boolean<br>自动缩进,设置是否根据上下文自动缩进(和上一行相同的缩进量)。默认为true。</li><li>tabSize: integer<br>tab字符的宽度,默认为4 。</li><li>indentWithTabs: boolean<br>在缩进时,是否需要把 n*tab宽度个空格替换成n个tab字符,默认为false 。</li><li>electricChars: boolean<br>在输入可能改变当前的缩进时,是否重新缩进,默认为true (仅在mode支持缩进时有效)。</li><li>keyMap: string<br>配置快捷键。默认值为default,即 codemorrir.js 内部定义。其它在key map目录下。</li><li>extraKeys: object<br>给编辑器绑定与前面keyMap配置不同的快捷键。</li><li>lineWrapping: boolean<br>在长行时文字是换行(wrap)还是滚动(scroll),默认为滚动(scroll)。</li><li>lineNumbers: boolean<br>是否在编辑器左侧显示行号。</li><li>firstLineNumber: integer<br>行号从哪个数开始计数,默认为1 。</li><li>lineNumberFormatter: function(line: integer) → string<br>使用一个函数设置行号。</li><li>scrollbarStyle: string<br>设置滚动条。默认为”native”,显示原生的滚动条。核心库还提供了”null”样式,此样式会完全隐藏滚动条。Addons可以设置更多的滚动条模式。</li><li>inputStyle: string<br>选择CodeMirror处理输入和焦点的方式。核心库定义了textarea和contenteditable输入模式。在移动浏览器上,默认是contenteditable,在桌面浏览器上,默认是textarea。在contenteditable模式下对IME和屏幕阅读器支持更好。</li><li>readOnly: boolean|string<br>编辑器是否只读。如果设置为预设的值 “nocursor”,那么除了设置只读外,编辑区域还不能获得焦点。</li><li>showCursorWhenSelecting: boolean<br>在选择时是否显示光标,默认为false。</li><li>autofocus: boolean<br>是否在初始化时自动获取焦点。默认情况是关闭的。但是,在使用textarea并且没有明确指定值的时候会被自动设置为true。</li></ul><p>更多的配置请查看<a href="https://codemirror.net/doc/manual.html#config">相关文档</a>。</p><h1 id="实时预览"><a href="#实时预览" class="headerlink" title="实时预览"></a>实时预览</h1><p>这个需求可以说是现代markdown编辑器的基本需求,因为markdown写起来本身是没有任何样式的,纯文本的话对于有些人来说不能看到最终效果就很不习惯,自己写的效果怎么样都不知道,写的心里就会很没底(当然再花哨的样式最后还是要看内容好不好)。说白了,实时预览更多是写作时一个心理上的满足,这对于写作体验的提升来说是必不可少的。</p><p>这里的markdown渲染引擎我直接用了比较受欢迎的<a href="https://marked.js.org/#/README.md#README.md"><code>marked</code></a>库,其实markdown渲染是很考验技术的一门活,绝大部分都是用正则匹配来进行标签的替换,但由于markdown的语法相对还是较多的,要自己写一个markdown渲染引擎还是有点难度的,考虑到时间因素所以一般都会用现成的库,当然以后有时间的话我也会考虑写一个markdown渲染引擎玩玩。</p><p>这里我因为是用<code>React</code>写的markdown组件,下面我就简单的介绍下如何实现实时预览。</p><p>首先在组件渲染完后,我们在<code>componentDidMount</code>函数里对codemirror进行事件监听:</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">componentDidMount () {</span><br><span class="line"> <span class="comment">// 因为需要使用fromTextArea获取options,所以采用document.getElementById</span></span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">codeMirror</span> = <span class="variable constant_">CM</span>.<span class="title function_">fromTextArea</span>(<span class="title class_">ReactDOM</span>.<span class="title function_">findDOMNode</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'codemirror'</span>)), <span class="variable language_">this</span>.<span class="title function_">getOptions</span>());</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">init</span>(<span class="variable language_">this</span>.<span class="property">editRoot</span>, <span class="variable language_">this</span>.<span class="property">codeMirror</span>);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">codeMirror</span>.<span class="title function_">setValue</span>(<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">defaultValue</span>);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">codeMirror</span>.<span class="title function_">on</span>(<span class="string">'change'</span>, <span class="variable language_">this</span>.<span class="property">codemirrorValueChanged</span>);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">_currentCodemirrorValue</span> = <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">defaultValue</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们使用<code>fromTextArea</code>获取codemirror的dom节点,它支持两个参数,第一个是codemirror所挂载的dom节点,第二个是配置的options。</p><p>之后调用init函数,这里之后会讲。然后就是初始值设置<code>codemirror.setValue(defaultValue)</code>,因为是编辑器,如果想对之前的文档进行修改,肯定需要传入初始值。</p><p>然后就是关键的监听事件<code>change</code>,当输入变化时调用<code>this.codemirrorValueChanged</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">codemirrorValueChanged = <span class="function">(<span class="params">cm</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> newValue = cm.<span class="title function_">getValue</span>();</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">_currentCodemirrorValue</span> = cm.<span class="title function_">getValue</span>();</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">onChange</span> && <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">onChange</span>(newValue);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里说下,我们通过<code>cm.getValue()</code>获取当前输入的值,然后通过调用<code>props.onChange(newValue)</code>将值抛给父组件处理。因为这里我想把编辑部分和预览部分分开来做成两个公共组件,这样组件复用性更强,有的可能只需要编辑功能,有的只需要预览展示功能。<br>那么在父组件里,我们需要对<code>onChange</code>事件传入的值进行处理,将其渲染成html然后展示出来,其实很简单:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MarkdownEditor</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"></span><br><span class="line"> handleEditChange = <span class="function">(<span class="params">newCode</span>) =></span> {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">renderMarkdown</span> && <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">renderMarkdown</span>(newCode);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">setState</span>({</span><br><span class="line"> <span class="attr">code</span>: newCode,</span><br><span class="line"> <span class="attr">hasChanged</span>: <span class="literal">true</span></span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> debounceEditChange = <span class="title function_">debounce</span>(<span class="variable language_">this</span>.<span class="property">handleEditChange</span>, <span class="number">500</span>);</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">const</span> preview = <span class="title function_">marked</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">code</span>);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">MyEditor</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml"> <span class="attr">defaultValue</span>=<span class="string">{this.state.code}</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml"> <span class="attr">onChange</span>=<span class="string">{this.debounceEditChange}</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml"> /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">div</span> <span class="attr">className</span>=<span class="string">"md-preview-container"</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">div</span> <span class="attr">className</span>=<span class="string">"preview-content"</span> <span class="attr">dangerouslySetInnerHTML</span>=<span class="string">{{__html:</span> <span class="attr">preview</span>}}/></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里要注意的是,我用到了<code>debounce</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">debounce</span> (fn, delay = <span class="number">20</span>) {</span><br><span class="line"> <span class="keyword">let</span> timeout;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params">...args</span>) {</span><br><span class="line"> <span class="keyword">const</span> self = <span class="variable language_">this</span>;</span><br><span class="line"> <span class="keyword">if</span> (timeout) {</span><br><span class="line"> <span class="built_in">clearTimeout</span>(timeout);</span><br><span class="line"> }</span><br><span class="line"> timeout = <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> fn.<span class="title function_">call</span>(self, ...args);</span><br><span class="line"> }, delay);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为考虑到性能原因,如果输入过快的话,<code>change</code>事件频繁触发,预览页面不断重绘,性能损耗十分严重,所以我用到了<code>debounce</code>,并设置延迟时间500毫秒,当然可以更短,这里我觉得设置500毫秒已经足够了。这样就达到了实时预览的效果。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>基于codemirror的markdown编辑器实现起来其实还有很多坑,本篇先介绍了<code>codemirror</code>的一些基本配置和如何实现markdown实时预览,接下来我将进一步介绍如何实现同屏滚动和自定义标签按钮等其他功能。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/%E7%BC%96%E8%BE%91%E5%99%A8/">编辑器</category>
<category domain="https://flyrk.github.io/tags/markdown/">markdown</category>
<comments>https://flyrk.github.io/2018/12/15/codemirror-markdown-editor-chapter1/#disqus_thread</comments>
</item>
<item>
<title>点击按钮粘贴所选内容到剪贴板</title>
<link>https://flyrk.github.io/2018/12/04/copy-to-clipboard/</link>
<guid>https://flyrk.github.io/2018/12/04/copy-to-clipboard/</guid>
<pubDate>Tue, 04 Dec 2018 13:13:09 GMT</pubDate>
<description><p>我们经常可以看到这样的功能,点击某个按钮可以自动复制想要的内容到剪贴板。比如说某个图片、视频的地址,某个输入的内容,我们有的时候懒得去用键盘鼠标选中某个区域,希望只用点击一下,就能复制想要的内容。</p></description>
<content:encoded><![CDATA[<p>我们经常可以看到这样的功能,点击某个按钮可以自动复制想要的内容到剪贴板。比如说某个图片、视频的地址,某个输入的内容,我们有的时候懒得去用键盘鼠标选中某个区域,希望只用点击一下,就能复制想要的内容。</p><span id="more"></span><h1 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h1><p>其实这个用原生JS实现难度不大,主要用到的是<code>document.execCommand()</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><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">function</span> <span class="title function_">copy</span>(<span class="params">e</span>) {</span><br><span class="line"> <span class="keyword">let</span> transfer = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">'input'</span>);</span><br><span class="line"> <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(transfer);</span><br><span class="line"> transfer.<span class="property">value</span> = target.<span class="property">value</span>; <span class="comment">// 这里表示想要复制的内容</span></span><br><span class="line"> transfer.<span class="title function_">focus</span>();</span><br><span class="line"> transfer.<span class="title function_">select</span>();</span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="title function_">execCommand</span>(<span class="string">'copy'</span>)) {</span><br><span class="line"> <span class="variable language_">document</span>.<span class="title function_">execCommand</span>(<span class="string">'copy'</span>);</span><br><span class="line"> }</span><br><span class="line"> transfer.<span class="title function_">blur</span>();</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'复制成功'</span>);</span><br><span class="line"> <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">removeChild</span>(transfer);</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$(<span class="string">'#copyBtn'</span>).<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, copy);</span><br></pre></td></tr></table></figure><p>这里我们其实就是新创建了一个<code>input</code>DOM元素,然后选中该元素,把要复制的内容赋给<code>input.value</code>,这个时候执行<code>document.execCommand('copy')</code>,会把当前页面所有选中的内容复制到剪贴板,也就实现了复制的操作,最后再把新建的DOM元素移除,在不影响DOM树的情况下达到复制的目的。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p><code>document.execCommand()</code> 方法可以使当前选中的可编辑内容实现一些常用的操作,如copy、cut、paste、delete、contentReadOnly等等,具体请看<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand">document.execCommand</a>,但个人感觉一般最好不要去用里面的一些操作,因为这样可能会和用户的交互操作产生冲突,发生意想不到的结果。但是<code>document.execCommand</code>在富文本编辑器里又是个神器,值得之后慢慢研究。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/JS%E6%8A%80%E5%B7%A7/">JS技巧</category>
<comments>https://flyrk.github.io/2018/12/04/copy-to-clipboard/#disqus_thread</comments>
</item>
<item>
<title>纯CSS绘制不同的图形</title>
<link>https://flyrk.github.io/2018/12/03/draw-different-shapes-with-css/</link>
<guid>https://flyrk.github.io/2018/12/03/draw-different-shapes-with-css/</guid>
<pubDate>Mon, 03 Dec 2018 12:44:19 GMT</pubDate>
<description><p>为什么要用CSS绘制图形?我们知道,一般表示图案可以用<code>img</code>标签,直接用切好的图片,或者用<code>background</code>加载背景图。但是,当我们遇到一些小图标,比如说三角箭头、半圆、对话框、圆形等等。这些简单的图案其实可以完全使用CSS来生成,这样可以极大的减少图片资源的请求和加载,从而使网页加载速度更快。</p></description>
<content:encoded><![CDATA[<p>为什么要用CSS绘制图形?我们知道,一般表示图案可以用<code>img</code>标签,直接用切好的图片,或者用<code>background</code>加载背景图。但是,当我们遇到一些小图标,比如说三角箭头、半圆、对话框、圆形等等。这些简单的图案其实可以完全使用CSS来生成,这样可以极大的减少图片资源的请求和加载,从而使网页加载速度更快。</p><span id="more"></span><p>这里我列举一些比较感兴趣的图形。</p><h1 id="基本图案"><a href="#基本图案" class="headerlink" title="基本图案"></a>基本图案</h1><h2 id="圆形"><a href="#圆形" class="headerlink" title="圆形"></a>圆形</h2><p>圆形可以说是除了方形以外最容易表达的图形了,多亏了CSS3的<code>border-radius</code>属性:</p><h3 id="基本圆形"><a href="#基本圆形" class="headerlink" title="基本圆形"></a>基本圆形</h3><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/circle.jpg" alt="circle"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.circle</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">background-color</span>: red;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#000</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="椭圆形"><a href="#椭圆形" class="headerlink" title="椭圆形"></a>椭圆形</h3><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/oval.jpg" alt="oval"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.oval</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">background-color</span>: red;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">50px</span> / <span class="number">100px</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="三角形"><a href="#三角形" class="headerlink" title="三角形"></a>三角形</h2><p>三角形其实利用的是<code>border</code>属性,利用不同方向的<code>border</code>宽度和颜色可以展现不同的三角形。</p><h3 id="triangle-top:"><a href="#triangle-top:" class="headerlink" title="triangle-top:"></a>triangle-top:</h3><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/triangle-top.jpg" alt="triangle-top"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.top-triangle</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">100px</span> solid red;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="triangle-bottom"><a href="#triangle-bottom" class="headerlink" title="triangle-bottom"></a>triangle-bottom</h3><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/triangle-bottom.jpg" alt="triangle-bottom"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.bottom-triangle</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">100px</span> solid red;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="triangle-left"><a href="#triangle-left" class="headerlink" title="triangle-left"></a>triangle-left</h3><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/triangle-left.jpg" alt="triangle-left"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.left-triangle</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">100px</span> solid red;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="triangle-right"><a href="#triangle-right" class="headerlink" title="triangle-right"></a>triangle-right</h3><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/triangle-right.jpg" alt="triangle-right"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.right-triangle</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">100px</span> solid red;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其他的三角形也就是利用border属性进行的变形。</p><h1 id="特殊图案"><a href="#特殊图案" class="headerlink" title="特殊图案"></a>特殊图案</h1><h2 id="跳转图标"><a href="#跳转图标" class="headerlink" title="跳转图标"></a>跳转图标</h2><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/border-top-left-radius.png" alt="border-top-left-radius"></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><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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.curvedarrow</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">26px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">26px</span> solid red;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">10deg</span>);</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.curvedarrow</span><span class="selector-pseudo">:after</span> {</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">0</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">6px</span> solid red;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">20px</span> <span class="number">0</span> <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">top</span>: -<span class="number">26px</span>;</span><br><span class="line"> <span class="attribute">left</span>: -<span class="number">16px</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">30px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">30px</span>;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">45deg</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里主要利用的属性有两个,一个是<code>border-radius: 20px 0 0 0;</code>。为什么要这样用呢?其实<code>border-radius</code>最多支持四个值:<code>border-top-left-radius</code>, <code>border-top-right-radius</code>,<code>border-bottom-right-radius</code>, and <code>border-bottom-left-radius</code>。这里我们把<code>border-top-left-radius</code>设为20px,其他的都设为0,再加上只设<code>border-top</code>的宽度,最后出来是一个尾部有弧度的长条。</p><p>第二个属性则是<code>rotate(deg)</code>,将两个图形经过不同的角度旋转后再移动长条的位置进行组合,最后达到跳转箭头的图标效果。</p><h2 id="等腰梯形"><a href="#等腰梯形" class="headerlink" title="等腰梯形"></a>等腰梯形</h2><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/trapezoid.png" alt="trapezoid"><br>梯形其实很好理解,通过设置border的宽度和透明度就可以形成梯形:</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.trapezoid</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">30px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">30px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">100px</span> solid red;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="平行四边形"><a href="#平行四边形" class="headerlink" title="平行四边形"></a>平行四边形</h2><p><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/parallel.png" alt="parallel"><br>很简单,直接利用<code>transform: skew(deg)</code>就可以得到任意角度的平行四边形:</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.parallel</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">50px</span>;</span><br><span class="line"> <span class="attribute">background</span>: red;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">skew</span>(<span class="number">30deg</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="五角星"><a href="#五角星" class="headerlink" title="五角星"></a>五角星</h2><p>其原理是利用三个三角形通过旋转不同的角度和绝对定位拼接而成。<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/star-five.png" alt="star-five"><br>具体代码如下:</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><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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.star-five</span> {</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">50px</span> <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">color</span>: red;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0px</span>;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">100px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">70px</span> solid red;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">100px</span> solid transparent;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">35deg</span>);</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.star-five</span><span class="selector-pseudo">:before</span> {</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">80px</span> solid red;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">30px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">30px</span> solid transparent;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">top</span>: -<span class="number">45px</span>;</span><br><span class="line"> <span class="attribute">left</span>: -<span class="number">65px</span>;</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">rotate</span>(-<span class="number">35deg</span>);</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.star-five</span><span class="selector-pseudo">:after</span> {</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">color</span>: red;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">3px</span>;</span><br><span class="line"> <span class="attribute">left</span>: -<span class="number">105px</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0px</span>;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">100px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">70px</span> solid red;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">100px</span> solid transparent;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">rotate</span>(-<span class="number">70deg</span>);</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">''</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="六角星"><a href="#六角星" class="headerlink" title="六角星"></a>六角星</h2><p>利用两个三角形拼接而成。<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/star-six.png" alt="star-six"></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><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="selector-class">.star-six</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">100px</span> solid red;</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.star-six</span><span class="selector-pseudo">:after</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border-left</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">50px</span> solid transparent;</span><br><span class="line"> <span class="attribute">border-top</span>: <span class="number">100px</span> solid red;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">30px</span>;</span><br><span class="line"> <span class="attribute">left</span>: -<span class="number">50px</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="心形"><a href="#心形" class="headerlink" title="心形"></a>心形</h2><p>利用两个半圆长条形状旋转一定角度后拼装成心形。<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/heart.png" alt="heart"></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><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="selector-class">.heart</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">90px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.heart</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.heart</span><span class="selector-pseudo">:after</span> {</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">50px</span>;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">80px</span>;</span><br><span class="line"> <span class="attribute">background</span>: red;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">50px</span> <span class="number">50px</span> <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">rotate</span>(-<span class="number">45deg</span>);</span><br><span class="line"> <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.heart</span><span class="selector-pseudo">:after</span> {</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">45deg</span>);</span><br><span class="line"> <span class="attribute">transform-origin</span>: <span class="number">100%</span> <span class="number">100%</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="月亮"><a href="#月亮" class="headerlink" title="月亮"></a>月亮</h2><p>非常简单,利用了<code>box-shadow</code>属性,<code>box-shadow</code>一般支持四个值:<code>offset-x</code>、<code>offset-y</code>、<code>blur-radius</code>、<code>spread-radius</code>、<code>color</code>。还有一个值<code>inset</code>表示shadow是否内嵌。这里我们通过设置<code>border-radius</code>使其为圆形然后令<code>box-shadow</code>便宜一定值得到月亮形状。<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/moon.png" alt="moon"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.moon</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span><br><span class="line"> <span class="attribute">box-shadow</span>: <span class="number">15px</span> <span class="number">15px</span> <span class="number">0</span> <span class="number">0</span> red;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="阴阳图案"><a href="#阴阳图案" class="headerlink" title="阴阳图案"></a>阴阳图案</h2><p>刚一看到这个图案感觉很神奇,不知道怎么实现的,但其实很简单,分三步:</p><p>首先,利用<code>border-width</code>画出一个一半深、一半浅的圆形:<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/yin-yang1.png" alt="yin-yang1"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.yin-yang</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">96px</span>;</span><br><span class="line"> <span class="attribute">box-sizing</span>: content-box;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">48px</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#eee</span>;</span><br><span class="line"> <span class="attribute">border-color</span>: black;</span><br><span class="line"> <span class="attribute">border-style</span>: solid;</span><br><span class="line"> <span class="attribute">border-width</span>: <span class="number">2px</span> <span class="number">2px</span> <span class="number">50px</span> <span class="number">2px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后,利用伪元素的<code>content</code>和<code>border</code>,生成一个小铜钱然后定位到适合的位置:<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/yin-yang2.png" alt="yin-yang2"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.yin-yang</span><span class="selector-pseudo">:before</span> {</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">50%</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#eee</span>;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">18px</span> solid black;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">12px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">12px</span>;</span><br><span class="line"> <span class="attribute">box-sizing</span>: content-box;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最后,在对称的位置上在生成一个小铜钱,颜色与之前的相反。<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/yin-yang3.png" alt="yin-yang3"></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.yin-yang</span><span class="selector-pseudo">:after</span> {</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">50%</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">50%</span>;</span><br><span class="line"> <span class="attribute">background</span>: black;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">18px</span> solid <span class="number">#eee</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">12px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">12px</span>;</span><br><span class="line"> <span class="attribute">box-sizing</span>: content-box;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>两个一拼接,就形成了想要的阴阳图案:<br><img src="https://photos-1258216033.cos.ap-shanghai.myqcloud.com/yin-yang.png" alt="yin-yang"></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>利用CSS,我们可以实现很多简单甚至是较复杂的图案,这样省去了切图的烦恼,还减少了http请求,资源加载速度变快,唯一不足的可能就是基本上都是用的CSS3属性,有兼容性问题。但是,既然能直接用CSS画出图案,当然是用起来!</p><blockquote><p>更多图案参见<a href="https://css-tricks.com/the-shapes-of-css/">参考资料</a></p></blockquote>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/CSS%E7%9B%B8%E5%85%B3/">CSS相关</category>
<category domain="https://flyrk.github.io/tags/CSS%E6%8A%80%E5%B7%A7/">CSS技巧</category>
<comments>https://flyrk.github.io/2018/12/03/draw-different-shapes-with-css/#disqus_thread</comments>
</item>
<item>
<title>什么是堆排序</title>
<link>https://flyrk.github.io/2017/09/05/what-is-heapSort/</link>
<guid>https://flyrk.github.io/2017/09/05/what-is-heapSort/</guid>
<pubDate>Tue, 05 Sep 2017 02:02:06 GMT</pubDate>
<description><p>堆排序是一种常见的排序算法,时间复杂度是O(nlgn),与归并排序一样,但它又与插入排序一样具有<em>空间原址性</em> :任何时候都只需要常数个额外的元素空间存储临时数据。</p></description>
<content:encoded><![CDATA[<p>堆排序是一种常见的排序算法,时间复杂度是O(nlgn),与归并排序一样,但它又与插入排序一样具有<em>空间原址性</em> :任何时候都只需要常数个额外的元素空间存储临时数据。</p><span id="more"></span><h1 id="什么是堆?"><a href="#什么是堆?" class="headerlink" title="什么是堆?"></a>什么是堆?</h1><p> 一般堆用数组存储,表现出近似完全二叉树形式,树上的每一个结点对应数组中的一个元素。除了最底层外,该树是完全充满的且从左至右填充。</p><h1 id="最大堆和最小堆"><a href="#最大堆和最小堆" class="headerlink" title="最大堆和最小堆"></a>最大堆和最小堆</h1><ul><li>最大堆:除了根以外的所有节点i都要满足A[parent(i)]>=A[i],即堆中最大元素是根节点。</li><li>最小堆:除了根以外的所有节点i都要满足A[parent(i)]<=A[i],即堆中最小元素是根节点。</li></ul><h1 id="堆中节点的高度"><a href="#堆中节点的高度" class="headerlink" title="堆中节点的高度"></a>堆中节点的高度</h1><p> 与二叉树的高度相同,定义为该节点到叶节点最长简单路径上边的数目。则包含n个元素的堆其高度为lgn。</p><h1 id="维护堆的性质与方法(数组下标都从1开始)"><a href="#维护堆的性质与方法(数组下标都从1开始)" class="headerlink" title="维护堆的性质与方法(数组下标都从1开始)"></a>维护堆的性质与方法(数组下标都从1开始)</h1><h2 id="maxHeapify"><a href="#maxHeapify" class="headerlink" title="maxHeapify"></a>maxHeapify</h2><p> 时间复杂度为O(lgn)。通过让A[i]的值在最大堆中“逐级下降”,从而使得以下标i为根节点的子树重新遵循最大堆性质。代码如下:<br> <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><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">maxHeapify</span>(<span class="params">arr, i</span>) {</span><br><span class="line"> <span class="keyword">let</span> largest;</span><br><span class="line"> <span class="keyword">let</span> left = i * <span class="number">2</span>; <span class="comment">// leftChild</span></span><br><span class="line"> <span class="keyword">let</span> right = i * <span class="number">2</span> + <span class="number">1</span>; <span class="comment">// rightChild</span></span><br><span class="line"> <span class="keyword">if</span>( left <= arr.<span class="property">length</span> && arr[i] < arr[left] ) {</span><br><span class="line"> largest = left; </span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> largest = i;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span>( right <= arr.<span class="property">length</span> && arr[largest] < arr[right] ) {</span><br><span class="line"> largest = right;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( largest !== i ) { <span class="comment">// 把左右子节点中最大的元素与当前节点i交换</span></span><br><span class="line"> arr[i] = arr[i] + arr[largest];</span><br><span class="line"> arr[largest] = arr[i] - arr[largest];</span><br><span class="line"> arr[i] = arr[i] - arr[largest];</span><br><span class="line"> <span class="title function_">maxHeapify</span>(arr, largest);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="buildMaxHeap"><a href="#buildMaxHeap" class="headerlink" title="buildMaxHeap"></a>buildMaxHeap</h2><p> 时间复杂度为O(n)。用<em>自底向上</em>的方法利用maxHeapify把大小为n的数组转换为最大堆。因为最后一个叶节点序号为n,则其父节点序号为n/2,所以子数组[n/2+1,….,n]都是堆的叶节点,所以循环从n/2开始递减到1,每一次都保证节点i+1,i+2…,n都是一个最大堆的根节点的性质。<br> <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">function</span> <span class="title function_">buildMaxHeap</span>(<span class="params">arr</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = arr.<span class="property">length</span> / <span class="number">2</span>; i >= <span class="number">1</span>; i--) {</span><br><span class="line"> <span class="title function_">maxHeapify</span>(arr,i);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="heapSort-堆排序算法。"><a href="#heapSort-堆排序算法。" class="headerlink" title="heapSort:堆排序算法。"></a>heapSort:堆排序算法。</h1><p> 有了上述两个函数方法,我们就可以实现堆排序。</p><p> 先将数组arr建为一个最大堆,因为最大堆的根节点总是最大的,通过把它与arr[n]互换可以得到正确位置,保证arr[n]总是当前堆中最大元素,然后将arr[n]存储到新的数组中。可以算出时间复杂度为O(nlgn)。<br> <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"><span class="keyword">function</span> <span class="title function_">heapSort</span>(<span class="params">arr</span>) {</span><br><span class="line"> <span class="keyword">let</span> arrSort = [];</span><br><span class="line"> <span class="title function_">buildMaxHeap</span>(arr); <span class="comment">// 先建一个最大堆</span></span><br><span class="line"> <span class="keyword">let</span> length = arr.<span class="property">length</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = length; i >= <span class="number">2</span>; i--) {</span><br><span class="line"> arr[<span class="number">1</span>] = arr[i];</span><br><span class="line"> arrSort.<span class="title function_">push</span>(arr[i]);</span><br><span class="line"> <span class="title function_">maxHeapify</span>(arr, <span class="number">1</span>); <span class="comment">// 每次交换后重新维护最大堆,复杂度为O(lgn)</span></span><br><span class="line"> }</span><br><span class="line"> arrSort.<span class="title function_">push</span>(arr[<span class="number">1</span>]);</span><br><span class="line"> <span class="keyword">return</span> arrSort;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p> 通过堆排序的实现,我们可以在时间复杂度为O(nlgn)的情况下对数组进行排序。而且当我们只需要找出数组中最大的几个元素,则可以用堆排序来实现,因为每次最大的元素总是当前最后一个,这样就不需要将数组全排序。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/%E7%AE%97%E6%B3%95/">算法</category>
<category domain="https://flyrk.github.io/tags/%E6%8E%92%E5%BA%8F/">排序</category>
<comments>https://flyrk.github.io/2017/09/05/what-is-heapSort/#disqus_thread</comments>
</item>
<item>
<title>用原生JS写轮播图</title>
<link>https://flyrk.github.io/2017/08/27/how-to-write-js-marquee/</link>
<guid>https://flyrk.github.io/2017/08/27/how-to-write-js-marquee/</guid>
<pubDate>Sun, 27 Aug 2017 08:53:36 GMT</pubDate>
<description><p>我们经常可以在网页上看到轮播图的效果,这是一个很常见的应用,但是,要想比较完美地实现这个功能,还是需要花点时间的。</p></description>
<content:encoded><![CDATA[<p>我们经常可以在网页上看到轮播图的效果,这是一个很常见的应用,但是,要想比较完美地实现这个功能,还是需要花点时间的。</p><span id="more"></span><h1 id="要实现的功能"><a href="#要实现的功能" class="headerlink" title="要实现的功能"></a>要实现的功能</h1><p>首先我们来看看要实现这么一个轮播图需要哪些功能,这里我把我想到的都列出来了。</p><ol><li>页面加载后轮播图自动开始播放,每张图片停几秒钟。</li><li>图片与图片之间实现平滑过渡动画效果,不显突兀。</li><li>鼠标悬停到当前图片时轮播动画停止,鼠标离开图片后继续开始轮播。</li><li>图片上有左右翻页功能按钮,点击左边按钮图片往左滑动,点击右边按钮图片右滑。</li><li>图片下端有显示图片个数的小圆点,当前图片是第几个,则第几个小圆点“点亮”。</li><li>离开当前页面后轮播动画停止,回到当前页面后轮播动画继续。</li></ol><p>我能想到的要实现的功能就这么多,接下来就一步步开始实现。</p><h1 id="HTMl结构"><a href="#HTMl结构" class="headerlink" title="HTMl结构"></a>HTMl结构</h1><p>先不管JS、CSS部分,我们先把整体结构定下来。这里直接贴上主体部分代码:</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"wrap"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"loop-container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"./assets/cat5.jpg"</span> <span class="attr">alt</span>=<span class="string">"5"</span> <span class="attr">class</span>=<span class="string">"loop-image"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"./assets/cat1.jpg"</span> <span class="attr">alt</span>=<span class="string">"1"</span> <span class="attr">class</span>=<span class="string">"loop-image"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"./assets/cat2.jpg"</span> <span class="attr">alt</span>=<span class="string">"2"</span> <span class="attr">class</span>=<span class="string">"loop-image"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"./assets/cat3.jpg"</span> <span class="attr">alt</span>=<span class="string">"3"</span> <span class="attr">class</span>=<span class="string">"loop-image"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"./assets/cat4.jpg"</span> <span class="attr">alt</span>=<span class="string">"4"</span> <span class="attr">class</span>=<span class="string">"loop-image"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"./assets/cat5.jpg"</span> <span class="attr">alt</span>=<span class="string">"5"</span> <span class="attr">class</span>=<span class="string">"loop-image"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"./assets/cat1.jpg"</span> <span class="attr">alt</span>=<span class="string">"5"</span> <span class="attr">class</span>=<span class="string">"loop-image"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"buttons"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"on"</span>></span>1<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span>></span>2<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span>></span>3<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span>></span>4<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span>></span>5<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"arrow arrow-left"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"pt"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"pt-inner"</span>></span><span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"arrow arrow-right"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"pt"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"pt-inner"</span>></span><span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>这里要强调的一点就是,虽然最后显示只有5张图片,但我插入了7个<code>img</code>标签,其中第一个和最后一张图一样,最后一个和第一个图一样。为什么要这么做呢?是因为要实现平滑的动画过渡效果,后面JS部分会提到。</p><h1 id="CSS样式"><a href="#CSS样式" class="headerlink" title="CSS样式"></a>CSS样式</h1><p>接下来就是CSS设置:</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><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><br><span class="line"><span class="selector-class">.wrap</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">600px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">500px</span>;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">50%</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">50%</span>;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">translate</span>(-<span class="number">50%</span>, -<span class="number">50%</span>);</span><br><span class="line"> <span class="attribute">overflow</span>: hidden;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.wrap</span> <span class="selector-class">.loop-container</span> {</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">left</span>: -<span class="number">600px</span>;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">700%</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">animation</span>: left .<span class="number">6s</span> ease-out;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.wrap</span><span class="selector-pseudo">:hover</span> > <span class="selector-class">.arrow</span> {</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.loop-container</span> <span class="selector-class">.loop-image</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">600px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">500px</span>;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里我没有把全部的CSS代码贴出来,完整的代码我会在最后给出。</p><p>我只说几个要注意的地方:</p><ul><li>首先外部容器要设置<code>overflow:hidden</code>,这样才能把多余的图片遮住;</li><li>其次由于img默认是inline元素,显示出来的特性是inline-block,所以img之间会有4px的空隙,即使设置了margin和padding为0也不能消除。为此我困惑了很久。。。后来上网找资料才发现解决方案。一般有几种解决方案,这里我用的是设置父元素的font-size为0,然后img的font-size设不设置根据需要,这样就可以消除空隙。更详尽的解决方案可以参考张大神的<a href="http://www.zhangxinxu.com/wordpress/2012/04/inline-block-space-remove-%E5%8E%BB%E9%99%A4%E9%97%B4%E8%B7%9D/">博客</a></li></ul><p>其他css设置就根据样式慢慢调整了。</p><h1 id="JS部分"><a href="#JS部分" class="headerlink" title="JS部分"></a>JS部分</h1><p>接下来就是重头戏,JS的实现了。我们一步一步来看。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Marquee</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">timer</span> = <span class="number">0</span>;</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">index</span> = <span class="number">0</span>; <span class="comment">// 保存当前是第几张图</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title class_">Marquee</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">animate</span> = <span class="keyword">function</span> (<span class="params">aimLeft</span>) { <span class="comment">// 具体的动画实现</span></span><br><span class="line"> <span class="keyword">var</span> curLeft = <span class="built_in">parseInt</span>(loopContainer.<span class="property">style</span>.<span class="property">left</span>) || -<span class="number">600</span>, <span class="comment">// 获取当前图片的left值</span></span><br><span class="line"> speed = (aimLeft - curLeft) / <span class="number">20</span>, <span class="comment">// 每次left移动的距离</span></span><br><span class="line"> delay = <span class="number">20</span>,</span><br><span class="line"> self = <span class="variable language_">this</span>;</span><br><span class="line"> <span class="keyword">var</span> time = <span class="built_in">setInterval</span>(<span class="keyword">function</span> (<span class="params"></span>) { <span class="comment">// 利用循环定时实现平滑移动的动画效果</span></span><br><span class="line"> curLeft += speed;</span><br><span class="line"> loopContainer.<span class="property">style</span>.<span class="property">left</span> = curLeft + <span class="string">'px'</span>;</span><br><span class="line"> <span class="keyword">if</span> (curLeft === aimLeft) { <span class="comment">// 如果移动到了下一张图片的位置,则此次移动动画结束</span></span><br><span class="line"> <span class="built_in">clearInterval</span>(time);</span><br><span class="line"> <span class="keyword">if</span> (aimLeft <= -<span class="number">3600</span>) { <span class="comment">// 特殊设置,如果是从最后一张图到第一张图,中间加一张图片实现动画过渡,当到达最后一张图后,立即设置left为第二个img的left。 </span></span><br><span class="line"> loopContainer.<span class="property">style</span>.<span class="property">left</span> = <span class="string">'-600px'</span>; <span class="comment">// 第一张图实际的left为-600px,因为html里left为0的位置是最后一张图</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (aimLeft >= <span class="number">0</span>) { <span class="comment">// 同理,当从第一张图过渡到最后一张图,先实现动画效果,当left为实际第一张图的位置0时,设置left为倒数第二个img的位置。</span></span><br><span class="line"> loopContainer.<span class="property">style</span>.<span class="property">left</span> = <span class="string">'-3000px'</span>;</span><br><span class="line"> }</span><br><span class="line"> self.<span class="title function_">showCurrentDot</span>(); <span class="comment">// 动画结束后再改变小圆点的外观</span></span><br><span class="line"> }</span><br><span class="line"> }, delay);</span><br><span class="line">};</span><br><span class="line"><span class="title class_">Marquee</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">showCurrentDot</span> = <span class="keyword">function</span> (<span class="params"></span>) { <span class="comment">// 设置代表当前图片位置的小圆点class</span></span><br><span class="line"> <span class="keyword">var</span> dots = <span class="variable language_">document</span>.<span class="title function_">getElementsByTagName</span>(<span class="string">'span'</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, len = dots.<span class="property">length</span>; i < len; i++) {</span><br><span class="line"> dots[i].<span class="property">className</span> = <span class="string">''</span>;</span><br><span class="line"> }</span><br><span class="line"> dots[<span class="variable language_">this</span>.<span class="property">index</span>].<span class="property">className</span> = <span class="string">'on'</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="title class_">Marquee</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">changePhoto</span> = <span class="keyword">function</span> (<span class="params">offset</span>) { <span class="comment">// 自动改变图片的函数</span></span><br><span class="line"> <span class="keyword">var</span> left = loopContainer.<span class="property">style</span>.<span class="property">left</span>,</span><br><span class="line"> newleft = left ? <span class="built_in">parseInt</span>(left) + offset : offset - <span class="number">600</span>; <span class="comment">// 新的位置</span></span><br><span class="line"> </span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">index</span> = offset > <span class="number">0</span> ? <span class="variable language_">this</span>.<span class="property">index</span> - <span class="number">1</span> : <span class="variable language_">this</span>.<span class="property">index</span> + <span class="number">1</span>; <span class="comment">// 判断向左还是向右滑动</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">index</span> > <span class="number">4</span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">index</span> = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">index</span> < <span class="number">0</span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">index</span> = <span class="number">4</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// console.log(left);</span></span><br><span class="line"> <span class="comment">// console.log(newleft);</span></span><br><span class="line"> <span class="comment">// console.log('------');</span></span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">animate</span>(newleft);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="title class_">Marquee</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">gotoPhoto</span> = <span class="keyword">function</span> (<span class="params">count</span>) { <span class="comment">// 跳转到第count个图片的函数</span></span><br><span class="line"> <span class="keyword">var</span> newleft = count * -<span class="number">600</span>;</span><br><span class="line"> <span class="comment">// console.log(newleft);</span></span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">index</span> = count - <span class="number">1</span>;</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">animate</span>(newleft);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> maq = <span class="keyword">new</span> <span class="title class_">Marquee</span>();</span><br></pre></td></tr></table></figure><h2 id="自动播放图片"><a href="#自动播放图片" class="headerlink" title="自动播放图片"></a>自动播放图片</h2><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><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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">'load'</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">var</span> prevBtn = <span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(<span class="string">'arrow-left'</span>)[<span class="number">0</span>],</span><br><span class="line"> nextBtn = <span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(<span class="string">'arrow-right'</span>)[<span class="number">0</span>],</span><br><span class="line"> loopContainer = <span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(<span class="string">'loop-container'</span>)[<span class="number">0</span>],</span><br><span class="line"> btns = <span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(<span class="string">'buttons'</span>)[<span class="number">0</span>],</span><br><span class="line"> wrap = <span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(<span class="string">'wrap'</span>)[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">var</span> stopFlag = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">startInterval</span>(<span class="params"></span>) { <span class="comment">// 开始循环动画</span></span><br><span class="line"> maq.<span class="property">timer</span> = <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// console.log(stopFlag);</span></span><br><span class="line"> <span class="keyword">if</span> (!stopFlag) {</span><br><span class="line"> maq.<span class="title function_">changePhoto</span>(-<span class="number">600</span>);</span><br><span class="line"> <span class="title function_">startInterval</span>();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">clearTimeout</span>(maq.<span class="property">timer</span>);</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">4500</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="title function_">startInterval</span>();</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>这里我循环动画没有用setInterval函数,而是用的setTimeout。因为setInterval的机制是每隔一段时间就把事件加入到队列中去,但如果之前的事件还没执行完,就会容易造成队列堵塞。比如说setInterval里的函数执行时间要4秒,如果setInterval的间隔时间少于4秒,则会造成队列里的事件越来越多,而之前的事件却没执行完,这样可能就会使队列里的事件堵塞,最后一次性全部执行,而没有达到预期的间隔效果。</p><p>所以我用setTimeout来代替setInterval,每次要加入新的事件之前都先判断一下<code>stopFlag</code>是否为0。<code>stopFlag</code>的作用就是记录是否要停止动画,为0则不停止,为1则停止。</p><p>这里记住要clearTimeout,目的是把已经在队列里但还没有执行的事件清除,这样可以达到立即停止动画的效果。</p><h2 id="鼠标悬停停止轮播动画,离开后开始动画"><a href="#鼠标悬停停止轮播动画,离开后开始动画" class="headerlink" title="鼠标悬停停止轮播动画,离开后开始动画"></a>鼠标悬停停止轮播动画,离开后开始动画</h2><p>监听mouseover和mouseout事件来达到目的:</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></pre></td><td class="code"><pre><span class="line">wrap.<span class="title function_">addEventListener</span>(<span class="string">'mouseover'</span>, <span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> stopFlag = <span class="number">1</span>;</span><br><span class="line"> <span class="built_in">clearTimeout</span>(maq.<span class="property">timer</span>);</span><br><span class="line">});</span><br><span class="line">wrap.<span class="title function_">addEventListener</span>(<span class="string">'mouseout'</span>, <span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> stopFlag = <span class="number">0</span>;</span><br><span class="line"> <span class="title function_">startInterval</span>();</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="左右切换图片"><a href="#左右切换图片" class="headerlink" title="左右切换图片"></a>左右切换图片</h2><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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">prevBtn.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> maq.<span class="title function_">changePhoto</span>(<span class="number">600</span>); <span class="comment">// 向左滑</span></span><br><span class="line">});</span><br><span class="line">nextBtn.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> maq.<span class="title function_">changePhoto</span>(-<span class="number">600</span>); <span class="comment">// 向右滑</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="点击小圆点跳转到对应图片"><a href="#点击小圆点跳转到对应图片" class="headerlink" title="点击小圆点跳转到对应图片"></a>点击小圆点跳转到对应图片</h2><p>这里我用了事件代理,不用在每个小圆点上绑定click事件,提高dom性能:</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></pre></td><td class="code"><pre><span class="line">btns.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span> (<span class="params">event</span>) {</span><br><span class="line"> <span class="keyword">var</span> count = <span class="built_in">parseInt</span>(event.<span class="property">target</span>.<span class="property">innerText</span>);</span><br><span class="line"> <span class="keyword">if</span> (count < <span class="number">6</span>) {</span><br><span class="line"> maq.<span class="title function_">gotoPhoto</span>(count);</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><p>这里我开始没有想到,后来是当我每次切换到别的页面后再回到当前页面,发现动画效果出现问题了。经过一番debug才发现是因为chrome浏览器设置了离开当前页面后setInterval继续执行,如果setInterval的间隔时间小于100ms,则按100ms来执行,于是回来时动画的时间就发生错误了。</p><p>所以我们需要设置离开页面时动画停止,这样也节省了不少性能。这里利用的是<code>onvisibilitychange</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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'visibilitychange'</span>, <span class="keyword">function</span> (<span class="params"></span>) { <span class="comment">// 离开当前页面后动画停止</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">hidden</span>) {</span><br><span class="line"> stopFlag = <span class="number">1</span>;</span><br><span class="line"> <span class="built_in">clearTimeout</span>(maq.<span class="property">timer</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> stopFlag = <span class="number">0</span>;</span><br><span class="line"> <span class="title function_">startInterval</span>();</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>这里可以直接调用<code>document.hidden</code>API判断当前页面是否被隐藏,如果document.hidden为true则代表已经切换到别的页面,于是设置<code>stopFlag</code>为1,使动画停止。也可以用<code>document.visibilityState</code>,如果不为’visible’,则代表离开了当前页面。</p><p>这里其实可以不用setTimeout、setInterval来实现动画,而是用<code>requestAnimationFrame</code>,后者的优点是自动以浏览器支持的最小刷新间隔来实现重绘,使性能得到最大化提升,而且实现了离开页面重绘停止,大大节省性能。这里我就没有实现了,要想了解requestAnimationFrame,可参考<a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame">资料</a>。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>通过上述方法,最后实现了一个比较完善的轮播图,全部的动画效果都是用JS来实现的。其实要想实现自动轮播,也可以用CSS3的animation来实现,而且实现起来更快,这里我就不阐述了。</p><p>最后贴出实现的<a href="https://codepen.io/flyrk/full/brxQBm/">demo</a>。</p>]]></content:encoded>
<category domain="https://flyrk.github.io/categories/JS%E7%9B%B8%E5%85%B3/">JS相关</category>
<category domain="https://flyrk.github.io/tags/JS%E6%8A%80%E5%B7%A7/">JS技巧</category>
<comments>https://flyrk.github.io/2017/08/27/how-to-write-js-marquee/#disqus_thread</comments>
</item>
</channel>
</rss>