webp实践

现在网站消耗流量最大的也就是图片了,而解决图片的体积过大的终极解决方案就是Webp图片了,虽然目前浏览器中只有chrome内核支持webp,但是考虑到chrome的用户量,我们也值得单独为chrome提供webp图片。

由于我们使用的是又拍云存储图片,所以最简单的办法就是使用又拍云提供的图片处理功能,直接在链接后加上 !/format/webp,又拍云就会提供转换后的图片。还有一个支持让浏览器支持webp的插件webpjs,但是这个插件性能很差,所以并不推荐使用。

所以我写了一个简单的指令替换图片的src属性

app.directive('imgWebp', function () {
    return {
        restrict: 'AE',
        link: function ($scope, $element, $attrs) {
            $attrs.$observe('imgWebp', function (value) {
                if (supportWebP) {
                    $element[0].setAttribute('src', value + '!/format/webp');
                } else {
                    $element[0].setAttribute('src', value);
                }
            });
        }
    };
});

关键点在于如何检测浏览器对webp的支持,简单的方法可以直接根据浏览器的UV来判断,但是最稳妥的办法还是对浏览器做特征检测。

var WebP = new Image();
        WebP.onload = WebP.onerror = function(){
            window.supportWebP = WebP.height == 1;
        };
WebP.src = "data:image/webp;base64,UklGRiYAAABXRUJQVlA4IBoAAAAwAQCdASoBAAEAAAAMJaQAA3AA/v89WAAAAA==";

原理也很简单加载一个1×1的wenp图片,然后在加载完成时检测图盘的高度是否为1.

Angular 中的 $watch , $watchGroup() 与 $watchCollection()

一、$watch

在Angular中大部分指令都依靠watch函数来监听Model变化,watch也是Angular的“脏检查”的核心之一。

$watch(watchExp, listener, objectEquality)

1.watchExp 很灵活,可以是一个$scope上的一个属性名,也可以是一个函数或一个字符串形式的表达式;
2.listener 是检测到变化时执行的函数,这个函数接受 newValue, oldValue 两个参数;
3.objectEquality 是一个可选布尔值,决定比较的深浅,默认是false,此时 watch 使用 === 进行浅比较,所以它对数组或 Object 进行比较是检查的是引用,也叫导致了数组或对象内容改变是并不会触发回调,同时两个内容相同的表达式也会判定为不同。当此项设置为 true 是,watch 会用 angular.equals() 进行深比较,这种比较方法会遍历数组或对象,这也导致了性能会比较差。

二、$watchGroup() 与 $watchCollection()

因为 watch 深比较性能较差,所以 Angular 还提供了 $watchGroup([watchExp], listener)$watchCollection(obj, listener) 方法来分别监听数组和对象。
$watchGroup 其实是使用 watch 监听一组 watchExp ,所以 watchGroup 不支持深比较
$watchCollection 比 $watch 进一步,但是基于性能考虑它只向内关注 1 层,对数组重新赋值,或是对数组元素进行新增、删除、修改时,回调会被调用,注意只要是修改就会调用,如果给数组赋的值和之前一样也会触发回调。如果某个数组元素内部的某个属性被更新时,回调不会被调用。

参考阅读:
1.在 AngularJS 中用 $watch / $watchCollection 监听数组变化的研究
2.Angular文档:$rootScope.Scope
3.How to use a $watchGroup with object equality or deep-watch the properties in the group?

ES6 中的 Set 和 Map

一、Set

set类似于数组,但是成员的值都是唯一的,没有重复的值。在向Set加入值的时候,不会发生类型转换。
为了与Map结构保持一致,Set结构也有keys和entries方法,这时每个值的键名就是键值。

属性:
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set的成员总数。

方法:
add(value):添加某个值,返回Set结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。

var s = new Set();

[2,3,5,4,5,2,2].map(x => s.add(x))

for (i of s) {console.log(i)}
// 2 3 4 5

二、Map

Map 类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应。

属性和方法
size:返回成员总数。
set(key, value):设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。
get(key):读取key对应的键值,如果找不到key,返回undefined。
has(key):返回一个布尔值,表示某个键是否在Map数据结构中。
delete(key):删除某个键,返回true。如果删除失败,返回false。
clear():清除所有成员,没有返回值。

遍历
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。

let map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

let map1 = new Map(
  [...map0].filter(([k, v]) => k < 3) 
);
// 产生Map结构 {1 => 'a', 2 => 'b'}

let map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
    );
// 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'}

三、应用场景

当你的元素或者键值有可能不是字符串时,无条件地使用Map和Set。 有移除操作的需求时,使用Map和Set。
当仅需要一个不可重复的集合时,使用Set优先于普通对象,而不要使用{foo: true}这样的对象。
当需要遍历功能时,使用Map和Set,因为其可以简单地使用for..of进行遍历。
因此,事实上仅有一种情况我们会使用普通的对象,即使用普通对象来表达一个仅有增量Map,且这个Map的键值是字符串。

注:文章摘自 ES6入门 使用ES6进行开发的思考

动态改变动画效果

动画效果一般有两种方法实现:transtion 和 animation

但是这些动画都是基于css实现的,也就是说我们单纯的用他们只能得到固定的动画。当遇到较为复杂的动画,每次需要的效果都不一样怎么办?我们举个常见的例子吧,抽奖的指针,我们为了让指针效果更逼真,需要随机出一个角度没让指针从 0 转到这个角度并逐渐减速。

一、transtion

transtion 动画需要触发,所以用这个实现最简单,只需要我们动态改变 transtion 的结束状态就可以了,如例子中的第二个。

二、animation

但是我们知道对于一些复杂的动画 transtion 就不合适了,这是我们会使用 animation 来定义关键帧,所以 animation 动画效果更复杂,但同时动态修改关键帧也更为复杂。具体实现方法可以看我的例子中的第一种,和拓展阅读,当然对于抽奖动画这种方法小题大做了。

<p data-height="350" data-theme-id="light" data-slug-hash="xOEQbO" data-default-tab="js,result" data-user="zhangchen915" data-embed-version="2" class="codepen">See the Pen 动态改变动画效果 by zhangchen (@zhangchen915) on CodePen.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>

拓展阅读:
Using JavaScript to set @keyframes in CSS animations

单行文字两端对齐

关于两端对齐,有两个属性
text-align: justify;
text-align-last: justify;
两端对齐其实对最后一行是不起作用的,在word中也是这样,其实就是排版需要。如果需要对最后一行设置两端对齐可以使用text-align-last但是这个属性 Chrome45 以下和 Safari 都不支持,这就麻烦了。

所以,对于单行文本我们只能用加一个伪元素让其成为文本的最后一行。

div.after{
  content: "";
  display: inline-block;
  width: 100%;
}

但是你发现除了IE,其他浏览器并没有起作用,所以这里还有一个坑,大部分浏览器两端对齐的实现都是通过调整字之间的空格大小来达成的,所以还要在每个单词和汉字间都插入一个空格,这样两端对其才会生效。

所以可见,高级浏览器都不太鸟单行文本两端对齐,也不知道设计师怎么想的,非要在输入框的 lable 弄个两端对齐......

参考阅读:
Web前端实验室 » CSS3 justify