-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
316 lines (255 loc) · 82.6 KB
/
search.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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>2021.12总结</title>
<url>/2022/01/01/202112/</url>
<content><![CDATA[<p>2021.12 月遇到的知识点总结如下:</p>
<span id="more"></span>
<h1 id="1-将-url-查询字符串转为对象"><a href="#1-将-url-查询字符串转为对象" class="headerlink" title="1.将 url 查询字符串转为对象"></a>1.将 url 查询字符串转为对象</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Object.fromEntries(new URLSearchParams('?foo=bar&baz=qux'))</span><br><span class="line">// { foo: "bar", baz: "qux" }</span><br></pre></td></tr></table></figure>
<h1 id="2-对象和-map-的互相转换"><a href="#2-对象和-map-的互相转换" class="headerlink" title="2.对象和 map 的互相转换"></a>2.对象和 map 的互相转换</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const map = new Map().set('foo', true).set('bar', false);</span><br><span class="line">Object.fromEntries(map)</span><br><span class="line">// { foo: true, bar: false }</span><br><span class="line"></span><br><span class="line">const obj = { foo: 'bar', baz: 42 };</span><br><span class="line">const map = new Map(Object.entries(obj));</span><br><span class="line">map // Map { foo: "bar", baz: 42 }</span><br></pre></td></tr></table></figure>
<h1 id="3-process-env-NODE-ENV"><a href="#3-process-env-NODE-ENV" class="headerlink" title="3.process.env.NODE_ENV"></a>3.process.env.NODE_ENV</h1><p><code>process.env.NODE_ENV</code> 是用来区分本地开发环境和打包后的环境的。<br><code>development</code> 开发环境,<code>production</code> 生产环境</p>
<h1 id="4-实例方法-vs-静态方法"><a href="#4-实例方法-vs-静态方法" class="headerlink" title="4.实例方法 vs 静态方法"></a>4.实例方法 vs 静态方法</h1><p>静态方法:类和属于类的方法可以直接调用的方法,可以用类名.方法名调用<br>实例方法:实例化类后的对象的调用方法</p>
<h1 id="5-类数组对象"><a href="#5-类数组对象" class="headerlink" title="5.类数组对象"></a>5.类数组对象</h1><p>类数组对象的本质特征只有一点,即必须具有 length 属性。</p>
<h1 id="6-arr-at"><a href="#6-arr-at" class="headerlink" title="6.arr.at()"></a>6.arr.at()</h1><p>数组不支持负索引,因为[]不仅用于数组还可以用于对象,对于对象来说,[]里面的是键名,所以数组的[]里面不能是负数。<br>新提案:<code>arr.at()</code>可以传一个负数的参数。</p>
<h1 id="7-str-padStart"><a href="#7-str-padStart" class="headerlink" title="7.str.padStart"></a>7.str.padStart</h1><p>补全字符串,可以用来补全时间格式或提醒字符串格式</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">day.padStart(2, '0')</span><br><span class="line">'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"</span><br></pre></td></tr></table></figure>
<h1 id="8-el-popover-的弹出框位置"><a href="#8-el-popover-的弹出框位置" class="headerlink" title="8.el-popover 的弹出框位置"></a>8.el-popover 的弹出框位置</h1><p>如果 el-popover 高度过高或位置在页面边缘,会导致页面出现滚动条,解决办法:<br>设置位置不合适时的替代选项 fallback-placements</p>
<h1 id="9-vue3-的-computed-传入参数"><a href="#9-vue3-的-computed-传入参数" class="headerlink" title="9.vue3 的 computed 传入参数"></a>9.vue3 的 computed 传入参数</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const x = computed(() => (val) => val + 1)</span><br></pre></td></tr></table></figure>
<h1 id="10-vue3-中声明响应式对象的-ref-和指向-dom-引用的-ref"><a href="#10-vue3-中声明响应式对象的-ref-和指向-dom-引用的-ref" class="headerlink" title="10.vue3 中声明响应式对象的 ref 和指向 dom 引用的 ref"></a>10.vue3 中声明响应式对象的 ref 和指向 dom 引用的 ref</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const ref1 = ref(null)</span><br><span class="line">const ref2 = ref('a')</span><br><span class="line"></span><br><span class="line"><div ref="ref1"></div></span><br><span class="line"></span><br><span class="line">ref2.value = 'b'</span><br></pre></td></tr></table></figure>
<h1 id="11-css-选择器之如何选择除最后一个元素外的其他元素"><a href="#11-css-选择器之如何选择除最后一个元素外的其他元素" class="headerlink" title="11.css 选择器之如何选择除最后一个元素外的其他元素"></a>11.css 选择器之如何选择除最后一个元素外的其他元素</h1><p><code>.tag:not(:last-child){}</code> // 除最后一个元素外的其他元素</p>
<p><code>.tag:not(:last-child)::after {}</code> //除最后一个元素外的其他元素的 after 伪元素</p>
<h1 id="12-v-model-参数"><a href="#12-v-model-参数" class="headerlink" title="12.v-model 参数"></a>12.v-model 参数</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><ChildComponent v-model:title="pageTitle" /></span><br><span class="line"></span><br><span class="line"><!-- 是以下的简写: --></span><br><span class="line"></span><br><span class="line"><ChildComponent :title="pageTitle" @update:title="pageTitle = $event" /></span><br></pre></td></tr></table></figure>
<h1 id="13-引入类型"><a href="#13-引入类型" class="headerlink" title="13.引入类型"></a>13.引入类型</h1><p>XXX only refers to a type, but is being used as a value here</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">import { XXX } from 'xxx';</span><br><span class="line">改为:</span><br><span class="line">import type { XXX } from 'xxx';</span><br></pre></td></tr></table></figure>
<h1 id="14-template-与-v-show"><a href="#14-template-与-v-show" class="headerlink" title="14.template 与 v-show"></a>14.template 与 v-show</h1><p><code>template上</code>不支持使用<code>v-show</code>,可以用<code>v-if</code></p>
<h1 id="15-按键修饰符-number"><a href="#15-按键修饰符-number" class="headerlink" title="15.按键修饰符.number"></a>15.按键修饰符.number</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><input v-model.number="age" /></span><br></pre></td></tr></table></figure>
<p>如果输入的是“12a”,显示的只有“12”,<br>如果输入的是“a12”,显示的还是“a12”,数字前面的字符串不能被过滤。</p>
<h1 id="16-Date-对象"><a href="#16-Date-对象" class="headerlink" title="16.Date 对象"></a>16.Date 对象</h1><p>如果要判断 x 分 y 秒是否大于 a 分 b 秒,可以先用 new Date()转换成日期类型,然后再用>运算符判断。<br>如果需要判断 x 分 y 秒是否在 a 分 b 秒和 c 分 d 秒区间内,转换成 Date 类型也很方便判断。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">new Date();</span><br><span class="line">new Date(value);</span><br><span class="line">new Date(dateString);</span><br><span class="line">new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);</span><br></pre></td></tr></table></figure>
<h1 id="17-对象转换成-url-参数字符串"><a href="#17-对象转换成-url-参数字符串" class="headerlink" title="17.对象转换成 url 参数字符串"></a>17.对象转换成 url 参数字符串</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">var params4 = new URLSearchParams({"foo" : 1 , "bar" : 2});</span><br></pre></td></tr></table></figure>
<p>其他 URLSearchParams 参数类型:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// Pass in a string literal</span><br><span class="line">var url = new URL('https://example.com?foo=1&bar=2');</span><br><span class="line">// Retrieve from window.location</span><br><span class="line">var url2 = new URL(window.location);</span><br><span class="line"></span><br><span class="line">// Retrieve params via url.search, passed into ctor</span><br><span class="line">var params = new URLSearchParams(url.search);</span><br><span class="line">var params2 = new URLSearchParams(url2.search);</span><br><span class="line"></span><br><span class="line">// Pass in a sequence</span><br><span class="line">var params3 = new URLSearchParams([["foo", 1],["bar", 2]]);</span><br><span class="line"></span><br><span class="line">// Pass in a record</span><br><span class="line">var params4 = new URLSearchParams({"foo" : 1 , "bar" : 2});</span><br></pre></td></tr></table></figure>
<h1 id="18-vue3-获取子组件-dom-结构和属性、方法"><a href="#18-vue3-获取子组件-dom-结构和属性、方法" class="headerlink" title="18.vue3 获取子组件 dom 结构和属性、方法"></a>18.vue3 获取子组件 dom 结构和属性、方法</h1><p>在子组合中需要用 defineExpose 定义暴露出的属性、方法</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 子组件</span><br><span class="line"><div ref="a"></div></span><br><span class="line">const a = ref()</span><br><span class="line">function test(){}</span><br><span class="line">defineExpose({</span><br><span class="line"> a,</span><br><span class="line"> test</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 父组件</span><br><span class="line"><son ref="b"></son></span><br><span class="line">const b = ref()</span><br><span class="line">onMounted(() => {</span><br><span class="line"> console.dir(b.value.a)</span><br><span class="line"> b.value.test()</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>canvas</tag>
</tags>
</entry>
<entry>
<title>board</title>
<url>/2022/01/01/board/</url>
<content><![CDATA[<p><code>canvas</code>的功能非常强大,不仅可以实现刮刮乐功能,还可以实现画板等功能。</p>
<span id="more"></span>
<h1 id="1-画板的功能"><a href="#1-画板的功能" class="headerlink" title="1.画板的功能"></a>1.画板的功能</h1><p>修改画笔颜色;<br>修改画笔粗细;<br>橡皮擦;<br>重置画板;<br>撤销上一步;<br>保存成图片;<br><img src="/2022/01/01/board/1.gif"></p>
<h1 id="2-所需知识"><a href="#2-所需知识" class="headerlink" title="2.所需知识"></a>2.所需知识</h1><p><code>Element.getBoundingClientRect()</code> 方法返回元素的大小及其相对于视口的位置。<br><code>ctx.moveTo(x, y)</code> 将一个新的子路径的起始点移动到(x,y)坐标<br><code>ctx.lineTo(x, y)</code> 使用直线连接子路径的终点到 x,y 坐标</p>
<h1 id="3-实现"><a href="#3-实现" class="headerlink" title="3.实现"></a>3.实现</h1><h3 id="第一步,实现基本功能,可以画出来鼠标路径;"><a href="#第一步,实现基本功能,可以画出来鼠标路径;" class="headerlink" title="第一步,实现基本功能,可以画出来鼠标路径;"></a>第一步,实现基本功能,可以画出来鼠标路径;</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><canvas id="myCanvas" width="400" height="400"></canvas></span><br><span class="line"></span><br><span class="line">class Board {</span><br><span class="line"> constructor(id) {</span><br><span class="line"> this.canvas = document.getElementById(id);</span><br><span class="line"> this.context = this.canvas.getContext('2d');</span><br><span class="line"> this.isDrawing = false;</span><br><span class="line"> this.posX = 0;</span><br><span class="line"> this.posY = 0;</span><br><span class="line"> this.init();</span><br><span class="line"> }</span><br><span class="line"> init() {</span><br><span class="line"> const bindDown = this.handleMouseDown.bind(this);</span><br><span class="line"> const bindMove = this.handleMouseMove.bind(this);</span><br><span class="line"> this.canvas.addEventListener('mousedown', bindDown);</span><br><span class="line"> this.canvas.addEventListener('mousemove', bindMove);</span><br><span class="line"> window.addEventListener('mouseup', () => {</span><br><span class="line"> this.isDrawing = false;</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> handleMouseDown(e) {</span><br><span class="line"> const rect = this.canvas.getBoundingClientRect();</span><br><span class="line"> this.posX = e.clientX - rect.left;</span><br><span class="line"> this.posY = e.clientY - rect.top;</span><br><span class="line"> this.isDrawing = true;</span><br><span class="line"> }</span><br><span class="line"> handleMouseMove(e) {</span><br><span class="line"> if (this.isDrawing === true) {</span><br><span class="line"> const rect = this.canvas.getBoundingClientRect();</span><br><span class="line"> this.drawLine(this.context, this.posX, this.posY, e.clientX - rect.left, e.clientY - rect.top);</span><br><span class="line"> this.posX = e.clientX - rect.left;</span><br><span class="line"> this.posY = e.clientY - rect.top;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> drawLine(context, x1, y1, x2, y2) {</span><br><span class="line"> context.beginPath();</span><br><span class="line"> context.strokeStyle = 'black';</span><br><span class="line"> context.lineWidth = 1;</span><br><span class="line"> context.moveTo(x1, y1);</span><br><span class="line"> context.lineTo(x2, y2);</span><br><span class="line"> context.stroke();</span><br><span class="line"> context.closePath();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> new Board('myCanvas');</span><br></pre></td></tr></table></figure>
<h3 id="第二步,可以修改画笔颜色;"><a href="#第二步,可以修改画笔颜色;" class="headerlink" title="第二步,可以修改画笔颜色;"></a>第二步,可以修改画笔颜色;</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><input id="colorPicker" type="color" /></span><br><span class="line"></span><br><span class="line">document.getElementById('colorPicker').addEventListener('change', e => {</span><br><span class="line"> b.changeColor(e.target.value);</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> class Board {</span><br><span class="line"> constructor(id, color = '#000') {</span><br><span class="line"> this.penColor = color;</span><br><span class="line"> }</span><br><span class="line"> drawLine(context, x1, y1, x2, y2) {</span><br><span class="line"> context.strokeStyle = this.penColor;</span><br><span class="line"> }</span><br><span class="line"> changeColor(color) {</span><br><span class="line"></span><br><span class="line">this.penColor = color;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="第三步,修改画笔粗细;"><a href="#第三步,修改画笔粗细;" class="headerlink" title="第三步,修改画笔粗细;"></a>第三步,修改画笔粗细;</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ctx.lineWidth = number;</span><br></pre></td></tr></table></figure>
<h3 id="第四步,橡皮擦;"><a href="#第四步,橡皮擦;" class="headerlink" title="第四步,橡皮擦;"></a>第四步,橡皮擦;</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">context.globalCompositeOperation = 'destination-out';</span><br></pre></td></tr></table></figure>
<p>参照刮刮乐功能。</p>
<h3 id="第五步,重置画板;"><a href="#第五步,重置画板;" class="headerlink" title="第五步,重置画板;"></a>第五步,重置画板;</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">context.clearRect(0, 0, width, height);</span><br></pre></td></tr></table></figure>
<h3 id="第六步,撤销上一步;"><a href="#第六步,撤销上一步;" class="headerlink" title="第六步,撤销上一步;"></a>第六步,撤销上一步;</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">this.canvas.toDataURL()</span><br></pre></td></tr></table></figure>
<p>将当前 canvas 保存为 base64 的图片,存放在数组中。再设置一个索引,撤销/恢复修改索引的值,从数组中取出对应的图片。</p>
<h3 id="第七步,保存为图片;"><a href="#第七步,保存为图片;" class="headerlink" title="第七步,保存为图片;"></a>第七步,保存为图片;</h3><p>创建一个 a 标签,href 为 toDataURL()生成的图片,模拟点击事件,点击 a 链接。</p>
<h1 id="4-完整代码"><a href="#4-完整代码" class="headerlink" title="4.完整代码"></a>4.完整代码</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><div class="opera"></span><br><span class="line"> <input id="colorPicker" type="color" /></span><br><span class="line"> <select id="fontsizeSelect"></span><br><span class="line"> <option value="1">1</option></span><br><span class="line"> <option value="2">2</option></span><br><span class="line"> <option value="3">3</option></span><br><span class="line"> <option value="4">4</option></span><br><span class="line"> <option value="5">5</option></span><br><span class="line"> </select></span><br><span class="line"> <button id="eraser">橡皮擦</button></span><br><span class="line"> <button id="reset">重置</button></span><br><span class="line"> <button id="revoke">撤销</button></span><br><span class="line"> <button id="recover">恢复</button></span><br><span class="line"> <button id="saveAsPic">保存为图片</button></span><br><span class="line"> </div></span><br><span class="line"> <canvas id="myCanvas" width="400" height="400"></canvas></span><br><span class="line"></span><br><span class="line">class Board {</span><br><span class="line"> constructor(id, color = '#000', fontsize = 1) {</span><br><span class="line"> this.canvas = document.getElementById(id);</span><br><span class="line"> this.context = this.canvas.getContext('2d');</span><br><span class="line"> this.isDrawing = false;</span><br><span class="line"> this.posX = 0;</span><br><span class="line"> this.posY = 0;</span><br><span class="line"> this.penColor = color;</span><br><span class="line"> this.fontsize = fontsize;</span><br><span class="line"> this.isErasing = false;</span><br><span class="line"> this.step = 0;</span><br><span class="line"> this.histroyList = [];</span><br><span class="line"> this.init();</span><br><span class="line"> }</span><br><span class="line"> init() {</span><br><span class="line"> const bindDown = this.handleMouseDown.bind(this);</span><br><span class="line"> const bindMove = this.handleMouseMove.bind(this);</span><br><span class="line"> this.canvas.addEventListener('mousedown', bindDown);</span><br><span class="line"> this.canvas.addEventListener('mousemove', bindMove);</span><br><span class="line"> window.addEventListener('mouseup', () => {</span><br><span class="line"> this.isDrawing = false;</span><br><span class="line"> });</span><br><span class="line"> this.canvas.addEventListener('mouseup', () => {</span><br><span class="line"> this.step++;</span><br><span class="line"> if (this.step < this.histroyList.length) {</span><br><span class="line"> this.histroyList.length = this.step;</span><br><span class="line"> }</span><br><span class="line"> this.histroyList.push(this.canvas.toDataURL());</span><br><span class="line"> });</span><br><span class="line"> this.histroyList.push(this.canvas.toDataURL());</span><br><span class="line"> }</span><br><span class="line"> handleMouseDown(e) {</span><br><span class="line"> const rect = this.canvas.getBoundingClientRect();</span><br><span class="line"> this.posX = e.clientX - rect.left;</span><br><span class="line"> this.posY = e.clientY - rect.top;</span><br><span class="line"> this.isDrawing = true;</span><br><span class="line"> }</span><br><span class="line"> handleMouseMove(e) {</span><br><span class="line"> const rect = this.canvas.getBoundingClientRect();</span><br><span class="line"> if (this.isErasing) {</span><br><span class="line"> this.context.globalCompositeOperation = 'destination-out';</span><br><span class="line"> this.context.beginPath();</span><br><span class="line"> this.context.arc(e.clientX - rect.left, e.clientY - rect.top, 10, 0, Math.PI \* 2);</span><br><span class="line"> this.context.fill();</span><br><span class="line"> } else if (this.isDrawing === true) {</span><br><span class="line"> this.drawLine(this.context, this.posX, this.posY, e.clientX - rect.left, e.clientY - rect.top);</span><br><span class="line"> this.posX = e.clientX - rect.left;</span><br><span class="line"> this.posY = e.clientY - rect.top;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> drawLine(context, x1, y1, x2, y2) {</span><br><span class="line"> context.beginPath();</span><br><span class="line"> context.strokeStyle = this.penColor;</span><br><span class="line"> context.lineWidth = this.fontsize;</span><br><span class="line"> context.moveTo(x1, y1);</span><br><span class="line"> context.lineTo(x2, y2);</span><br><span class="line"> context.stroke();</span><br><span class="line"> context.closePath();</span><br><span class="line"> }</span><br><span class="line"> changeColor(color) {</span><br><span class="line"> this.penColor = color;</span><br><span class="line"> }</span><br><span class="line"> changeFontSize(size) {</span><br><span class="line"> this.fontsize = size;</span><br><span class="line"> }</span><br><span class="line"> switchEraseStatus() {</span><br><span class="line"> this.isErasing = !this.isErasing;</span><br><span class="line"> }</span><br><span class="line"> clearBoard() {</span><br><span class="line"> this.context.clearRect(0, 0, window.myCanvas.width, window.myCanvas.height);</span><br><span class="line"> this.step = 0;</span><br><span class="line"> this.histroyList = [];</span><br><span class="line"> }</span><br><span class="line"> revoke() {</span><br><span class="line"> if (this.step > 0) {</span><br><span class="line"> this.step--;</span><br><span class="line"> this.context.clearRect(0, 0, window.myCanvas.width, window.myCanvas.height);</span><br><span class="line"> let pic = new Image();</span><br><span class="line"> pic.src = this.histroyList[this.step];</span><br><span class="line"> pic.addEventListener('load', () => {</span><br><span class="line"> this.context.drawImage(pic, 0, 0);</span><br><span class="line"> })</span><br><span class="line"> } else {</span><br><span class="line"> console.log('不能继续撤销了')</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> recover() {</span><br><span class="line"> if (this.step < this.histroyList.length - 1) {</span><br><span class="line"> this.step++;</span><br><span class="line"> this.context.clearRect(0, 0, window.myCanvas.width, window.myCanvas.height);</span><br><span class="line"> let pic = new Image();</span><br><span class="line"> pic.src = this.histroyList[this.step];</span><br><span class="line"> pic.addEventListener('load', () => {</span><br><span class="line"> this.context.drawImage(pic, 0, 0);</span><br><span class="line"> })</span><br><span class="line"> } else {</span><br><span class="line"> console.log('不能继续恢复了')</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> saveAsPic() {</span><br><span class="line"> const el = document.createElement('a');</span><br><span class="line"> el.href = this.canvas.toDataURL();</span><br><span class="line"> el.download = 'canvas';</span><br><span class="line"> const event = new MouseEvent('click');</span><br><span class="line"> el.dispatchEvent(event);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> const b = new Board('myCanvas');</span><br><span class="line"></span><br><span class="line">window.colorPicker.addEventListener('change', e => {</span><br><span class="line"> b.changeColor(e.target.value);</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line">window.fontsizeSelect.addEventListener('change', e => {</span><br><span class="line"> b.changeFontSize(window.fontsizeSelect.value);</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line">window.eraser.addEventListener('click', () => {</span><br><span class="line"> b.switchEraseStatus();</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line">window.reset.addEventListener('click', () => {</span><br><span class="line"> b.clearBoard();</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line">window.revoke.addEventListener('click', () => {</span><br><span class="line"> b.revoke();</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line">window.recover.addEventListener('click', () => {</span><br><span class="line"> b.recover();</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line">window.saveAsPic.addEventListener('click', () => {</span><br><span class="line"> b.saveAsPic();</span><br><span class="line"> })</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>canvas</tag>
</tags>
</entry>
<entry>
<title>复制粘贴相关API</title>
<url>/2021/10/28/copy/</url>
<content><![CDATA[<p>在业务中,经常遇到需要进行复制粘贴的需求。怎么自定义复制或粘贴的内容呢?怎么禁止用户复制当前页面内容呢?</p>
<span id="more"></span>
<p>可以实现复制粘贴功能的 API 有 3 个。</p>
<h3 id="document-execCommand"><a href="#document-execCommand" class="headerlink" title="document.execCommand"></a>document.execCommand</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">dom1.select();</span><br><span class="line"><span class="built_in">document</span>.execCommand(<span class="string">"copy"</span>);</span><br><span class="line"></span><br><span class="line">dom2.focus();</span><br><span class="line"><span class="built_in">document</span>.execCommand(<span class="string">"paste"</span>);</span><br></pre></td></tr></table></figure>
<p>复制前需要先选中,粘贴前需要先聚焦。<br>缺点:<br><strong>不能自定义剪贴板内容</strong>,只能复制粘贴选中的内容。<br><strong>同步操作</strong>,复制粘贴大量数据时会卡顿。<br>某些浏览器会询问用户权限。</p>
<h3 id="clipboard"><a href="#clipboard" class="headerlink" title="clipboard"></a>clipboard</h3><p>它的 4 个方法都是<strong>异步</strong> API</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">const</span> text = <span class="keyword">await</span> navigator.clipboard.readText();</span><br><span class="line"> <span class="built_in">console</span>.log(text);</span><br><span class="line">})();</span><br><span class="line"></span><br><span class="line">navigator.clipboard.readText(); <span class="comment">// 获取剪贴板中的文本数据</span></span><br><span class="line">navigator.clipboard.read(); <span class="comment">// 获取剪贴板中的数据,不仅仅是文本类型</span></span><br><span class="line">navigator.clipboard.writeText(str); <span class="comment">// 将 str 设置为剪贴板内容</span></span><br><span class="line">navigator.clipboard.write(x); <span class="comment">// 将任意类型数据写入剪贴板</span></span><br></pre></td></tr></table></figure>
<p>缺点:<br>只能在 <strong>localhost</strong> 或 <strong>https</strong> 中使用。<br>粘贴(获取剪贴板内容)时会询问用户权限。</p>
<h3 id="copy-amp-paste"><a href="#copy-amp-paste" class="headerlink" title="copy & paste"></a>copy & paste</h3><p>推荐使用这个方法</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">dom.addEventListener(<span class="string">"copy"</span>, <span class="function"><span class="params">e</span> =></span> {</span><br><span class="line"> e.preventDefault();</span><br><span class="line"> e.clipboardData.setData(type, data); <span class="comment">// 设置剪贴板数据</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">dom.addEventListener(<span class="string">"paste"</span>, <span class="function"><span class="params">e</span> =></span> {</span><br><span class="line"> e.clipboardData.getData(type); <span class="comment">// 获取剪贴板数据</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>在监控 copy 的回调函数中可以设置剪贴板数据,但是用 getData 获取不到数据。<br>所以如果想修改复制粘贴的内容,要在 paste 的回调函数中设置。<br>但是,如果是在当前页面复制,粘贴到其他页面,怎么办?需要用 range。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">str = <span class="built_in">document</span>.getSelection().toString(); <span class="comment">// 将当前选中的内容转为字符串类型</span></span><br><span class="line">str = ... <span class="comment">// 对数据进行处理</span></span><br><span class="line">e.clipboardData.setData(<span class="string">'text/plain'</span>, str); <span class="comment">// 设置剪贴板数据</span></span><br></pre></td></tr></table></figure>
<p>注:如果从页面复制内容粘贴到 excel 后,格式错误,可以先获取剪贴板内容,再手动转换成字符串,再设置到剪贴板上。<br>如果是表头格式不对,可以检查是否多了 \n 或 \t 。</p>
<h3 id="range-常用-api"><a href="#range-常用-api" class="headerlink" title="range 常用 api"></a>range 常用 api</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">range = <span class="built_in">document</span>.createRange(); <span class="comment">// 创建 range 对象</span></span><br><span class="line">range = <span class="built_in">document</span>.getSelection().getRangeAt(<span class="number">0</span>); <span class="comment">// 获取当前选中内容的 range 对象</span></span><br><span class="line"></span><br><span class="line">range.setStart(startNode, startOffset); <span class="comment">// 设置 range 的起始位置</span></span><br><span class="line">range.setEnd(endNode, endOffset); <span class="comment">// 设置 range 的结束位置</span></span><br><span class="line"></span><br><span class="line">documentFragment = range.cloneContents(); <span class="comment">// 获取 range 中所有 dom 的副本</span></span><br></pre></td></tr></table></figure>
<h3 id="禁止用户选中当前页面的内容"><a href="#禁止用户选中当前页面的内容" class="headerlink" title="禁止用户选中当前页面的内容"></a>禁止用户选中当前页面的内容</h3><p>某些网站可能有禁止用户复制当前页面内容的需求,禁止复制就是要禁止选中。<br>方法 1:通过设置 css 属性</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line">* {</span><br><span class="line"> user-select: none;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>方法 2: 设置 DOM0 级事件</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">body</span> <span class="attr">onselectstart</span>=<span class="string">"return false"</span>></span><span class="tag"></<span class="name">body</span>></span></span><br></pre></td></tr></table></figure>
<p>方法 3:设置 DOM2 级事件</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">document</span>.onselectstart = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>javascript</category>
</categories>
<tags>
<tag>原生js</tag>
<tag>事件监听</tag>
</tags>
</entry>
<entry>
<title>刮刮乐效果原来如此简单</title>
<url>/2021/12/08/erase/</url>
<content><![CDATA[<p>刮刮乐效果经常用于抽奖活动等情景,算是常用功能吧。</p>
<span id="more"></span>
<h2 id="1-刮刮乐(橡皮擦)效果的核心-api"><a href="#1-刮刮乐(橡皮擦)效果的核心-api" class="headerlink" title="1.刮刮乐(橡皮擦)效果的核心 api"></a>1.刮刮乐(橡皮擦)效果的核心 api</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ctx.globalCompositeOperation = type;</span><br></pre></td></tr></table></figure>
<p>设置要在绘制新形状时应用的合成操作的类型。<br>我们这里需要用到的类型是 <code>destination-out</code></p>
<p><img src="/2021/12/08/erase/1.png" alt="image.png"></p>
<p>此属性的详细信息:<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation">MDN 文档</a></p>
<h2 id="2-基础版刮刮乐功能"><a href="#2-基础版刮刮乐功能" class="headerlink" title="2.基础版刮刮乐功能"></a>2.基础版刮刮乐功能</h2><p>canvs 覆盖在图片上<br><img src="/2021/12/08/erase/1.gif"></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><style></span><br><span class="line"> body {</span><br><span class="line"> margin: 0;</span><br><span class="line"> }</span><br><span class="line"> img {</span><br><span class="line"> width: 400px;</span><br><span class="line"> height: 300px;</span><br><span class="line"> left: 200px;</span><br><span class="line"> position: absolute;</span><br><span class="line"> z-index: -1;</span><br><span class="line"> }</span><br><span class="line"> canvas {</span><br><span class="line"> margin-left: 200px;</span><br><span class="line"> }</span><br><span class="line"> </style></span><br><span class="line"></span><br><span class="line"> <img src="./test.jpg" alt="pic"/></span><br><span class="line"> <canvas id="canvas" width="400" height="300"></canvas></span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><script></span><br><span class="line"> let canvas = document.querySelector('#canvas');</span><br><span class="line"> let context = canvas.getContext('2d');</span><br><span class="line"> // 绘制涂层</span><br><span class="line"> context.beginPath();</span><br><span class="line"> context.fillStyle = 'grey';</span><br><span class="line"> context.fillRect(0, 0, 400, 300);</span><br><span class="line"> // 监听鼠标移动事件</span><br><span class="line"> canvas.addEventListener('mousemove', (e) => {</span><br><span class="line"> // 当鼠标左键按下&&移动鼠标时,清除鼠标附近涂层</span><br><span class="line"> if (e.which === 1 && e.button === 0) {</span><br><span class="line"> const x = e.clientX, y = e.clientY;</span><br><span class="line"> context.globalCompositeOperation = 'destination-out';</span><br><span class="line"> context.beginPath();</span><br><span class="line"> // 清除以鼠标位置为圆心,半径为10px的圆的范围</span><br><span class="line"> context.arc(x - 200, y, 10, 0, Math.PI * 2);</span><br><span class="line"> context.fill();</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> </script></span><br></pre></td></tr></table></figure>
<h2 id="3-进阶版刮刮乐功能"><a href="#3-进阶版刮刮乐功能" class="headerlink" title="3.进阶版刮刮乐功能"></a>3.进阶版刮刮乐功能</h2><p>进阶功能:<br>点击时,以当前位置为圆心刮开一部分区域;<br>刮开 x 百分比(可以自定义)后后显示全部,并且使用动画逐渐变淡;<br>调用第一次刮的回调方法和刮完的回调方法,可以传入或不传;<br>涂层上面可以显示自定义文字;</p>
<p><img src="/2021/12/08/erase/2.gif"></p>
<p>首先改为 class 形式,方便多次创建刮刮乐。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">class Scratch {</span><br><span class="line"> constructor(id, { maskColor = 'grey', cursorRadius = 10 } = {}) {</span><br><span class="line"> this.canvas = document.getElementById('canvas');</span><br><span class="line"> this.context = this.canvas.getContext('2d');</span><br><span class="line"> this.width = this.canvas.clientWidth;</span><br><span class="line"> this.height = this.canvas.clientHeight;</span><br><span class="line"> this.maskColor = maskColor; // 涂层颜色</span><br><span class="line"> this.cursorRadius = cursorRadius; // 光标半径</span><br><span class="line"> this.init();</span><br><span class="line"> }</span><br><span class="line"> init() {</span><br><span class="line"> // 添加涂层</span><br><span class="line"> this.addCoat();</span><br><span class="line"> let bindEarse = this.erase.bind(this);</span><br><span class="line"> this.canvas.addEventListener('mousedown', (e) => {</span><br><span class="line"> // 按下左键</span><br><span class="line"> if (e.which === 1 && e.button === 0) {</span><br><span class="line"> // 擦掉涂层</span><br><span class="line"> this.canvas.addEventListener('mousemove', bindEarse);</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> document.addEventListener('mouseup', () => {</span><br><span class="line"> this.canvas.removeEventListener('mousemove', bindEarse);</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> addCoat() {</span><br><span class="line"> this.context.beginPath();</span><br><span class="line"> this.context.fillStyle = this.maskColor;</span><br><span class="line"> this.context.fillRect(0, 0, this.width, this.height);</span><br><span class="line"> }</span><br><span class="line"> erase(e) {</span><br><span class="line"> const x = e.clientX, y = e.clientY;</span><br><span class="line"> this.context.globalCompositeOperation = 'destination-out';</span><br><span class="line"> this.context.beginPath();</span><br><span class="line"> this.context.arc(x - this.width / 2, y, this.cursorRadius, 0, Math.PI * 2);</span><br><span class="line"> this.context.fill();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> new Scratch('canvas');</span><br></pre></td></tr></table></figure>
<p>然后,记录鼠标位置,mouseup 时判断是点击还是点击&移动鼠标,如果是点击则以当前位置为圆心刮开一部分区域;</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">this.canvas.addEventListener('mousedown', (e) => {</span><br><span class="line"> this.posX = e.clientX;</span><br><span class="line"> this.posY = e.clientY;</span><br><span class="line"> ...</span><br><span class="line">})</span><br><span class="line"> document.addEventListener('mouseup', (e) => {</span><br><span class="line"> if (this.posX === e.clientX && this.posY === e.clientY) {</span><br><span class="line"> this.erase(e);</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>然后,判断刮开面积是否超过一半,如果是清空涂层;</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ImageData ctx.getImageData(sx, sy, sw, sh);</span><br></pre></td></tr></table></figure>
<p>sx:将要被提取的图像数据矩形区域的左上角 x 坐标。<br>sy:将要被提取的图像数据矩形区域的左上角 y 坐标。<br>sw:将要被提取的图像数据矩形区域的宽度。<br>sh:将要被提取的图像数据矩形区域的高度。</p>
<p>ImageData 对象,包含 canvas 给定的矩形图像数据。可以用来判断是否被刮开。<br>每 4 个元素表示一个像素点的 rgba 值,所以可以判断第 4 个的值是否小于 256 的一半即 128,如果小于 128 即可视为透明(被刮开)。</p>
<p>清空指定区域内容:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">void ctx.clearRect(x, y, width, height);</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">document.addEventListener('mouseup', (e) => {</span><br><span class="line"> this.getScratchedPercentage();</span><br><span class="line"> if (this.currPerct >= this.maxEraseArea) {</span><br><span class="line"> this.context.clearRect(0, 0, this.width, this.height);</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">getScratchedPercentage() {</span><br><span class="line"> const pixels = this.context.getImageData(0, 0, this.width, this.height).data;</span><br><span class="line"> let transparentPixels = 0;</span><br><span class="line"> for (let i = 0; i < pixels.length; i += 4) {</span><br><span class="line"> if (pixels[i + 3] < 128) {</span><br><span class="line"> transparentPixels++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> this.currPerct = (transparentPixels / pixels.length * 4 * 100).toFixed(2);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后,设置第一次刮的回调方法和刮完的回调方法;</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">constructor(id, { maskColor = 'grey', cursorRadius = 10, maxEraseArea = 50,</span><br><span class="line"> firstEraseCbk = () => { }, lastEraseCbk = () => { } } = {}) {</span><br><span class="line"> ...</span><br><span class="line"> this.firstEraseCbk = firstEraseCbk; // 第一次刮的回调函数</span><br><span class="line"> this.lastEraseCbk = lastEraseCbk; // 刮开的回调函数</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">this.canvas.addEventListener('mousedown', (e) => { </span><br><span class="line"> if (this.currPerct === 0) {</span><br><span class="line"> this.firstEraseCbk();</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line">document.addEventListener('mouseup', (e) => {</span><br><span class="line"> if (this.currPerct >= this.maxEraseArea) {</span><br><span class="line"> this.context.clearRect(0, 0, this.width, this.height);</span><br><span class="line"> this.lastEraseCbk();</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>然后,刮开全部时慢慢清空涂层,设置背景色渐变效果;<br><code>requestAnimationFrame</code> 做出来的动画更流畅<br>回调函数用闭包形式可以给回调函数传参</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">document.addEventListener('mouseup', (e) => {</span><br><span class="line"> if (this.currPerct >= this.maxEraseArea) {</span><br><span class="line"> this.done = true;</span><br><span class="line"> requestAnimationFrame(this.fadeOut(255));</span><br><span class="line"> this.lastEraseCbk();</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">fadeOut(alpha) {</span><br><span class="line"> return () => {</span><br><span class="line"> this.context.save();</span><br><span class="line"> this.context.globalCompositeOperation = 'source-in';</span><br><span class="line"> this.context.fillStyle = this.context.fillStyle + (alpha -= 1).toString(16);</span><br><span class="line"> this.context.fillRect(0, 0, this.width, this.height);</span><br><span class="line"> this.context.restore();</span><br><span class="line"> // 到210已经看不到涂层了</span><br><span class="line"> if (alpha > 210) {</span><br><span class="line"> requestAnimationFrame(this.fadeOut(alpha));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后,初始化涂层的时候,再涂层上显示自定义文字;</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">addCoat() {</span><br><span class="line"> ...</span><br><span class="line"> if (this.text) {</span><br><span class="line"> this.context.font = 'bold 48px serif';</span><br><span class="line"> this.context.fillStyle = '#fff';</span><br><span class="line"> this.context.textAlign = 'center';</span><br><span class="line"> this.context.textBaseline = 'middle';</span><br><span class="line"> this.context.fillText(this.text, this.width / 2, this.height / 2);</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><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><!DOCTYPE html></span><br><span class="line"><html lang="en"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><head></span><br><span class="line"> <meta charset="UTF-8"></span><br><span class="line"> <meta http-equiv="X-UA-Compatible" content="IE=edge"></span><br><span class="line"> <meta name="viewport" content="width=device-width, initial-scale=1.0"></span><br><span class="line"> <title>Document</title></span><br><span class="line"> <style></span><br><span class="line"> body {</span><br><span class="line"> margin: 0;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> img {</span><br><span class="line"> width: 400px;</span><br><span class="line"> height: 300px;</span><br><span class="line"> left: 200px;</span><br><span class="line"> position: absolute;</span><br><span class="line"> z-index: -1;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> canvas {</span><br><span class="line"> margin-left: 200px;</span><br><span class="line"> }</span><br><span class="line"> </style></span><br><span class="line"></head></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><body></span><br><span class="line"> <img src="./test.jpg" alt="pic" /></span><br><span class="line"> <canvas id="canvas" width="400" height="300"></canvas></span><br><span class="line"> <script></span><br><span class="line"> class Scratch {</span><br><span class="line"> constructor(id, { maskColor = 'grey', cursorRadius = 10, maxEraseArea = 50, text = '',</span><br><span class="line"> firstEraseCbk = () => { }, lastEraseCbk = () => { } } = {}) {</span><br><span class="line"> this.canvasId = id;</span><br><span class="line"> this.canvas = document.getElementById(id);</span><br><span class="line"> this.context = this.canvas.getContext('2d');</span><br><span class="line"> this.width = this.canvas.clientWidth;</span><br><span class="line"> this.height = this.canvas.clientHeight;</span><br><span class="line"> this.maskColor = maskColor; // 涂层颜色</span><br><span class="line"> this.cursorRadius = cursorRadius; // 光标半径</span><br><span class="line"> this.maxEraseArea = maxEraseArea; // 刮开多少后自动清空涂层</span><br><span class="line"> this.text = text;</span><br><span class="line"> this.firstEraseCbk = firstEraseCbk; // 第一次刮的回调函数</span><br><span class="line"> this.lastEraseCbk = lastEraseCbk; // 刮开的回调函数</span><br><span class="line"> this.currPerct = 0; // 当前刮开多少百分比</span><br><span class="line"> this.done = false; // 是否刮完</span><br><span class="line"> this.init();</span><br><span class="line"> }</span><br><span class="line"> init() {</span><br><span class="line"> // 添加涂层</span><br><span class="line"> this.addCoat();</span><br><span class="line"> let bindEarse = this.erase.bind(this);</span><br><span class="line"> this.canvas.addEventListener('mousedown', e => {</span><br><span class="line"> if (this.done) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> this.posX = e.clientX;</span><br><span class="line"> this.posY = e.clientY;</span><br><span class="line"> // 按下左键</span><br><span class="line"> if (e.which === 1 && e.button === 0) {</span><br><span class="line"> // 擦掉涂层</span><br><span class="line"> this.canvas.addEventListener('mousemove', bindEarse);</span><br><span class="line"> }</span><br><span class="line"> if (this.currPerct === 0) {</span><br><span class="line"> this.firstEraseCbk();</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> document.addEventListener('mouseup', e => {</span><br><span class="line"> if (this.done) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> if (e.target.id !== this.canvasId) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> if (this.posX === e.clientX && this.posY === e.clientY) {</span><br><span class="line"> this.erase(e);</span><br><span class="line"> }</span><br><span class="line"> this.canvas.removeEventListener('mousemove', bindEarse);</span><br><span class="line"> this.getScratchedPercentage();</span><br><span class="line"> if (this.currPerct >= this.maxEraseArea) {</span><br><span class="line"> this.done = true;</span><br><span class="line"> requestAnimationFrame(this.fadeOut(255));</span><br><span class="line"> this.lastEraseCbk();</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"> addCoat() {</span><br><span class="line"> this.context.beginPath();</span><br><span class="line"> this.context.fillStyle = this.maskColor;</span><br><span class="line"> this.context.fillRect(0, 0, this.width, this.height);</span><br><span class="line"> // 绘制涂层上的文字</span><br><span class="line"> if (this.text) {</span><br><span class="line"> this.context.font = 'bold 48px serif';</span><br><span class="line"> this.context.fillStyle = '#fff';</span><br><span class="line"> this.context.textAlign = 'center';</span><br><span class="line"> this.context.textBaseline = 'middle';</span><br><span class="line"> this.context.fillText(this.text, this.width / 2, this.height / 2);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // 擦除某位置涂层</span><br><span class="line"> erase(e) {</span><br><span class="line"> const x = e.clientX, y = e.clientY;</span><br><span class="line"> this.context.globalCompositeOperation = 'destination-out';</span><br><span class="line"> this.context.beginPath();</span><br><span class="line"> this.context.arc(x - this.width / 2, y, this.cursorRadius, 0, Math.PI * 2);</span><br><span class="line"> this.context.fill();</span><br><span class="line"> }</span><br><span class="line"> // 计算被擦除的部分占全部的百分比</span><br><span class="line"> getScratchedPercentage() {</span><br><span class="line"> const pixels = this.context.getImageData(0, 0, this.width, this.height).data;</span><br><span class="line"> let transparentPixels = 0;</span><br><span class="line"> for (let i = 0; i < pixels.length; i += 4) {</span><br><span class="line"> if (pixels[i + 3] < 128) {</span><br><span class="line"> transparentPixels++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> this.currPerct = (transparentPixels / pixels.length * 4 * 100).toFixed(2);</span><br><span class="line"> }</span><br><span class="line"> // 清空涂层时淡出效果</span><br><span class="line"> fadeOut(alpha) {</span><br><span class="line"> return () => {</span><br><span class="line"> this.context.save();</span><br><span class="line"> this.context.globalCompositeOperation = 'source-in';</span><br><span class="line"> this.context.fillStyle = this.context.fillStyle + (alpha -= 1).toString(16);</span><br><span class="line"> this.context.fillRect(0, 0, this.width, this.height);</span><br><span class="line"> this.context.restore();</span><br><span class="line"> // 到210已经看不到涂层了</span><br><span class="line"> if (alpha > 210) {</span><br><span class="line"> requestAnimationFrame(this.fadeOut(alpha));</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"> new Scratch('canvas', { text: '刮一刮', maxEraseArea: 10 });</span><br><span class="line"> </script></span><br><span class="line"></body></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></html></span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>css</category>
</categories>
<tags>
<tag>canvas</tag>
</tags>
</entry>
<entry>
<title>如何获取下载时间</title>
<url>/2021/11/15/get-down-time/</url>
<content><![CDATA[<p>场景:文件较大时,展示下载进度条;埋点时获取下载所需时间…</p>
<span id="more"></span>
<p>方法:借助 promise 链式调用</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> time1 = <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();</span><br><span class="line">axios(url, params).then(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> res</span><br><span class="line"> .blob()</span><br><span class="line"> .then(<span class="function"><span class="params">blob</span> =></span> {</span><br><span class="line"> <span class="comment">// 保存文件到本地...</span></span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">const</span> time2 = <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();</span><br><span class="line"> <span class="built_in">console</span>.log(time2 - time1);</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>总结:获取的时间是从服务器请求文件的时间,不包含保存到本地的时间。打印时间的时候,正好弹出选择文件下载位置弹窗。</p>
]]></content>
<categories>
<category>接口请求</category>
</categories>
<tags>
<tag>axios</tag>
</tags>
</entry>
<entry>
<title>hexo 使用指南</title>
<url>/2021/10/10/hexo-guide/</url>
<content><![CDATA[<p>hexo 是一个快速、简洁且高效的博客框架,在搭建博客的过程中,我遇到并解决了不少问题。<br>下面将讲一下博客的搭建流程,和遇到的问题以及如何解决这些问题。</p>
<span id="more"></span>
<h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g hexo-cli</span><br></pre></td></tr></table></figure>
<h3 id="将博客项目与-github-关联"><a href="#将博客项目与-github-关联" class="headerlink" title="将博客项目与 github 关联"></a>将博客项目与 github 关联</h3><p>第一步,新建博客项目</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo init <folder></span><br><span class="line"><span class="built_in">cd</span> <folder></span><br><span class="line">npm install</span><br></pre></td></tr></table></figure>
<p>第二步,新建 github 仓库,仓库名为 <strong>xx.github.io</strong><br>第三步,安装 <strong>hexo-deployer-git</strong> 插件</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm i hexo-deployer-git --save</span><br></pre></td></tr></table></figure>
<p>第四步,修改 <strong>_config.yml</strong> 配置</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">deploy:</span><br><span class="line"> <span class="built_in">type</span>: git</span><br><span class="line"> repo: https://github.com/xx/xx.github.io</span><br><span class="line"> branch: main</span><br></pre></td></tr></table></figure>
<h3 id="本地启动及部署流程"><a href="#本地启动及部署流程" class="headerlink" title="本地启动及部署流程"></a>本地启动及部署流程</h3><p>hexo 本地启动命令(热更新)</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo s -g --debug</span><br></pre></td></tr></table></figure>
<p>依次执行以下以下命令,然后在浏览器地址栏中输入 <strong>xx.github.io</strong>就可以看到博客了</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo clean(清除缓存)</span><br><span class="line">hexo g(上传到仓库)</span><br><span class="line">hexo d(部署)</span><br></pre></td></tr></table></figure>
<h3 id="github-博客映射到个人域名"><a href="#github-博客映射到个人域名" class="headerlink" title="github 博客映射到个人域名"></a>github 博客映射到个人域名</h3><p>第一步,域名解析<br>第二步,设置 CNAME 文件<br>第三步,进入 github 绑定域名<br>输入域名可以看到博客<br><a href="https://blog.csdn.net/heimu24/article/details/81159099">参考链接</a></p>
<h3 id="设置主题"><a href="#设置主题" class="headerlink" title="设置主题"></a>设置主题</h3><p>我选用了 <strong>next</strong> 主题</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/iissnan/hexo-theme-next themes/next</span><br></pre></td></tr></table></figure>
<p>修改 <strong>_config.yml</strong> 配置</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">theme: next</span><br><span class="line">language: zh-CN</span><br></pre></td></tr></table></figure>
<p><a href="https://blog.csdn.net/as480133937/article/details/100138838">详细配置的参考链接</a></p>
<h3 id="遇到的问题及解决办法"><a href="#遇到的问题及解决办法" class="headerlink" title="遇到的问题及解决办法"></a>遇到的问题及解决办法</h3><p>OpenSSL SSL_read: Connection was reset, errno 10054<br>解决办法:<code>git config --global http.sslVerify 'false'</code></p>
<p>spawn failed 错误<br>删除 .deploy_git 文件夹,执行 <code>git config --global core.autocrlf false</code>,重新部署</p>
]]></content>
<categories>
<category>文档</category>
</categories>
<tags>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title>神奇的色调旋转滤镜hue-rotate</title>
<url>/2021/11/15/hue-rotate/</url>
<content><![CDATA[<p>filter 是个功能强大的方法,可以给元素添加各种各样的滤镜,比如模糊、阴影、变灰等等。<br>hue-rotate 可以改变元素的色调,展示出更漂亮的颜色。</p>
<span id="more"></span>
<p>如果想实现颜色变化的动效,可以考虑使用色调旋转滤镜:filter:hue-rotate()。<br>案例 1:设置按钮的背景色</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><button style=<span class="string">"background: #2486ff;filter: hue-rotate(350deg);"</span>>按钮</button></span><br></pre></td></tr></table></figure>
<p>但是,单纯设置背景色的话,色调变化多少不好把握,而且一般 UI 会给出具体的颜色值。所以,hue-rotate 更适合做颜色变化的动效。<br>案例 2:高亮文字颜色闪烁变化</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span>啦啦啦</span></span><br><span class="line"></span><br><span class="line">span {</span><br><span class="line"> <span class="attr">background</span>: red;</span><br><span class="line"> animation: hue 3s;</span><br><span class="line">}</span><br><span class="line">@keyframes hue {</span><br><span class="line"> <span class="keyword">from</span> {</span><br><span class="line"> <span class="attr">filter</span>: hue-rotate(0deg);</span><br><span class="line"> }</span><br><span class="line"> to {</span><br><span class="line"> <span class="attr">filter</span>: hue-rotate(-360deg);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>css</category>
</categories>
<tags>
<tag>filter</tag>
</tags>
</entry>
<entry>
<title>proxy 504 的原因以及解决办法</title>
<url>/2021/10/26/proxy504/</url>
<content><![CDATA[<p>在开发中可能会碰到接口报错 <strong>504 Gateway Time-out</strong> 的情况,在此记录一下如何解决这个问题。</p>
<span id="more"></span>
<p>出现的原因:接口时间过长,超出了设置的超时时间。<br>解决办法:<br>首先设置 nginx 的 <strong>proxy_read_timeout</strong> ,这个值的单位是 s(秒)。<br>如果还不行的话,要看一下是否设置了 <strong>负载均衡</strong> ,要把负载均衡(LB)的时间也调大一点。<br>注:如果日志中出现了 <strong>499</strong> 状态码,也可以尝试此方案。499 代表客户端等待时间过长,主动断开连接。</p>
]]></content>
<categories>
<category>接口请求</category>
</categories>
<tags>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title>如何处理同一个接口后发出的请求先返回的情况</title>
<url>/2021/10/27/request-interceptors/</url>
<content><![CDATA[<p>有一种场景是:请求一个接口,然后改变参数再请求一次这个接口,由于网络阻塞或后端处理等原因,<br><strong>后发起的请求先返回</strong>。这会造成应该展示的数据被之前的请求覆盖掉。</p>
<span id="more"></span>
<p>解决思路:每次请求都用一个 id 标记,每请求一次 id 自增 1,接口返回数据时判断这个接口的 id 是否等于最新的 id。<br>如果不相同,则不处理这次返回的数据。<br>具体代码如下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> requestObj = {};</span><br><span class="line"></span><br><span class="line">request.interceptors.request.use(<span class="function">(<span class="params">url, option</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (!requestObj[url]) {</span><br><span class="line"> requestObj[url] = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> requestObj[url] = ++requestObj[url];</span><br><span class="line"> option[<span class="string">"repeatId"</span>] = requestObj[url];</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">request.interceptors.response.use(<span class="function">(<span class="params">response, option</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (option[<span class="string">"repeatId"</span>] < requestObj[<span class="keyword">new</span> URL(response.url).pathname]) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">() =></span> {});</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> response;</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>此处以 <strong>umi-request</strong> 库为例,axios 与此类似。</p>
<p>注:<br>如果同一个页面中请求了多次同一个接口,则需要在请求这个接口的时候单独加上 repeatId。<br>在 response 拦截器中返回一个空的 Promise ,会一直处理 pending 状态,不会进入 then,也不会进入 error。</p>
<hr>
<p>但是,上面的代码中,当此次的返回结果不是最新的接口时,需要停掉 promise 链,不继续执行后面的代码。使用的方法是返回一个永远在 pending 的 promise。这种方法可能会导致内存泄漏。<br>所以最好的办法是:重写 then 方法,判断是否需要执行 原来的 then。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> STOP_VALUE = {};</span><br><span class="line"> <span class="keyword">var</span> STOPPER_PROMISE = <span class="built_in">Promise</span>.resolve(STOP_VALUE);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Promise</span>.prototype._then = <span class="built_in">Promise</span>.prototype.then;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Promise</span>.stop = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> STOPPER_PROMISE; <span class="comment">//不是每次返回一个新的Promise,可以节省内存</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Promise</span>.prototype.then = <span class="function"><span class="keyword">function</span> (<span class="params">onResolved, onRejected</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>._then(<span class="function"><span class="keyword">function</span> (<span class="params">value</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> value === STOP_VALUE ? STOP_VALUE : onResolved(value);</span><br><span class="line"> }, onRejected);</span><br><span class="line"> };</span><br><span class="line">})();</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.resolve(<span class="number">8</span>)</span><br><span class="line"> .then(<span class="function"><span class="params">v</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(v);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">9</span>;</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function"><span class="params">v</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(v);</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Promise</span>.stop();</span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// will never called but will be GCed</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"catch"</span>);</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// will never called but will be GCed</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"then"</span>);</span><br><span class="line"> });</span><br></pre></td></tr></table></figure>
<p>参考链接:<br><a href="https://github.com/xieranmaya/blog/issues/5">如何停掉 promise 链</a></p>
]]></content>
<categories>
<category>接口请求</category>
</categories>
<tags>
<tag>axios</tag>
<tag>request</tag>
<tag>promise</tag>
</tags>
</entry>
<entry>
<title>浏览器画中画功能</title>
<url>/2021/11/22/picture-in-picture/</url>
<content><![CDATA[<p>浏览器的画中画功能允许在一个小窗口中播放页面中的视频,而且标签页隐藏小窗口依然显示。</p>
<span id="more"></span>
<p>谷歌浏览器默认支持画中画功能,用户可以在控制栏中打开或关闭画中画功能。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><video src=<span class="string">"./video1.mp4"</span> controls></video></span><br></pre></td></tr></table></figure>
<h3 id="如何自动控制画中画功能?"><a href="#如何自动控制画中画功能?" class="headerlink" title="如何自动控制画中画功能?"></a>如何自动控制画中画功能?</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><video id=<span class="string">"video"</span> src=<span class="string">"./video1.mp4"</span> controls></video></span><br><span class="line"><span class="xml"><span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"btn"</span>></span>开启/关闭画中画<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"></span><br><span class="line"><span class="built_in">window</span>.btn.addEventListener(<span class="string">'click'</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">window</span>.video === <span class="built_in">document</span>.pictureInPictureElement) {</span><br><span class="line"> <span class="built_in">document</span>.exitPictureInPicture();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">window</span>.video.requestPictureInPicture();</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p><code>document.pictureInPictureElement</code>:画中画功能开启时指向<code><video></code>,关闭时指向 null<br><code>document.exitPictureInPicture</code>:关闭画中画功能,异步操作<br><code>video.requestPictureInPicture</code>:打开画中画功能,异步操作</p>
<h3 id="如何监听打开或关闭画中画操作?"><a href="#如何监听打开或关闭画中画操作?" class="headerlink" title="如何监听打开或关闭画中画操作?"></a>如何监听打开或关闭画中画操作?</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">window</span>.video.addEventListener(<span class="string">"enterpictureinpicture"</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"on"</span>);</span><br><span class="line">});</span><br><span class="line"><span class="built_in">window</span>.video.addEventListener(<span class="string">"leavepictureinpicture"</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"off"</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h3 id="如何判断是否能使用画中画功能?"><a href="#如何判断是否能使用画中画功能?" class="headerlink" title="如何判断是否能使用画中画功能?"></a>如何判断是否能使用画中画功能?</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (!(<span class="string">"pictureInPictureEnabled"</span> <span class="keyword">in</span> <span class="built_in">document</span>)) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"浏览器不支持"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">document</span>.pictureInPictureEnabled) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"画中画功能不可用"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>js</category>
</categories>
<tags>
<tag>浏览器</tag>
</tags>
</entry>
</search>