fox的博客

github地址 -> https://github.com/FoxDaxian


  • Home

  • Archives

proxy简析

Posted on 2021-05-09 In 原理

Proxy

使用proxy,你可以把老虎伪装成猫的外表,这有几个例子,希望能让你感受到proxy的威力。
proxy 用来定义自定义的基本操作行为,比如查找、赋值、枚举性、函数调用等。

proxy接受一个待代理目标对象和一些包含元操作的对象,为待代理目标创建一个‘屏障’,并拦截所有操作,重定向到自定义的元操作对象上。

proxy通过

Proxy```来创建,接受两个参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 待代理目标对象
2. 元操作对象

闲话少说,直接看例子。

#### 最简单的只代理一个方功能,在这个例子里,我们让```get```操作,永远返回一个固定的值

```javascript
let target = {
name: 'fox',
age: 23
}
let handler = {
get: (obj, k) => 233
}
target = new Proxy(target, handler);
target.a // 233
target.b // 233
target.c // 233

无论你

'x')```都会返回233
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
当然,代理```get```仅仅是其中一种操作,还有:
- [get](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get)
- [set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/set)
- [has](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/has)
- [apply](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply)
- [construct](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/construct)
- [ownKeys](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/ownKeys)
- [deleteProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/deleteProperty)
- [defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/defineProperty)
- [isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/isExtensible)
- [preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/preventExtensions)
- [getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getPrototypeOf)
- [setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/setPrototypeOf)
- [getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor)

#### 改变默认值为0
在其他语言中,如果访问对象中没有的属性,默认会返回0,这在某些场景下很有用,很方便,比如坐标系,一般来说z轴默认是0.
但是在js中,对象中不存在的key的默认值是undefined,而不是合法的初始值。
不过可以使用proxy解决这个问题
```javascript
const defaultValueObj = (target, defaultValue) => new Proxy(target, {
get: (obj, k) => Reflect.has(obj, k) ? obj[k] : defaultValue
})

建议根据不同类型返回不同的默认值,Number => 0 String => ‘’ Object => {} Array => []等等

数组负索引取值

js中,获取数组的最后一个元素是相对麻烦的,容易出错的。这就是为什么TC39提案定义一个方便的属性,

1
2
3
4
5
6
其他语言比如python,和ruby提供了访问数组最后一个元素的方法,例如使用arr[-1]代替arr[arr.length - 1]
不过,我们有proxy,负索引在js中也可以实现。
```javascript
const negativeArray = els => new Proxy(els, {
get: (target, k) => Reflect.get(target, +k < 0 ? String(target.length + +k) : k)
})

需要注意的一点是,get操作会字符串化所有的操作,所以我们需要转换成number在进行操作,
这个运用也是

1
2
3
4
5
6
7
8
9
10

#### 隐藏属性
js未能实现私有属性,尽管之后引入了```Symbol```去设置独一无二的属性,但是这个被后来的```Object.getOwnPropertySumbols```淡化了
长期以来,人们使用下划线_来表示属性的私有,这意味着不运行外部操作该属性。不过,proxy提供了一种更好的方法来实现类似的私有属性
```javascript
const enablePrivate = (target, prefix = '_') => new Proxy(target, {
has: (obj, k) => (!k.startsWith(prefix) && k in obj),
ownKeys: (obj, k) => Reflece.ownKeys(obj).filter(k => (typeof k !== 'string' || !k.startsWith(prefix))),
get: (obj, k, rec) => (k in rec) ? obj[k] : undefined
})

结果

1
2
3
4
5
6
7
8
9
let userData = enablePrivate({
firstName: 'Tom',
mediumHandle: '@tbarrasso',
_favoriteRapper: 'Drake'
})

userData._favoriteRapper // undefined
('_favoriteRapper' in userData) // false
Object.keys(userData) // ['firstName', 'mediumHandle']

如果你打印该proxy代理对象,会在控制台看到,不过无所谓。

缓存失效

服务端和客户端同步一个状态可能会出现问题,这很常见,在整个操作周期内,数据都有可能被改变,并且很难去掌握需要重新同步的时机。
proxy提供了一种新的办法,可以让属性在必要的时候失效,所有的访问操作,都会被检查判断,是否返回缓存还是进行其他行为的响应。

1
2
3
4
5
6
7
const timeExpired = (target, ttl = 60) => {
const created_at = Date.now();
const isExpired = () => (Date.now - created_at) > ttl * 1000;
return new Proxy(tarvet, {
get: (target, k) => isExpired() ? undefined : Reflect.get(target, k);
})
}

上面的功能很简单,他在一定时间内正常返回访问的属性,当超出ttl时间后,会返回undefined。

1
2
3
4
5
6
7
8
9
let timeExpired = ephemeral({
balance: 14.93
}, 10)

console.log(bankAccount.balance) // 14.93

setTimeout(() => {
console.log(bankAccount.balance) // undefined
}, 10 * 1000)

上面的例子会输出undefined在十秒后,更多的骚操作还请自行斟酌。

只读

尽管

1
2
3
4
5
6
7
8
9
10
11
```javascript
const nope = () => {
throw new Error('不能改变只读属性')
}
const read_only = (obj) => new Proxy(obj, {
set: nope,
defineProperty: nope,
deleteProperty: nope,
preentExtensions: nope,
setPrototypeOf: nope
});

枚举

结合上面的只读方法

1
2
3
4
5
6
7
8
const createEnum = (target) => read_only(new Proxy(target, {
get: (obj, k) = {
if (k in obj) {
return Reflect.get(obj, k)
}
throw new ReferenceError(`找不到属性${k}`)
}
}))

我们得到了一个对象,如果你访问不存在的属性,不会得到undefined,而是抛出一个指向异常错误,折让调试变得更方便。
这也是一个代理代理的例子,需要保证被代理的代理是一个合法的代理对象,这个有助于混合一些复杂的功能。

重载操作符

最神奇的可能就是重载某些操作符了,比如使用

1
2
3
4
5
in用来判断指定的属性是否指定对象或者对象的原型链上,这种行为可以很优雅的被重载,比如创建一个用于判断目标数字是否在制定范围内的代理
```javascript
const range = (min, max) => new Proxy(Object.create(null), {
has: (obj, k) => (+k > min && +k < max)
})

1
2
3
4
5
6
7
8
const X = 10.5
const nums = [1, 5, X, 50, 100]

if (X in range(1, 100)) { // true
// ...
}

nums.filter(n => n in range(1, 10)) // [1, 5]

上面的例子,虽然不是什么复杂的操作,也没有解决什么复杂的问题,但是这种清晰,可读,可复用的方式相信也是值得推崇的。
当然除了in操作符,还有delete 和 new;

其他

  • 兼容性一般,不过谷歌开发的proxy-polyfill目前已经支持get、set、apply、construct到ie9了
  • 目前浏览器没有办法判断对象是否被代理,不过在node版本10以上,可以使用util.types.isProxy来判断
  • proxy的第一个参数必须是对象,不能代理原始值
  • 性能,proxy的一个缺点就是性能,但是这个也因人/浏览器而异,不过,proxy绝对不适合用在性能关键点的代码上,当然,你可以衡量proxy带来的遍历和可能损耗的性能,进行合理的中和,来达到最佳的开发体验和用户体验

为什么margin:auto会居中?

Posted on 2021-05-09 In 原理

根本原因

auto值的计算是基于可用空间的,也就是只有块级非替换元素才会有剩余空间,这也就解释了为什么有些行内元素使用position:absolute;也会居中,因为absolute和float会悄悄的把元素编成block元素呀

如果是块级元素,他会填充父级所有的可用空间,如图:

gif

如果是替换元素的话,就不会产生这样的效果,如图:
gif

大概的原理是,如果块级非内联元素定位为非当前内容流,比如absolute,那么该元素不会和普通内容流一起渲染,如果设置

1
2
3
4
5
6
x{
top: 0;
bottom: 0;
left: 0;
right: 0;
}

那么该元素会填充父级所有的可用空间,当然他没有width、height优先级高,所以没有宽高会充满,有宽高会水平垂直居中

根据eslint自动格式化

Posted on 2021-05-09 In 效率

prettier + eslint

  1. 重置 vscode 的配置,采用.prettierrc 文件
  2. 编写 eslint 规则和.eslintignore
  3. 添加 package.json 的 lint 相关的命令
  4. 增加 prettier-eslint-cli,配置 format 命令
  5. 集成所有命令

prettier-eslin 优先读取 eslint 的配置,如果被禁用那么去读 .prettierrc

demo

1
2
3
4
5
6
7
8
9
// .prettierrc
{
"trailingComma": "none",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"bracketSpacing": false
}
1
2
3
4
5
6
7
8
9
"scripts": {
"lint": "./node_modules/.bin/eslint .",
"format": "./node_modules/.bin/prettier-eslint --write \"utils/**/*.js\" \"components/**/*.?(vue|js)\""
},
"husky": {
"hooks": {
"pre-commit": "npm run format && git add ."
}
}

eslint 配置文件参数说明

微信分享功能实战

Posted on 2021-05-09 In 原理
  1. 引入wx的npm(weixin-js-sdk)包
  2. 判断是否为微信或qq环境
  3. 接口请求微信配置相关参数,注意接口传入的参数须和当前分享页的地址相同(不包括#以及后面的)
  4. 配置需要的微信jsapi相关信息
  5. wx.ready => wx.checkJsApi 检查api可行性
  6. 判断并调用wx相关方法
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
import wx from 'weixin-js-sdk';

export default ({分享配置参数} = {}) => {
if (weixin || qq) {
getWxConfApi.then(() => {
const wxApiData = {
forFriend: 'updateAppMessageShareData',
forGroup: 'updateTimelineShareData'
};
const wxApi = Object.values(wxApiData);
wx.config({
debug: process.env.NODE_ENV === 'development',
appId, // 必填,公众号的唯一标识
timestamp, // 必填,生成签名的时间戳
nonceStr, // 必填,生成签名的随机串
signature, // 必填,签名
jsApiList: wxApi // 必填,需要使用的JS接口列表
});
wx.ready(() => {
wx.checkJsApi({
jsApiList: wxApi,
success: function (res) {
for (let i = 0, len = wxApi.length; i < len; i++) {
switch (api检查通过 && wxApi[i]) {
case wxApiData.forFriend:
// 调用分享给朋友wx api
break;
case wxApiData.forGroup:
// 调用分享给朋友圈wx api
break;
default:
console.log('部分功能不支持,请使用最新版微信');
break;
}
}
}
});
});
wx.error(res => {
console.log("配置初始化错误");
});
});
}
};

微信分享本地测试方法

  1. 获取微信平台安全域名,比如test.com
  2. 修改本地host配置
    1. xxx.xxx.xx.xx test.com(xxx.xxx.xx.xx你的ip地址,mac下使用ifconfig | grep “inet “ | grep -v 127.0.0.1获取)
  3. nginx代理test.com:80 => localhost:port(你的本地服务,locate nginx.conf查看nginx配置文件在哪)
  4. 手机连接和电脑相同的wifi,连接charles
  5. 手机扫码打开h5,即可进行微信分享

一些问题

  1. 签名错误,按照以下顺序查找问题
    1. 检查签名是否正确校验地址
    2. 检查wx.config的字段大小写拼写是否正确
    3. 确定url的完整性,仅仅不包括#以及#后面的
    4. 确认 config 中的 appid 与用来获取 jsapi_ticket 的 appid 一致。
    5. 检查获取wx config字段的接口的url是否和当前待分享页url一致,并且属于安全域名
  2. 配置好本地测试后,如果遇到其他接口504 gate way time-out,请检查配置的host时候与api地址冲突

js数组中的漏洞

Posted on 2021-05-09 In 原理

这边文章介绍了js中各个数组方法是如何处理数组中有空元素的

  1. 运行实例
    所有的方法都基于下面这个数组:
    1
    var arr = ['a', , 'b']

如你所见,我们创建一个数组,第一位是a,第二位是空,第三位是b

  1. 预热

    1. 数组最后一个位置是空
      在本文中,需要注意的是,js会忽略数组中最后的逗号,这意味着,如果你想在数组的最后添加空元素,你需要增加两个逗号:

      1
      2
      3
      4
      > ['a', 'b', ]
      [ 'a', 'b' ]
      > ['a', 'b', , ]
      [ 'a', 'b', empty ]
    2. 数组复制
      我们使用sclice创建一个arr的浅克隆副本

      1
      var arr2 = arr.slice();

    右边部分等价于:

    1
    arr.slice(0, arr.length);
  2. 数组方法对待空元素的表现

  • foreach会跳过空元素

    1
    2
    3
    4
    5
    6
    > arr.forEach(function(x, i) {
    console.log(`${i}.${x}`);
    })
    // 输出
    0.a
    2.b
  • every和some也会跳过

    1
    2
    3
    > arr.every(function (x) { return x.length === 1 })
    // 输出
    true
  • map会跳过空元素的那次,但是会保留空元素的位置

    1
    2
    3
    > arr.map(function (x,i) { return i+'.'+x })
    // 输出
    [ '0.a', , '2.b' ]
  • filter会移除空元素

    1
    2
    3
    > arr.filter(function (x) { return true })
    // 输出
    [ 'a', 'b' ]
  • join会转换undefined和空元素为空的字符串:’’

    1
    2
    3
    4
    5
    6
    > arr.join('-')
    // 输出
    'a--b'
    > [ 'a', undefined, 'b' ].join('-')
    // 输出
    'a--b'
  • 其他方法都会保留空元素

  1. 循环
    for循环不访问数组,for-ion循环正确的列出了所有的键值

    1
    2
    3
    4
    5
    6
    7
    8
    > var arr2 = arr.slice()
    > arr2.foo = 123;
    > for(var key in arr2) { console.log(key) }
    // 输出
    0
    2
    foo
    解释:因为数组是对象,所以会打印对象中的key,其他则会忽略非数字key
  2. Function.prototype.apply()
    apply方法将空元素看做undefined,这样提供了一个非常简单的方法去创建一个由undefined组成的特定长度的数组。

    1
    2
    3
    4
    // 元素为undefined
    > Array.apply(null, Array(3))
    // 输出
    [ undefined, undefined, undefined ]

如果不指定this,那么:

1
2
3
4
// 空元素
> Array(3)
// 输出
[ , , , , ]

apply很容易的插入空元素到数组中,但是,你不能使用它执行任意数组,因为这些数组可能包含也可能不包含空元素,例如,任意数组[2]不包含空元素,apply应该返回’unchangede’,但是还是创建了一个长度为2的数组,因为Array()解释数组为数组长度,而不是数组元素

1
2
3
> Array.apply(null, [2])
// 输出
[ , ,]
  1. 总结和建议
    我们看到js有很多处理漏洞的方式,不过庆幸的是,我们一般不需要关心他是如何处理的,因为这种漏洞应该被避免,漏洞会造成性能问题,几乎百害无一用
  2. 扩展阅读
    稀疏数组和密集数组
    js迭代数组和对象
    原文链接

console介绍

Posted on 2021-05-09 In 介绍

工作中console可能是用来debug的最频繁的原生方法了,它不仅仅非常实用,而且还有一些有趣的东西。

  • console.log

    1. 常见占位符

      %o:接受Object
      %s:接受String
      %d:接受Number

      图片

    2. css占位符
      %c:接受CSS

      图片


  • console.dir
    1. 打印对象的时候与console.log无明显区别,但是当打印元素的时候有明显区别,如图:
      图片
      log会展示当前节点以及子孙节点,dir会展示节点的所有属性

  • console.warn
    1. 在输出上与console.log不同,它的输出为黄色警告样式

  • console.table
    1. 输出漂亮的格式化后的数组,并且提供排序功能,有可选的第二个数组参数,过滤想要展示的key。不过该方法仅仅能展示最多1000行,所以不适合处理数据量大的数组。如图:
      图片

  • console.assert
    1. 有至少两个参数,第一个参数为条件,当条件为真的时候,无任何输出,当条件为false的时候,像console.log一样输出之后的所有参数

  • console.count
    1. 记录调用的次数,接受一个参数作为输出前缀,可以通过console.countReset ()重置

  • console.trace
    1. 输出堆栈跟踪记录,如图
      图片

  • console.time & console.timeEnd
    1. 记录js操作花费了多长时间,接受一个参数,该参数在两个方法里必须都一致才能输出计算时间,单位为毫秒,如图
      图片

  • console.group & console.groupEnd
    1. 在控制台创建新的缩进列,以group开始,以groupEnd接受,group接受一个参数,来表示缩进前缀,如图
      图片

总结

console对象上还有一些其他方法,这里就不过多介绍了,以上方法,用的做多的仅仅只有console.log,其他的偶会会用到,不过知道了真的就是知道了。

浏览器内核

Posted on 2021-05-09 In 原理

浏览器是用户的窗口,而这个窗口则是前端工程师们的舞台。
据统计,目前常见的浏览器有:

  • 谷歌
  • 火狐
  • ie、edge
  • opera
  • safari
  • uc
  • qq
  • 搜狗
  • 360
  • 百度

浏览器占比

对于前端来说,浏览器是一台转换代码的机器,将代码转换成用户可以看到的界面,但是,如何解释和执行代码却因为浏览器的种类繁多而千差万别。而决定如何解释和执行代码的就是浏览器内核(排版引擎 | 浏览器引擎 | 页面渲染引擎 | 渲染引擎)。

浏览器结构

  1. 用户界面
    你能看到的,包括工具栏、导航栏、书签菜单等等
  2. 浏览器引擎
    是一个可嵌入的组件,为渲染引擎提供高级接口。
    可以加载一个指定的URI,支持和历史记录相关的操作
    查询和修改渲染引擎
  3. 渲染引擎
    显示请求的内容,比如解析html、css,并将解析后的render tree渲染到浏览器中
  4. 网络
    用于网络调用,具有独立性,可在不同平台使用
  5. js解释器
    用来解释执行js代码,由于安全问题,浏览器引擎和渲染引擎可能会禁止掉某些js功能。
  6. xml解析器
    将xml文档解析成文档对象模型树,xml解析器是浏览器结构中复用最多的子系统之一,几乎所有的浏览器都复用现有的xml解析器,而不是从头开始
  7. 显示后端
    显示后端提供绘图和窗口原语,包括用户界面控件集合、字体集合
  8. 数据持久层
    数据持久层将会话相关的各种数据存储在硬盘上。比如书签,工具栏设置,比如cookies,安全证书,localstore

一般来说,浏览器只有一个内核,但是为了ie,发明了双核浏览器,大部分都是

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
webkit是苹果研发的,chrome起初使用webkit,后来另起分支blink,并且越来越好
[视频介绍](https://www.youtube.com/watch?v=DQPqZPTIESc&t=8s)

接着说说移动端的浏览器内核。

首先移动端不像pc,网站都是前端开发的。移动端目前来说常用的有下面几种:

| | native | rn、wx、flutter | hybrid | webapp |
| -- | -- | -- | -- | -- |
| 跨平台 | 复杂 | 容易 | 容易 | 容易 |
| 版本更新 | 慢 | 快 | 快 | 快 |
| 性能和体验 | 最高 | 高 | 一般 | 一般 |
| 学习和开发成本 | 很大 | 大 | 一般 | 一般 |

这里拿和浏览器内核有关的```hybrid```和```webapp```来说,其余感兴趣的在做深入了解。

介绍移动端内核之前,简单介绍一下hybrid和webapp,hybrid是native的webview容器中的webapp,webapp就是普通的页面。正因为有这个webview的关系,导致hybrid自诞生以来就比webapp多了一个初始化webview容器的这个损耗,所以才会有后面的rn,flutter等等。简单的解释下这个过程:

在浏览器中,我们输入地址时,浏览器就可以加载页面。

![浏览器结构](https://raw.githubusercontent.com/FoxDaxian/FoxDaxian.github.io/master/assets/07_browser_webview/borwser.png)

在客户端中,首先需要花费时间初始化webview,才能加载页面。所以如果webview没有加载完毕,用户可能就会看到白屏影响体验。

![浏览器结构](https://raw.githubusercontent.com/FoxDaxian/FoxDaxian.github.io/master/assets/07_browser_webview/webview.png)

当然,也不是没有优化方案。比如:
- 使用单例模式去初始化webview,这样只有在第一次的时候回慢一些,之后就没问题了,缺点就是内存消耗和怎么处理同时存在多个webview
- 将网络请求相关交给native去做,也就是初始化webview和网络请求并行的方案,即客户端代理数据请求
- dns采用和客户端api相同的域名,类似dns预解析



我们都知道手机有两大主类:Android和IOS。不过总的来说大部分都在使用webkit或者类webkit内核。

webapp:
- ios safari 使用webkit内核
- android4.4之前使用webkit,4.4之后切换到了chromium(基于一个webkit的分支blink,是chrome的先行版)


webview(webkit内核+js引擎):
- ios8之前使用uiwebview,之后使用wkwebview
- 安卓常用的有:

安卓4.4之前的webkit内核和4.4之后的blink内核:

| | webkit | chromium | 备注 |
| -- | -- | -- | -- |
| html5test | 278 | 434 | http://html5test.com/ |
| 远程调试 | 不支持 | 支持 | 安卓4.4以上支持 |
| 内存占用 | 小 | 大 | 相差20-30M左右 |
| WebAudio | 不支持 | 支持 | Android 5.0及以上支持 |
| WebGL | 不支持 | 支持 | Android 5.0及以上支持 |
| WebRTC | 不支持 | 支持 | Android 5.0及以上支持 |


由于低版本安卓的有很多兼容的问题,所以一般会采用三方webview,常见的:

| 类别 | 介绍 | 性能 | 特点 | html5test |
| -- | -- | -- | -- | -- |
| 系统自带webview | 安卓默认 | 一般 | 没有额外jar负担,原生api,不过兼容性差 | 一般 |
| X5 webview | 腾讯出品 | 一般 | 兼容性好,可信度高,不过工作量会增大,并且不支持Cordova | 良好 |
| crosswalk | 国外为安卓提供的一个融合webkit的方案 | 优 | 无兼容性,性能问题,支持Cordova,不过体积大 | 较佳 |


两个优化方案。
### 首先是webp

3w方法:
1. 什么是webp
2. webp是怎么做的
3. 如何使用webp




#### 什么是webp
webp是一种可以有损或无损的压缩图片的方法,能够适用于有很多图片资源的网站上,它还供你选择压缩程度,让你自由控制图片大小和图片质量,在不影响质量的前提下,大约可以节省30%的体积,详细见:[官网比较](https://developers.google.com/speed/webp/docs/c_study)
#### webp是怎么做的
我们知道,在大部分网站中,图片占用了60%左右的页面资源,这非常影响整个渲染时间,而且,在移动端页面大小更是重要,这关系到流量和电池寿命。那webp是怎么做的呢?

1. 有损压缩
有损webp基于VP8视频编码方法来压缩图像数据,基础步骤类似于JPEG压缩,流程如下图所示,红色部门代表与JPEG不同的部分。
![alt](https://raw.githubusercontent.com/FoxDaxian/FoxDaxian.github.io/master/assets/07_browser_webview/webpProcess.jpg)
1. 格式转换
若压缩前图像数据为RGB格式,则需要先将格式转换成YUV格式,Y表示亮度,UV表示色度,之所以这么做是因为人类的眼睛对色度敏感度比亮度低,所以可通过减少色度来减少图片占用空间。比如每四个相邻像素点采用一对UV值
2. 分割宏块
将数据分割成4 * 4或8 * 8 或 16 * 16的宏块
3. 预测编码
预测编码的原理是基于前面编码好的宏块,预测多余的动作颜色等信息,属于帧内预测(帧内预测是一个物理学术语,指的是H.264采用的一种新技术)
- H_PRED(horizontal prediction)使用宏块左边的一列(简称L)来填充剩余列
- V_PRED(vertical prediction) 使用宏块上面的一行(简称A)来填充剩余行
- DC_PRED(DC prediction) 使用L和A的所有像素的平均值填充宏块
- TM_PRED(TrueMotion prediction) 使用渐进的方式
下面展示了不同帧内预测的模式
![alt](https://raw.githubusercontent.com/FoxDaxian/FoxDaxian.github.io/master/assets/07_browser_webview/compression-intra_modes.png)
4. 接下来通过FDCT(正向离散余弦变换)处理残差。让变换后的数据低频部分分布在数据块左上方,高频集中在右下方以实现更高效的压缩
5. 最后将结果量化并进行熵编码,webp使用布尔算数编码作为熵编码方式,直接把输入的消息编码为一个满足(0.0 <= n <= 1.0)的小数n

2. 无损压缩
无损webp采用了预测变换、颜色变换、减去绿色变换、彩色缓存编码、LZ77反响参考等不同技术来处理图像,之后对变换图像数据和参数进行熵编码,有兴趣的自行了解。

#### 如何使用webp
首先是支持度:
pc上谷歌、火狐、edge、opera支持度很好,换句话说使用blink内核的都没啥问题。
android4以上 ios支持过一段时间,后来移除了,web端支持尚好,不过ie和safari完全不支持
详见[wiki](https://en.wikipedia.org/wiki/WebP#Support)
其实,目前有很多垫片已经几乎可以做到常用环境全支持webp了,比如
- [安卓4.0以下支持](https://github.com/alexey-pelykh/webp-android-backport)
- [ios支持](https://github.com/carsonmcdonald/WebP-iOS-example)
不过这些是native那边做的,那我们前端怎么做呢?
- 服务端
1. 通过accetp头信息进行协商
发送accept字段非常常见,这个字段说明浏览器可以接受的内容类型,如果浏览器发送一个*image/webp*,那么服务端就知道浏览器支持webp格式,然后直接使用ok

- 客户端
1. [Modernizr](https://github.com/Modernizr/Modernizr)
Modernizr是一个js库,可以非常方便的查看你的浏览器H5/Css3的支持度,你可以查看下面这些字段:Modernizr.webp, Modernizr.webp.lossless, Modernizr.webp.alpha and Modernizr.webp.animation
2. 使用H5的**picture**元素
picture允许你放置多个source和一个回退的img,就算webp不支持,也会显示回退方案
3. 使用js
这是尝试解码一个非常小的不同版本webp图片
```javascript
// check_webp_feature:
// 'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'.
// 'callback(feature, result)' will be passed back the detection result (in an asynchronous way!)
function check_webp_feature(feature, callback) {
var kTestImages = {
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
};
var img = new Image();
img.onload = function () {
var result = (img.width > 0) && (img.height > 0);
callback(feature, result);
};
img.onerror = function () {
callback(feature, false);
};
img.src = "data:image/webp;base64," + kTestImages[feature];
}

再说说variable fonts

之前就有了解,直接抛链接了: variable fonts
支持程度

参考链接

现代浏览器工作原理
浏览器内核
前端解读webview
美团webview性能、体验分析和优化
主流浏览器内核介绍
安卓webview选择对比
webp官网
webp原理

css实现表单验证

Posted on 2021-05-09 In 原理

有没有办法只通过css来确定input标签是否有输入?

我有这个想法是因为我想完成一个自动补全的input部件,最基本的功能是:

  • 如果input没有内容,这隐藏下拉框
  • 反之,显示下拉框

我找到了一个也许不是很完美的实现方案,描述中可能会有一些细微的区别,不过我还是很希望能做这个简单的分享


首先,我们构造一个简单的form表单,仅仅只有一个input

1
2
3
4
<form>
<label for="input">输入框</label>
<input type="text" id="input"/>
</form>

当输入一些值,我设置input的边框颜色为绿的,下面是一个例子:
gif

判断input是否为空

我通过html表单验证去判断是否为空,所以,这里我使用了

1
2
3
4
5
6
7
8
9
```css
<form>
<label> Input </label>
<input type="text" name="input" id="input" required />
</form>
// valid:当input输入值也合法值时采用的样式
#input:valid{
border-color: green;
}

这时,当有输入的时候,input表现的很好,边框颜色也有了相应的变化:

gif

但是,这里有个问题,如果用户输入的是空格,那么边框颜色也会发生改变。

gif

原理上看,这种表现是正常的,因为输入框确实有了内容。
但是,实际上,我不想让空格来触发自动补全弹窗
所以这还不能满足我们的需求,我要做更细致的检查

进一步完善

html提供我们利用正则去验证输入框内容的属性:

1
2
3
4
5
6
7

因为想把空格视为非法输入,我使用```\S+```,这个很简单,匹配一个或者多个任何非空白字符
```css
<form>
<label> Input </label>
<input type="text" name="input" id="input" required pattern="\S+"/>
</form>

使用这种方式,的确奏效了,如果用户输入空格,输入框没有任何变化

gif

但了个是,但是这个正则还是有问题,因为只允许输入非空白字符,所以你在任何位置输入空白都会导致输入框校验失败

gif

这里可以使用其他的正则来匹配,比如

1
2
3
4
5
```css
<form>
<label> Input </label>
<input type="text" name="input" id="input" required pattern="\S+.*"/>
</form>

现在输入框可以和空格混合输入了!
但是如果当前校验失败,输入框没有任何提示,这很不友好!
但我写这篇文章的时候,有一个问题我不断思考,能不能只用css给非法验证也加一种样式?

输入无效

这里不能使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

查看了相关资源,我们可以使用```:placeholder-shown```来达到我们的目的

大概思路是:
- 增加placeholder
- 如果输入框如果用户输入了内容,但还不合法做一个处理
- 最后利用css的覆盖特性,添加一个当验证成功的样式处理

最终的css大概是这样
```css
/* 当填充的时候展示红色,所以这里默认是校验失败 */
input:not(:placeholder-shown) {
border-color: hsl(0, 76%, 50%);
}
/* 当验证成功的时候,采用这个样式 */
input:valid {
border-color: hsl(120, 76%, 50%);
}

这里有一个小小的demo

总结

上面的内容就是如何只用css来提供一个基础表单验证功能,说是只用css,其实也利用的pattern能接受正则表达式,哈哈,所以最根本的是如何写出最优的正则表达式。

原文链接

css实现伪随机数

Posted on 2021-05-09 In 黑魔法

前言时间急,任务重,直接上原理和代码。

原理和问题

前提是需要label和input,这样才能让css知道当前选中了哪个,也就是checked属性。
然后利用z-index的keyframes实现一个有规律但是delay各不相同的animation。

alt

有一个小问题是,浏览器触发click和press事件的前提是,mousedown 和 mouseup都作用于同一个element对象才行,你可以自己实践一下,就是在一个可点击的元素上,按下鼠标左键,然后鼠标移出这个可点击的元素,松开鼠标左键,看看会不会触发这个元素本身的行为。
这个问题会导致一种看起来点击失效的问题,不过我们可以通过点击重置element的position行为,然后提供一个z-index最高的可视区mask,这样就保证了我们mouseup和mousedown始终在同一个element对象上了。

代码

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
* {
user-select: none;
}

@keyframes zindex {
0% {
z-index: 9;
}

100% {
z-index: 1;
}
}

.randomBox {
height: 100px;
}

.randomBox>label {
top: 100px;
left: 8px;
width: 200px;
height: 100px;
box-sizing: border-box;
border: 1px solid red;
position: absolute;
}

.randomBox .one {
animation: zindex 0.3s 0s infinite;
}

.randomBox .two {
animation: zindex 0.3s -0.1s infinite;
}

.randomBox .three {
animation: zindex 0.3s -0.2s infinite;
}

input {
position: absolute;
visibility: hidden;
}

.value {
margin-top: 50px;
}

input#one:checked~.value .one {
background-color: red;
}

input#two:checked~.value .two {
background-color: red;
}

input#three:checked~.value .three {
background-color: red;
}

label:active {
position: static;
margin-left: -1000px;
}

label:active::before {
content: "";
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 10;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<h2>css实现随机数</h2>
<input type="radio" id="one" name="random">
<input type="radio" id="two" name="random">
<input type="radio" id="three" name="random">

<div class="randomBox">
<label for="one" class="one">随机一个数</label>
<label for="two" class="two">随机一个数</label>
<label for="three" class="three">随机一个数</label>
</div>
<div class="value">
<div class="one">1</div>
<div class="two">2</div>
<div class="three">3</div>
</div>
<!-- 下面的js用来验证点击是否按照预期触发 -->
<script>
const labels = document.querySelectorAll('label');
labels.forEach((label, index) => label.addEventListener('click', function (e) {
console.log(`${index}被点击了`);
}))
</script>

总结

上面就实现了一个最简单的css随机数,当然这个效果可能不是很明显,如果更复杂就会更逼真,比如这个例子


参考

css shapes

Posted on 2021-05-09 In 介绍

css shapes可以让css来生成一些几何图形,这种特性可以和float布局结合,让文字不再仅仅只能围绕在元素的矩形元素周围,还可以围绕在圆形、椭圆形、多边形甚至png图片内所示形状的元素盒子周围。
12
实现css shapes的主要属性是shape-outside,这个属性定义了文字可以围绕的一些几何形状。

常用的一些属性:

  • circle(): 圆形
  • ellipse(): 椭圆形
  • inset(): 类似阴影的inset
  • olygon(): 自定义多边形

被应用的元素必须是float的,并且应该具有宽高属性

具体细节请参考这篇文章:参考链接


附注:文中提及的CSS Shapes Editor的使用方法:

  1. 随便https或者https协议服务网站
  2. 打开开发者工具
  3. 选择一个你想要的形状元素,选择Styles那一行的shapes的选项卡
    12
  4. 点击+号,页面会出现编辑样式得虚线框和操作提示
  5. 进行拖拽,平移,旋转操作
  6. 被选中元素会出现ploygon相关属性,复制下来即可使用
12…4

FoxDaxian

遇到好玩的,稀奇的,新鲜的,记录下来,分享出去。
40 posts
11 categories
4 tags
© 2021 FoxDaxian
Powered by Hexo v3.9.0
|
Theme – NexT.Muse v7.2.0