├── .gitignore ├── README.md ├── articles ├── Python实时获取子进程输出.md ├── TS中的?和!.md ├── TS中的extends.md ├── canvas基础之绘制箭头.md ├── clone仓库太大优化方案.md ├── frp配合Nginx实现https访问内网服务.md ├── js实用小技巧.md ├── npm知识合集.md ├── v2rayU谷歌翻译.md └── 箭头跟随鼠标移动效果.md ├── images ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 3225d83ebf58d328096f1d10e24689d9df647877defb92306dd7a60538e15569.png ├── 4.jpg ├── 5.jpg ├── 55403052ba6ab76211bcb76466d5daebb1223e6986c7cf53c5d60cf724b71413.png ├── 8c990bb42615590073e363faae97bf3c8265a9f9719ab8bcfec6a37126601e72.png ├── a0630acd5b2d5efe0545bad00cb8485e52085d816b7ab4a50241980d387a1869.png ├── arrow.gif ├── settext.gif ├── wave-line.gif └── wave.gif └── src └── canvas ├── canvas箭头跟随鼠标.html ├── components └── arrow.js ├── particle.js ├── similarPicture.js ├── utiles └── tools.js └── wave.html /.gitignore: -------------------------------------------------------------------------------- 1 | .hg 2 | .svn 3 | .CVS 4 | .idea 5 | .history 6 | node_modules/ 7 | jscoverage_lib/ 8 | bower_components/ 9 | dist/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | 折腾过挺多博客的,发现直接用Github的 Issues,写技术博客能够更加专心,后面就持续在此更新博客了~ 4 | 5 | ## 博客分类 6 | 7 | - [Canvas](https://github.com/tomatoKnightJ/Blog/labels/canvas) 8 | 9 | - [浏览器](https://github.com/tomatoKnightJ/Blog/labels/%E6%B5%8F%E8%A7%88%E5%99%A8) 10 | 11 | - [ES6](https://github.com/tomatoKnightJ/Blog/labels/es6) 12 | 13 | - [Git](https://github.com/tinet-jutt/Blog/issues?q=is%3Aissue+is%3Aopen+label%3AGit) 14 | 15 | - [NPM](https://github.com/tinet-jutt/Blog/labels/NPM) 16 | 17 | - [TypeScript](https://github.com/tinet-jutt/Blog/issues?q=is%3Aissue+is%3Aopen+label%3ATypeScript) 18 | 19 | - [Python](https://github.com/tinet-jutt/Blog/labels/Python) 20 | -------------------------------------------------------------------------------- /articles/Python实时获取子进程输出.md: -------------------------------------------------------------------------------- 1 | # Python实时获取输出 2 | 3 | #### python print 介绍 4 | 5 | >python中的print语句就是调用了sys.stdout.write(),例如在打印对象调用print(obj) 时,事实上是调用了 sys.stdout.write(obj+'\n'); 6 | 默认以\n换行结束,可通过设置参数 end 参数选择结束符号。通常设置 end=' '实现单行覆盖输出。 7 | 8 | #### python中输出规则 9 | >python中标准错误(std.err)和标准输出(std.out)的输出规则(标准输出默认需要缓存后再输出到屏幕,而标准错误则直接打印到屏幕) 10 | 11 | 新建 system_time.py 12 | 13 | ```python 14 | # !/usr/bin/python3 15 | # -*- coding: utf-8 -*- 16 | import datetime 17 | import time 18 | 19 | for line in range(0, 3): 20 | print(datetime.datetime.now().strftime("%H:%M:%S")) 21 | if line == 2: 22 | break 23 | time.sleep(1) 24 | 25 | ``` 26 | 新建 result_output.py 27 | ```python 28 | # !/usr/bin/python3 29 | # -*- coding: utf-8 -*- 30 | import subprocess 31 | res = subprocess.Popen(["/usr/bin/python3 ./system_time.py"], 32 | shell=True, 33 | stdout=subprocess.PIPE, 34 | stderr=subprocess.PIPE) 35 | while res.poll() is None: 36 | print(res.stdout.readline()) 37 | ``` 38 | 39 | 40 | >执行`python3 result_output.py`发现一次性输出3次时间 41 | 42 | 43 | 原因是python缓存机制,虽然stderr和stdout默认都是指向屏幕的,但是stderr是无缓存的,程序往stderr输出一个字符,就会在屏幕上显示一个;而stdout是有缓存的,只有遇到换行或者积累到一定的大小,才会显示出来。这就是为什么上面的会最先显示两个stderr的原因。 44 | 45 | - -u参数 46 | 47 | python命令加上-u(unbuffered)参数后会强制其标准输出也同标准错误一样不通过缓存直接打印到屏幕。 48 | ```python 49 | python3 -u app.py 50 | ``` 51 | 52 | - print方法flush=True 53 | 54 | flush=True可将缓存里面的内容立即输出到标准输出流。 55 | 56 | ``` 57 | print(datetime.datetime.now().strftime("%H:%M:%S"), flush=True) 58 | ``` 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /articles/TS中的?和!.md: -------------------------------------------------------------------------------- 1 | # 什么是 ?(问号)操作符? 2 | 在TypeScript里面,有~3~ **4**个地方会出现问号操作符,他们分别是 3 | 4 | ### 三元运算符 5 | ```ts 6 | // 当 isNumber(input) 为 True 是返回 ? : 之间的部分; isNumber(input) 为 False 时 7 | // 返回 : ; 之间的部分 8 | const a = isNumber(input) ? input : String(input); 9 | ``` 10 | 11 | ### 参数 12 | ```ts 13 | // 这里的 ?表示这个参数 field 是一个可选参数 14 | function getUser(user: string, field?: string) { 15 | } 16 | ``` 17 | 18 | ### 成员 19 | ```ts 20 | // 这里的?表示这个name属性有可能不存在 21 | class A { 22 | name?: string 23 | } 24 | 25 | interface B { 26 | name?: string 27 | } 28 | ``` 29 | 30 | ### 安全链式调用 31 | ```ts 32 | // 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示 33 | // 出错 TS2532: Object is possibly 'undefined'. 34 | return new Error().stack.split('\n'); 35 | 36 | // 我们可以添加?操作符,当stack属性存在时,调用 stack.split。若stack不存在,则返回空 37 | return new Error().stack?.split('\n'); 38 | 39 | // 以上代码等同以下代码 40 | const err = new Error(); 41 | return err.stack && err.stack.split('\n'); 42 | ``` 43 | 44 | # 什么是!(感叹号)操作符? 45 | 在TypeScript里面有3个地方会出现感叹号操作符,他们分别是 46 | 47 | ### 一元运算符 48 | ```ts 49 | // ! 就是将之后的结果取反,比如: 50 | // 当 isNumber(input) 为 True 时返回 False; isNumber(input) 为 False 时返回True 51 | const a = !isNumber(input); 52 | ``` 53 | 54 | ### 成员 55 | ```ts 56 | // 因为接口B里面name被定义为可空的值,但是实际情况是不为空的,那么我们就可以 57 | // 通过在class里面使用!,重新强调了name这个不为空值 58 | class A implemented B { 59 | name!: string 60 | } 61 | 62 | interface B { 63 | name?: string 64 | } 65 | ``` 66 | 67 | ### 强制链式调用 68 | ```ts 69 | // 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示 70 | // 出错 TS2532: Object is possibly 'undefined'. 71 | new Error().stack.split('\n'); 72 | 73 | // 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在 74 | new Error().stack!.split('\n'); 75 | ``` 76 | 77 | 祝大家编程愉快 -------------------------------------------------------------------------------- /articles/TS中的extends.md: -------------------------------------------------------------------------------- 1 | # TS中的extends 2 | 3 | ## 1.做判断用 4 | ```ts 5 | T extends U ? X : Y 6 | ``` 7 | 8 | ### 1. T 为非复合类型 9 | 10 | ``` 11 | 如果T类型包含U类型,那么取结果X,否则取结果Y。 12 | ``` 13 | 举例: 14 | 15 | ```ts 16 | type NonNullable = T extends null | undefined ? never : T; 17 | 18 | // 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。 19 | let demo1: NonNullable; // => number 20 | let demo2: NonNullable; // => string 21 | let demo3: NonNullable; // => never 22 | 23 | ``` 24 | 25 | ### 2. T 为复合类型 26 | 27 | 当T为联合类型的时候,会进行拆分,有点类似数学中的分解因式: 28 | 29 | ``` 30 | (a + b) * c => ac + bc 31 | ``` 32 | 举例: 33 | ```ts 34 | type Diff = T extends U ? never : T; // 找出T的差集 35 | type Filter = T extends U ? T : never; // 找出交集 36 | 37 | type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // => "b" | "d" 38 | // <"a" | "b" | "c" | "d", "a" | "c" | "f"> 39 | // 相当于 40 | // <'a', "a" | "c" | "f"> | 41 | // <'b', "a" | "c" | "f"> | 42 | // <'c', "a" | "c" | "f"> | 43 | // <'d', "a" | "c" | "f"> 44 | type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // => "a" | "c" 45 | // <"a" | "b" | "c" | "d", "a" | "c" | "f"> 同上 46 | 47 | let demo1: Diff; // => number 48 | 49 | ``` 50 | 51 | ## 2.通过该关键字来约束数据的类型属性 52 | 53 | ```ts 54 | function loggingIdentity(arg: T): T { 55 | console.log(arg.length) 56 | return arg 57 | } 58 | ``` 59 | 上述代码是会报错的,因为arg的类型是不确定的,所以不一定会有length属性,那么如何来解决这个问题呢? 60 | 61 | 使用extends关键字,通过该关键字来约束数据的类型属性。 62 | 63 | 这里要定义一个接口 64 | 65 | ```ts 66 | interface LengthWise { 67 | length: number 68 | } 69 | 70 | function loggingIdentity(arg: T): T { 71 | console.log(arg.length) 72 | return arg 73 | } 74 | 75 | ``` 76 | 77 | 这样就保证arg变量肯定会有length属性了,但与此同时也限制了arg的传入类型范围,比如,你传入数字就是不可以的。 -------------------------------------------------------------------------------- /articles/canvas基础之绘制箭头.md: -------------------------------------------------------------------------------- 1 | # canvas基础之绘制箭头 2 | 3 | 先看下效果图 4 | 5 | 6 | ![](../images/3225d83ebf58d328096f1d10e24689d9df647877defb92306dd7a60538e15569.png) 7 | 8 | 9 | ### canvas坐标系 10 | canvas坐标系和我们在数学中学到的坐标系不同,y轴向下为正方向 11 | ![picture 1](../images/55403052ba6ab76211bcb76466d5daebb1223e6986c7cf53c5d60cf724b71413.png) 12 | 13 | ### 绘制 14 | ![picture 2](../images/a0630acd5b2d5efe0545bad00cb8485e52085d816b7ab4a50241980d387a1869.png) 15 | 16 | 1. 假设箭头的宽高为w, h。 17 | 2. translate() 方法重新映射画布上的 (0,0) 位置。利用此方法改变画布原点为箭头中心点坐标。 18 | 3. rotate(angle) 方法旋转当前的绘图,angle以弧度计。如需将角度转换为弧度,请使用 degrees*Math.PI/180 公式进行计算。 19 | 20 | 21 | 22 | #### 开始绘制 23 | ``` 24 | var ocanvas=document.getElementById('mycanvas'), 25 | ctx=ocanvas.getContext('2d'), 26 | W=ocanvas.width, 27 | H=ocanvas.height; 28 | // 设置画布的中点为原点 29 | ctx.translate(W/2, H/2); 30 | ctx.beginPath(); 31 | // 移动画笔到指定点 32 | ctx.moveTo(-w/2, -h/2); 33 | // 绘制直线 34 | ctx.lineTo(w/10, -h/2); 35 | ctx.lineTo(w/10, -h); 36 | ctx.lineTo(w/2, 0); 37 | ctx.lineTo(w/10, h); 38 | ctx.lineTo(w/10, h/2); 39 | ctx.lineTo(-w/2, h/2); 40 | ctx.closePath(); 41 | // 设置填充颜色 42 | ctx.fillStyle = 'rgb(57, 119, 224)'; 43 | // 设置绘制颜色 44 | ctx.strokeStyle = 'rgba(0, 0, 0, 0)'; 45 | // 填充动作 46 | ctx.fill(); 47 | // 描边动作 48 | ctx.stroke(); 49 | ``` 50 | 51 | ### 实现一个箭头类 52 | ``` 53 | /** 54 | * 绘制箭头类 55 | */ 56 | 57 | class Arrow { 58 | constructor(props){ 59 | this.x = 0; 60 | this.y = 0; 61 | this.w = 60; 62 | this.h = 30; 63 | // 箭头旋转角度 64 | this.rotation = 0; 65 | this.fillStyle = 'rgb(57, 119, 224)'; 66 | this.strokeStyle = 'rgba(0, 0, 0, 0)'; 67 | Object.assign(this, props); 68 | return this; 69 | } 70 | createPath(ctx){ 71 | let {w, h} = this; 72 | ctx.beginPath(); 73 | ctx.moveTo(-w/2, -h/2); 74 | ctx.lineTo(w/10, -h/2); 75 | ctx.lineTo(w/10, -h); 76 | ctx.lineTo(w/2, 0); 77 | ctx.lineTo(w/10, h); 78 | ctx.lineTo(w/10, h/2); 79 | ctx.lineTo(-w/2, h/2); 80 | ctx.closePath(); 81 | return this; 82 | } 83 | render(ctx){ 84 | let {fillStyle, strokeStyle, rotation, x, y} = this; 85 | ctx.save(); 86 | ctx.fillStyle = fillStyle; 87 | ctx.strokeStyle = strokeStyle; 88 | ctx.translate(x, y); 89 | ctx.rotate(rotation); 90 | this.createPath(ctx); 91 | ctx.fill(); 92 | ctx.stroke(); 93 | ctx.restore(); 94 | return this; 95 | } 96 | } 97 | 98 | ``` 99 | ### 使用 100 | ``` 101 | var ocanvas=document.getElementById('mycanvas'), 102 | ctx=ocanvas.getContext('2d'), 103 | W=ocanvas.width, 104 | H=ocanvas.height; 105 | 106 | let arrow=new Arrow({ 107 | x:W/2, 108 | y:H/2, 109 | rotation:Math.PI/180*30 110 | }).render(ctx) 111 | ``` 112 | 113 | 源码地址:[https://github.com/tomatoKnightJ/Blog/blob/main/src/arrow.js](https://github.com/tomatoKnightJ/Blog/blob/main/src/arrow.js) 114 | -------------------------------------------------------------------------------- /articles/clone仓库太大优化方案.md: -------------------------------------------------------------------------------- 1 | # 克隆的仓库太大优化方案 2 | 3 | ### 1. 浅层克隆 ```--depth=n``` 4 | n表示拉取最新的commit的次数 5 | 6 | 对比下 7 | - 使用`git pull --depth=1`拉取的仓库用`git log`查看commit记录 8 | ``` 9 | * 64a5b4c - (grafted, HEAD -> dev, origin/dev, origin/HEAD) update (47 分钟前) 10 | ``` 11 | 12 | - 使用`git pull`拉取的仓库用`git log`查看commit记录 13 | ``` 14 | * 64a5b4c - (HEAD -> dev, origin/dev) update (2 小时前) 15 | * b3326dc - heihei (5 小时前) 16 | * 84dba47 - (reset) haha (5 小时前) 17 | * a3748e9 - 还原A.txt (1 年 9 个月前) 18 | * cfc063f - Revert "添加EEE" (1 年 9 个月前) 19 | * eb283a6 - 添加EEE (1 年 9 个月前) 20 | * a9bb437 - 添加ddd (1 年 9 个月前) 21 | * 7ba2c82 - 修改 (1 年 9 个月前) 22 | ``` 23 | 24 | ### 2. 只拉取指定文件或文件夹 25 | 26 | 1. 开启`Sparse Checkouts` 27 | ```bash 28 | git config core.sparsecheckout true 29 | ``` 30 | 2. 告诉git你想跟踪的文件或文件夹路径 31 | ```bash 32 | # git 项目根目录下操作 33 | # 只更新当前项目的contrib文件夹下的completion文件夹下的所有文件 34 | echo contrib/completion/ >> .git/info/sparse-checkout 35 | ``` 36 | 或者直接更改.git/info/sparse-checkout文件的内容也ok 37 | 38 | **此方法针对一个git仓库存放了多个项目而本身只关注其中的某一个项目有奇效😄** -------------------------------------------------------------------------------- /articles/frp配合Nginx实现https访问内网服务.md: -------------------------------------------------------------------------------- 1 | #### 起因 2 | 已有的vps挂有不可描述应用,已经用```acme```脚本申请过证书,所以用nginx配合frp做反向代理实现https访问内网应用 3 | 4 | #### 在域名服务商处添加二级域名解析 5 | 添加 ```frp.sample.com``` 解析或者 ```*.sample.com```解析到vps服务ip地址 6 | 7 | #### 申请证书 8 | - 申请 9 | ```sh 10 | acme.sh --issue -d "${domain}" --standalone -k ec-256 --force 11 | ``` 12 | - 配置 13 | ```sh 14 | acme.sh --installcert -d "${domain}" --fullchainpath /data/v2ray.crt --keypath /data/v2ray.key --ecc --force 15 | ``` 16 | 17 | 18 | #### 安装frp 19 | - ##### 服务端 20 | 1. 下载frp 21 | ```sh 22 | wget https://github.com/fatedier/frp/releases/download/v0.37.1/frp_0.37.1_darwin_amd64.tar.gz 23 | ``` 24 | 25 | 2. 解压 ```tar xzvf frp_0.37.1_darwin_amd64.tar.gz``` 26 | 27 | 3. 编辑 ```frps.ini```文件 28 | ```sh 29 | [common] 30 | bind_port = 7000 31 | dashboard_port = 7500 32 | dashboard_user = ***** // frp 面板用户名 33 | dashboard_pwd = ***** // frp 面板密码 34 | vhost_http_port = 9000 // http 服务端口 35 | max_pool_count=5 36 | privilege_token = ********** // 验证token,需要和客户端保持一致 37 | ``` 38 | 39 | 4. 添加systemctl管理 40 | ```sh 41 | vi /usr/lib/systemd/system/frp.service 42 | ``` 43 | 贴入以下内容 44 | ```sh 45 | [Unit] 46 | Description=The nginx HTTP and reverse proxy server 47 | After=network.target remote-fs.target nss-lookup.target 48 | 49 | [Service] 50 | Type=simple 51 | ExecStart=/root/frp/frp_0.37.1_linux_amd64/frps -c /root/frp/frp_0.37.1_linux_amd64/frps.ini 52 | KillSignal=SIGQUIT 53 | TimeoutStopSec=5 54 | KillMode=process 55 | PrivateTmp=true 56 | StandardOutput=syslog 57 | StandardError=inherit 58 | 59 | [Install] 60 | WantedBy=multi-user.target 61 | ``` 62 | 执行 ``` systemctl daemon-reload```重新载入配置文件,让设置生效 63 | 64 | 手动开启frp ```systemctl start frp.service``` 65 | 66 | 设置开机启动 ```systemctl enable frp.service``` 67 | 68 | - 客户端 69 | 步骤和上面差不多不赘述了,列下不同点 70 | 1. frp客户端地址为```https://github.com/fatedier/frp/releases/download/v0.37.1/frp_0.37.1_linux_arm.tar.gz``` 71 | 72 | 2. 客户端的配置文件为``` frpc.ini```内容如下 73 | ```sh 74 | [common] 75 | server_addr = ****** // 服务器ip地址 76 | server_port = 7000 77 | privilege_token = ****** 78 | 79 | [web] 80 | type = http 81 | local_ip = 127.0.0.1 // 本地服务ip 82 | local_port = 80 // 本地服务端口 83 | custom_domains = frp.sample.com 84 | remote_port = 80 // 远程服务端口,nginx服务端口 85 | 86 | [ssh] 87 | type = tcp 88 | local_ip = 127.0.0.1 89 | local_port = 22 90 | remote_port = 6000 91 | ``` 92 | 93 | 3. systemctl管理 94 | 树莓派路径为:```/etc/systemd/system/frpc.service``` 95 | 96 | #### 服务端Nginx配置 97 | ```sh 98 | server { 99 | listen 443 ssl http2; 100 | listen [::]:443 http2; 101 | ssl_certificate /data/frp.crt; 102 | ssl_certificate_key /data/frp.key; 103 | ssl_protocols TLSv1.3; 104 | ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5; 105 | server_name frp.sample.com; 106 | index index.html index.htm; 107 | root /home/wwwroot/3DCEList; 108 | error_page 400 = /400.html; 109 | 110 | ssl_early_data on; 111 | ssl_stapling on; 112 | ssl_stapling_verify on; 113 | add_header Strict-Transport-Security "max-age=31536000"; 114 | 115 | # 将https服务代理到http 116 | location / 117 | { 118 | proxy_pass http://frp.sample.com:9000; 119 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 120 | proxy_set_header Host $http_host; 121 | proxy_set_header X-NginX-Proxy true; 122 | proxy_http_version 1.1; 123 | proxy_set_header Upgrade $http_upgrade; 124 | proxy_set_header Connection "upgrade"; 125 | proxy_max_temp_file_size 0; 126 | proxy_redirect off; 127 | proxy_read_timeout 240s; 128 | } 129 | 130 | } 131 | server { 132 | listen 80; 133 | listen [::]:80; 134 | server_name frp.sample.com; 135 | return 301 https://frp.sample.com$request_uri; 136 | } 137 | 138 | ``` 139 | 140 | #### 服务端frp dashboard Nginx配置 141 | 142 | 用```/dashboard/``` 前缀做的反向代理 143 | ```sh 144 | location /dashboard/ 145 | { 146 | proxy_pass http://localhost:7500/; 147 | proxy_http_version 1.1; 148 | proxy_set_header Upgrade $http_upgrade; 149 | proxy_set_header Connection 'upgrade'; 150 | proxy_set_header Host $host; 151 | proxy_set_header X-Forwarded-For $remote_addr; 152 | proxy_cache_bypass $http_upgrade; 153 | } 154 | ``` 155 | 156 | dashboard 登录完成后会出现前缀路径丢失变成了```frp.sample.com/static/```代理的路径应该为```frp.sample.com/dashboard/static/```基于这个原因做了路径改写 157 | ```sh 158 | location / 159 | { 160 | rewrite (.*)/static/$ $1/dashboard/static/ permanent; 161 | } 162 | ``` 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /articles/js实用小技巧.md: -------------------------------------------------------------------------------- 1 | ## js实用小技巧 2 | 3 | #### 获取数组最后一个元素 4 | ```js 5 | var array = [1, 2, 3, 4] 6 | 7 | console.log(array.slice(-1)) // [4] 8 | console.log(array.slice(-2)) // [3, 4] 9 | console.log(array.slice(-3)) // [2, 3, 4] 10 | console.log(array.slice(-4)) // [1, 2, 3, 4] 11 | ``` 12 | #### 条件短路语句 13 | ```js 14 | if (condition) { 15 | dosomething() 16 | } 17 | ``` 18 | 这时,你可以这样子运用短路: 19 | ```js 20 | condition && dosomething() 21 | ``` 22 | 23 | #### 取整 24 | 可以使用 Math.floor()、Math.ceil()或 Math.round()将浮点数转换成整数,但有另一种更快的方式 25 | ```js 26 | console.log(1.6 | 0); 27 | ``` 28 | 29 | 30 | #### 获取文件拓展名 31 | ```js 32 | var file1 = "50.xsl"; 33 | var file2 = "30.doc"; 34 | getFileExtension(file1); //returs xsl 35 | getFileExtension(file2); //returs doc 36 | 37 | function getFileExtension(filename) { 38 | /*TODO*/ 39 | } 40 | ``` 41 | - 方法一 String split 方法 42 | ```js 43 | function getFileExtension(filename) { 44 | return filename.split('.').pop(); 45 | } 46 | ``` 47 | - 方法二 String的slice、lastIndexOf方法 48 | ```js 49 | function getFileExtension3(filename) { 50 | return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2); 51 | } 52 | console.log(getFileExtension3('')); // '' 53 | console.log(getFileExtension3('filename')); // '' 54 | console.log(getFileExtension3('filename.txt')); // 'txt' 55 | console.log(getFileExtension3('.hiddenfile')); // '' 56 | console.log(getFileExtension3('filename.with.many.dots.ext')); // 'ext' 57 | ``` 58 | 59 | 如何实现的? 60 | 61 | 无符号右移操作符(>>>) 将-1转换为4294967295,这个方法可以保证边缘情况时文件名不变。 62 | 63 | #### 比较相等 64 | - == 65 | js类型隐式转换会出现意想不到的结果。 66 | ```js 67 | 0 == ' ' //true 68 | null == undefined //true 69 | [1] == true //true 70 | ``` 71 | - === 72 | 它比不全等操作符更加严格并且不会发生类型转换,但是用 === 来进行比较并不是最好的解决方案。你可能会得到: 73 | ```js 74 | NaN === NaN //false 75 | ``` 76 | - Object.is 77 | `Object.is()` 方法,它具有 === 的一些特点,而且更好、更精确。 78 | ```js 79 | Object.is(0 , ' '); //false 80 | Object.is(null, undefined); //false 81 | Object.is([1], true); //false 82 | Object.is(NaN, NaN); //true 83 | ``` 84 | 85 | #### js 逗号操作符 86 | 除了分号之外,逗号允许你在同一个地方放多个语句。 例如: 87 | ```js 88 | for(var i=0, j=0; i<5; i++, j++, j++){ 89 | console.log("i:"+i+", j:"+j); 90 | } 91 | ``` 92 | 输出 93 | ```js 94 | i:0, j:0 95 | i:1, j:2 96 | i:2, j:4 97 | i:3, j:6 98 | i:4, j:8 99 | ``` 100 | 当放一个表达式时,它由左到右计算每个表达式,并传回最右边的表达式。 101 | 102 | 例如: 103 | ```js 104 | function a(){console.log('a'); return 'a';} 105 | function b(){console.log('b'); return 'b';} 106 | function c(){console.log('c'); return 'c';} 107 | 108 | var x = (a(), b(), c()); 109 | 110 | console.log(x); // 输出「c」 111 | ``` 112 | 输出: 113 | ```js 114 | "a" 115 | "b" 116 | "c" 117 | "c" 118 | ``` 119 | 注意:逗号(,)操作符在 JavaScript 中所有的操作符里是最低的优先顺序,所以没有括号表达式时将变为:(x = a()), b(), c();。 120 | 121 | #### 函数空参数异常处理 122 | ```js 123 | function fun(a,b,c) {} 124 | 125 | fun(1,,3) 126 | ``` 127 | 报错 128 | ``` 129 | Uncaught SyntaxError: Unexpected token , 130 | ``` 131 | 可以使用数组是松散的,所以给它传空值是可以的优点。 132 | ```js 133 | fun(...[1,,3]) 134 | ``` 135 | 136 | #### 字符串拼接 137 | ```js 138 | var a = 1; 139 | var b = 2; 140 | var c = '3'; 141 | var result = ''.concat(a, b, c); //"123" 142 | ``` 143 | 144 | #### New操作符号 145 | ```js 146 | function Thing() { 147 | this.one = 1; 148 | this.two = 2; 149 | } 150 | 151 | var myThing = new Thing(); 152 | 153 | myThing.one // 1 154 | myThing.two // 2 155 | ``` 156 | 默认返回 `this` 157 | ```js 158 | function Thing() { 159 | this.one = 1; 160 | this.two = 2; 161 | 162 | return 5; 163 | } 164 | 165 | var myThing = new Thing(); 166 | ``` 167 | 返回值不变 168 | ```js 169 | function Thing() { 170 | this.one = 1; 171 | this.two = 2; 172 | 173 | return { 174 | three: 3, 175 | four: 4 176 | }; 177 | } 178 | 179 | var myThing = new Thing(); 180 | console.log(myThing); 181 | /* 182 | Object {three: 3, four: 4} 183 | */ 184 | ``` 185 | 总结: 186 | - 使用new关键字调用一个函数的时候默认会返回`this`对象; 187 | - 使用new关键字调用一个返回原始变量的函数将不会返回你指定的值,而是会返回`this`对象; 188 | - 使用new关键字调用一个返回一个非原始变量像object、array或function将会覆盖this实例,并返回那个非原始变量。 189 | 190 | -------------------------------------------------------------------------------- /articles/npm知识合集.md: -------------------------------------------------------------------------------- 1 | # npm 知识合集 2 | 3 | - ### npm换源的几种方式 4 | 1. 使用nrm 5 | - 安装 6 | ``` 7 | npm install -g nrm 8 | ``` 9 | - 列出源的列表 10 | ``` 11 | nrm ls 12 | ``` 13 | - 输出结果 14 | ``` 15 | * npm ---- https://registry.npmjs.org/ 16 | cnpm --- http://r.cnpmjs.org/ 17 | taobao - https://registry.npm.taobao.org/ 18 | nj ----- https://registry.nodejitsu.com/ 19 | rednpm - http://registry.mirror.cqupt.edu.cn/ 20 | npmMirror https://skimdb.npmjs.com/registry/ 21 | edunpm - http://registry.enpmjs.org/ 22 | ``` 23 | - 使用 24 | ``` 25 | nrm use taobao 26 | ``` 27 | 2. 改变全局的注册 28 | - 设置成淘宝源 29 | ``` 30 | npm config set registry https://registry.npm.taobao.org 31 | ``` 32 | 可使用`npm config set @myco:registry =`为不同包范围注册不同的包地址 33 | - 查看结果 34 | ``` 35 | npm config get registry 36 | ``` 37 | - 输出结果: 38 | ``` 39 | https://registry.npm.taobao.org/ 40 | ``` 41 | 测试一下(npm info [packageName] 查看指定包的详情) 42 | ``` 43 | npm info vue 44 | ``` 45 | 3. 在命令行里指定源 46 | ``` 47 | npm --registry https://registry.npm.taobao.org install [name] 48 | ``` 49 | 50 | 4. 修改全局.npmrc 文件位于 ~/.npmrc 51 | ``` 52 | registry = https://registry.npm.taobao.org 53 | 54 | ``` 55 | 为不同包范围注册不同的包地址 56 | ``` 57 | @myco:registry= 58 | registry= 59 | ``` 60 | 以@myco开头的包的注册地址是A,其余的包注册地址是B -------------------------------------------------------------------------------- /articles/v2rayU谷歌翻译.md: -------------------------------------------------------------------------------- 1 | #### V2rayU谷歌翻译 2 | 3 | ``` 4 | /Users/用户名/.V2rayU/pac 5 | 修改 proxy.js 删除 6 | "@@||translate.googleapis.com", 7 | "@@|http:\/\/translate.google.cn", 8 | 9 | gfwlist.txt 貌似不用改 proxy.js 应该是解析 gfwlist.txt 生成 10 | ``` -------------------------------------------------------------------------------- /articles/箭头跟随鼠标移动效果.md: -------------------------------------------------------------------------------- 1 | # 箭头跟随鼠标移动效果 2 | ![](../images/arrow.gif) 3 | 4 | ### canvas动画原理 5 | 1. canvas 绘制的是一张静态图片,可通过每隔一定时间清除之前图形然后重新绘制下一帧图形,用来模拟出一个动画过程,可以使用context.clearRect(0, 0, width, height)方法来刷新需要绘制的图形。 6 | 2. 通过window.requestAnimationFrame方法定时执行。 7 | 3. 基于上一章的[箭头类](https://github.com/tomatoKnightJ/Blog/issues/6)实现。 8 | 9 | 10 | ### 动画分析 11 | 12 | ![picture 10](../images/8c990bb42615590073e363faae97bf3c8265a9f9719ab8bcfec6a37126601e72.png) 13 | 14 | 15 | - 通过观察发现鼠标的位置距离箭头越远,箭头的速度则越快,反之则越慢 16 | - 速度向量与距离向量成相似三角形 17 | 18 | 获取鼠标当前在画布上的位置信息 19 | ``` 20 | function getxy(ele){ 21 | let mouse={x:0,y:0}; 22 | ele.addEventListener('mousemove',function(e){ 23 | let ev=e || window.event; 24 | let {pageX, pageY, target} = ev; 25 | let {left, top} = target.getBoundingClientRect(); 26 | let {x,y} = {x: pageX - left, y: pageY - top} 27 | mouse.x=x; 28 | mouse.y=y; 29 | }); 30 | return mouse; 31 | } 32 | ``` 33 | 通过获取鼠标的位置信息得到箭头到鼠标的xy坐标距离为dx, dy,通过勾股定理可以获取斜边长度为 `d = (Math.sqrt(dx*dx+dy*dy))`,由d乘以速度系数0.1(系数0到1之间的小数,值越大速度越快) 34 | 35 | ``` 36 | (function move(){ 37 | window.requestAnimationFrame(move); 38 | ctx.clearRect(0,0,W,H); 39 | let dx = mouse.x-arrow.x, 40 | dy = mouse.y-arrow.y, 41 | speed = (Math.sqrt(dx*dx+dy*dy))*0.9, 42 | angle = Math.atan2(dy,dx), 43 | vx = speed*Math.cos(angle), 44 | vy = speed*Math.sin(angle); 45 | // 防止箭头方向出错,当箭头到达鼠标位置时dx=dy=0,导致angle等于0 46 | if(Math.abs(dx) > 0.1 || Math.abs(dy) > 0.1) { 47 | arrow.rotation = angle; 48 | arrow.x += vx; 49 | arrow.y += vy; 50 | } 51 | arrow.render(ctx); 52 | })() 53 | ``` 54 | 源码地址:[https://github.com/tomatoKnightJ/Blog/blob/main/src/canvas/canvas%E7%AE%AD%E5%A4%B4%E8%B7%9F%E9%9A%8F%E9%BC%A0%E6%A0%87.html](https://github.com/tomatoKnightJ/Blog/blob/main/src/canvas/canvas%E7%AE%AD%E5%A4%B4%E8%B7%9F%E9%9A%8F%E9%BC%A0%E6%A0%87.html) -------------------------------------------------------------------------------- /images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/1.jpg -------------------------------------------------------------------------------- /images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/2.jpg -------------------------------------------------------------------------------- /images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/3.jpg -------------------------------------------------------------------------------- /images/3225d83ebf58d328096f1d10e24689d9df647877defb92306dd7a60538e15569.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/3225d83ebf58d328096f1d10e24689d9df647877defb92306dd7a60538e15569.png -------------------------------------------------------------------------------- /images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/4.jpg -------------------------------------------------------------------------------- /images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/5.jpg -------------------------------------------------------------------------------- /images/55403052ba6ab76211bcb76466d5daebb1223e6986c7cf53c5d60cf724b71413.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/55403052ba6ab76211bcb76466d5daebb1223e6986c7cf53c5d60cf724b71413.png -------------------------------------------------------------------------------- /images/8c990bb42615590073e363faae97bf3c8265a9f9719ab8bcfec6a37126601e72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/8c990bb42615590073e363faae97bf3c8265a9f9719ab8bcfec6a37126601e72.png -------------------------------------------------------------------------------- /images/a0630acd5b2d5efe0545bad00cb8485e52085d816b7ab4a50241980d387a1869.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/a0630acd5b2d5efe0545bad00cb8485e52085d816b7ab4a50241980d387a1869.png -------------------------------------------------------------------------------- /images/arrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/arrow.gif -------------------------------------------------------------------------------- /images/settext.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/settext.gif -------------------------------------------------------------------------------- /images/wave-line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/wave-line.gif -------------------------------------------------------------------------------- /images/wave.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinet-jutt/Blog/b3ad7d738d02e8cc06e764062ee3855e16b7fbdf/images/wave.gif -------------------------------------------------------------------------------- /src/canvas/canvas箭头跟随鼠标.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | arrow 5 | 10 | 11 | 12 | 13 | 14 | 15 | 51 | 52 | -------------------------------------------------------------------------------- /src/canvas/components/arrow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 绘制箭头类 3 | */ 4 | 5 | class Arrow { 6 | constructor(props){ 7 | this.x = 0; 8 | this.y = 0; 9 | this.w = 60; 10 | this.h = 30; 11 | this.rotation = 0; 12 | this.fillStyle = 'rgb(57, 119, 224)'; 13 | this.strokeStyle = 'rgba(0, 0, 0, 0)'; 14 | Object.assign(this, props); 15 | return this; 16 | } 17 | createPath(ctx){ 18 | let {w, h} = this; 19 | ctx.beginPath(); 20 | ctx.moveTo(-w/2, -h/2); 21 | ctx.lineTo(w/10, -h/2); 22 | ctx.lineTo(w/10, -h); 23 | ctx.lineTo(w/2, 0); 24 | ctx.lineTo(w/10, h); 25 | ctx.lineTo(w/10, h/2); 26 | ctx.lineTo(-w/2, h/2); 27 | ctx.closePath(); 28 | return this; 29 | } 30 | render(ctx){ 31 | let {fillStyle, strokeStyle, rotation, x, y} = this; 32 | ctx.save(); 33 | ctx.fillStyle = fillStyle; 34 | ctx.strokeStyle = strokeStyle; 35 | ctx.translate(x, y); 36 | ctx.rotate(rotation); 37 | this.createPath(ctx); 38 | ctx.fill(); 39 | ctx.stroke(); 40 | ctx.restore(); 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/canvas/particle.js: -------------------------------------------------------------------------------- 1 | class Particle { 2 | constructor(props) { 3 | this.x = 0; 4 | this.y = 0; 5 | this.vx = 0; 6 | this.vy = 0; 7 | this.finalX = 0; 8 | this.finalY = 0; 9 | this.r = 1; 10 | this.fillStyle = '#000'; 11 | this.strokeStyle = '#000'; 12 | Object.assign(this, props); 13 | } 14 | render(ctx) { 15 | const { x, y, r, fillStyle, strokeStyle } = this; 16 | ctx.save(); 17 | ctx.translate(x, y); 18 | ctx.fillStyle = fillStyle; 19 | ctx.strokeStyle = strokeStyle; 20 | ctx.beginPath(); 21 | ctx.arc(0, 0, r, 0, 2 * Math.PI); 22 | ctx.stroke(); 23 | ctx.fill(); 24 | ctx.restore(); 25 | return this; 26 | } 27 | } 28 | 29 | class ParticleSystem { 30 | constructor(props) { 31 | this.nextFont = this.nextFont.bind(this); 32 | this.oCanvas = document.createElement('canvas'); 33 | this.fontDataList = null; 34 | this.currentFontIndex = 0; 35 | this.font = '💕'; 36 | this.color = 'red'; 37 | this.container = null; 38 | this.width = 200; 39 | this.height = 200; 40 | this.size = 0.8; 41 | //粒子未扩散之前的范围 42 | this.range = 0.5; 43 | //粒子密度 44 | this.density = 4; 45 | Object.assign(this, props); 46 | this.init(); 47 | return this; 48 | } 49 | 50 | setCanvasSize() { 51 | const { width, height, oCanvas } = this; 52 | oCanvas.setAttribute('width', width); 53 | oCanvas.setAttribute('height', height); 54 | this.ctx = oCanvas.getContext('2d'); 55 | } 56 | 57 | setFont() { 58 | const { height, width, size, font, ctx } = this; 59 | ctx.clearRect(0, 0, width, height); 60 | ctx.fillStyle = '#000'; 61 | ctx.font = 'blod 10px Arial'; 62 | const measure = ctx.measureText(font); 63 | const lineHeight = 7; 64 | const fSize = Math.min(height * size * 10 / lineHeight, width * size * 10 / measure.width); 65 | ctx.save(); 66 | ctx.font = `bold ${fSize}px Arial`; 67 | const measureResize = ctx.measureText(font); 68 | const left = (width - measureResize.width) / 2; 69 | const bottom = (height + fSize / 10 * lineHeight) / 2; 70 | ctx.fillText(font, left, bottom); 71 | this.getImageData(); 72 | ctx.restore(); 73 | } 74 | 75 | setImage() { 76 | const { height, width, font, ctx } = this; 77 | ctx.clearRect(0, 0, width, height); 78 | ctx.drawImage(font, 0, 0, width, height); 79 | this.getImageData(); 80 | } 81 | 82 | getImageData() { 83 | const { ctx, color, range, width, height, density } = this; 84 | const data = ctx.getImageData(0, 0, width, height); 85 | const particleList = []; 86 | for (let x = 0, width = data.width, height = data.height; x < width; x += density) { 87 | for (let y = 0; y < height; y += density) { 88 | const currentX = (width - width * range) / 2 + Math.random() * width * range; 89 | const currentY = (height - height * range) / 2 + Math.random() * height * range; 90 | const i = (y * width + x) * 4; 91 | if (data.data[i + 3]) { 92 | particleList.push( 93 | new Particle({ 94 | x: currentX, 95 | y: currentY, 96 | finalX: x, 97 | finalY: y, 98 | fillStyle: color, 99 | strokeStyle: color 100 | }) 101 | ); 102 | } 103 | } 104 | } 105 | this.particleList = particleList; 106 | this.drawParticle(); 107 | } 108 | 109 | drawParticle() { 110 | const { particleList, ctx, width, height } = this; 111 | if (!particleList) return false; 112 | ctx.clearRect(0, 0, width, height); 113 | const spring = 0.01; 114 | const FRICTION = 0.88; 115 | particleList.forEach((item, index) => { 116 | item.vx += (item.finalX - item.x) * spring; 117 | item.vy += (item.finalY - item.y) * spring; 118 | item.x += item.vx; 119 | item.y += item.vy; 120 | item.vx *= FRICTION; 121 | item.vy *= FRICTION; 122 | item.render(ctx); 123 | }); 124 | } 125 | 126 | animate() { 127 | const _this = this; 128 | let startTime = +new Date(); 129 | (function move() { 130 | const { currentFontIndex, fontDataList } = _this; 131 | let endTime = +new Date(); 132 | let interTime = endTime - startTime; 133 | let lifetime = fontDataList[currentFontIndex] && fontDataList[currentFontIndex].lifetime; 134 | if (interTime >= lifetime) { 135 | _this.nextFont(); 136 | startTime = +new Date(); 137 | } 138 | _this.drawParticle(); 139 | requestAnimationFrame(move); 140 | })(); 141 | } 142 | 143 | nextFont() { 144 | const { fontDataList, loop } = this; 145 | const _this = this; 146 | Object.entries(fontDataList[this.currentFontIndex]).forEach(([ key, value ]) => { 147 | _this[key] = value || _this.defaultValue(key); 148 | }); 149 | if (typeof _this.font == 'string') _this.setFont(); 150 | if (typeof _this.font == 'object') _this.setImage(); 151 | this.currentFontIndex += 1; 152 | if (fontDataList.length <= this.currentFontIndex) { 153 | if (!loop) return false; 154 | this.currentFontIndex = 0; 155 | } 156 | } 157 | 158 | defaultValue(key) { 159 | const defaultValue = { 160 | font: '💕', 161 | color: 'red', 162 | size: 0.8 163 | }; 164 | return defaultValue[key]; 165 | } 166 | 167 | appendCanvas() { 168 | const { container, oCanvas } = this; 169 | if (!container) throw 'Need parameter container'; 170 | container.appendChild(oCanvas); 171 | } 172 | 173 | init() { 174 | this.setCanvasSize(); 175 | this.animate(); 176 | this.appendCanvas(); 177 | } 178 | } 179 | 180 | function loadImg(src) { 181 | return new Promise((res, rej) => { 182 | let oImg = new Image(); 183 | oImg.src = src; 184 | oImg.onload = () => res(oImg); 185 | oImg.onerror = (err) => rej(err); 186 | }); 187 | } 188 | 189 | (async () => { 190 | const oImg4 = await loadImg('./images/1.png'); 191 | let fontList = [ 192 | { 193 | font: '3', 194 | color: 'green', 195 | lifetime: 2000, 196 | size: 0.8, 197 | density: 5 198 | }, 199 | { 200 | font: '2', 201 | lifetime: 2000, 202 | density: 5 203 | }, 204 | { 205 | font: '1', 206 | lifetime: 2000, 207 | density: 5 208 | }, 209 | { 210 | font: oImg4, 211 | lifetime: 2000, 212 | density: 5, 213 | color: "red" 214 | }, 215 | { 216 | font: 'I ❤ YOU', 217 | lifetime: 2000, 218 | color: 'red', 219 | density: 3 220 | } 221 | ]; 222 | const container = document.getElementById('canvasContainer'); 223 | new ParticleSystem({ 224 | fontDataList: fontList, 225 | loop: true, 226 | width: 200, 227 | height: 200, 228 | size: 0.8, 229 | container 230 | }); 231 | })(); -------------------------------------------------------------------------------- /src/canvas/similarPicture.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ttj 3 | * @Github: https://github.com/tomatoKnightJ 4 | * @Description: 匹配图片的相似度 5 | * @Date: 2019-02-28 16:07:38 6 | */ 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const { createImageData, loadImage, createCanvas } = require('canvas'); 10 | const basePath = process.argv[2] || './images/'; 11 | const imgFolder = path.resolve(__dirname, basePath); 12 | console.log('basePath',basePath); 13 | console.log('imgFolder',imgFolder); 14 | const imgList = fs.readdirSync(imgFolder); 15 | // 过滤非图片文件 16 | const filterImgList = imgList.filter((item) => { 17 | const reg=/(.*)\.(jpg|bmp|gif|ico|pcx|jpeg|tif|png|raw|tga)$/; 18 | return reg.test(item); 19 | }); 20 | const canvas = createCanvas(200, 200); 21 | const ctx = canvas.getContext('2d'); 22 | 23 | async function shrinkingImg (imgList=[]) { 24 | const list = (imgList.map( async item => { 25 | const oImg = await loadImage(imgFolder+'/'+item); 26 | const imgWidth = 8; 27 | ctx.clearRect(0,0,imgWidth,imgWidth); 28 | ctx.drawImage(oImg, 0, 0, imgWidth, imgWidth); 29 | const data = ctx.getImageData(0,0,imgWidth,imgWidth); 30 | return data.data; 31 | })); 32 | console.log('list',list); 33 | 34 | return list; 35 | } 36 | 37 | async function getHashList (imgList) { 38 | const list = await shrinkingImg(imgList); 39 | const averageList = []; 40 | list.forEach(item => { 41 | const itemList = []; 42 | item.forEach((newItem, index) => { 43 | if ((index+1)%4 === 0) { 44 | const newItem1 = item[index-3]; 45 | const newItem2 = item[index-2]; 46 | const newItem3 = item[index-1]; 47 | const gray = (newItem1 + newItem2 + newItem3)/3; 48 | itemList.push(~~gray); 49 | } 50 | }); 51 | const hashData = getHash(itemList); 52 | averageList.push(hashData); 53 | }); 54 | return averageList; 55 | } 56 | 57 | function getHash (arr) { 58 | const length = arr.length; 59 | const average = arr.reduce((pre, next) => pre+next, 0)/length; 60 | return arr.map(item => item >= average ? 1 : 0).join(''); 61 | } 62 | 63 | // 编辑距离算法 64 | function strSimilarity2Number(s, t){ 65 | let n = s.length, m = t.length, d=[]; 66 | let i, j, s_i, t_j, cost; 67 | if (n == 0) return m; 68 | if (m == 0) return n; 69 | for (i = 0; i <= n; i++) { 70 | d[i]=[]; 71 | d[i][0] = i; 72 | } 73 | for(j = 0; j <= m; j++) { 74 | d[0][j] = j; 75 | } 76 | for (i = 1; i <= n; i++) { 77 | s_i = s.charAt (i - 1); 78 | for (j = 1; j <= m; j++) { 79 | t_j = t.charAt (j - 1); 80 | if (s_i == t_j) { 81 | cost = 0; 82 | }else{ 83 | cost = 1; 84 | } 85 | d[i][j] = Minimum (d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1] + cost); 86 | } 87 | } 88 | return d[n][m]; 89 | } 90 | function Minimum(a,b,c){ 91 | return a t.length ? s.length : t.length; 96 | let d = strSimilarity2Number(s, t); 97 | return (1-d/l).toFixed(4); 98 | } 99 | /** 100 | * @description: 相似度判断 101 | * @param {imgList:Array[文件列表数组], limit:Number[相似度系数]} 102 | * @return: 相似度二维数组 103 | */ 104 | async function getSimilarImgList (imgList=[], limit=0.85) { 105 | //异常处理 106 | if (!imgList.length) return []; 107 | // 获取图片索引二维数组 108 | const arr = await getHashList(imgList); 109 | const array = []; 110 | // 已经匹配的图片无需再做遍历 111 | const includeList = []; 112 | for (let index = 0,length = arr.length; index < length; index++) { 113 | const element = arr[index]; 114 | const list = []; 115 | for (let i = index+1; i < length; i++) { 116 | const elementNext = arr[i]; 117 | const percent = strSimilarity2Percent(element, elementNext); 118 | const includeItem = includeList.indexOf(i) > 0; 119 | if (percent>limit && !includeItem) { 120 | list.push(i); 121 | includeList.push(i); 122 | } 123 | } 124 | if (list.length) array.push([index,...list]); 125 | } 126 | const mappingArr = array.map(item=>{ 127 | return item.map(index => imgList[index]); 128 | }); 129 | return mappingArr; 130 | } 131 | 132 | getSimilarImgList(filterImgList, 0.99); -------------------------------------------------------------------------------- /src/canvas/utiles/tools.js: -------------------------------------------------------------------------------- 1 | let C={}; 2 | //获取相对坐标点 3 | C.getxy=function(ele){ 4 | let mouse={x:0,y:0}; 5 | ele.addEventListener('mousemove',function(e){ 6 | let ev=e || window.event; 7 | let {x,y} = C.wrapevent(ev); 8 | mouse.x=x; 9 | mouse.y=y; 10 | }); 11 | return mouse; 12 | } 13 | 14 | //坐标系转换 15 | C.wrapevent=function(ev){ 16 | let {pageX, pageY, target} = ev; 17 | let {left, top} = target.getBoundingClientRect(); 18 | return {x: pageX - left, y: pageY - top}; 19 | } 20 | // 取随机数 21 | C.random = function(a,b){ 22 | return Math.min(a,b)+Math.random()*Math.abs(b-a); 23 | } 24 | // 随机颜色 25 | C.creatColor=function(){ 26 | return '#'+(~~(Math.random()*(1<<24))).toString(16); 27 | } 28 | // 矩形碰撞检测 29 | C.rectBounceing = function(self,aim){ 30 | return (self.x+self.w>=aim.x && self.x<=aim.x+aim.w && self.y<=aim.y+aim.h && self.y+self.h>=aim.y); 31 | } 32 | //圆边界反弹 33 | C.rebound = function(ball,w,h,bounce){ 34 | if(ball.x+ball.r>w){ 35 | ball.x = w - ball.r; 36 | ball.vx *= -bounce; 37 | } 38 | if(ball.xh){ 43 | ball.y = h - ball.r; 44 | ball.vy *= -bounce; 45 | } 46 | if(ball.y 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 100 | 101 | --------------------------------------------------------------------------------