李昌义

李昌义 查看完整档案

成都编辑成都大学  |  计算机科学与技术 编辑  |  填写所在公司/组织 blog.lcylove.cn 编辑
编辑

新手,请大佬多多指教。。

个人动态

李昌义 回答了问题 · 9月24日

mongo可以对数组顺序查询吗?

设定有序的 id 集合是 ids1;
然后用 $in 把所有数据都查出来,ids2;
所以此时的问题,变成了根据 ids1 完成对 ids2 进行排序;
然后

const result = ids1.filter(id => ids2.includes(id));

关注 3 回答 2

李昌义 回答了问题 · 9月23日

解决一个promise面试题

const MAX_N = 10;

let n = MAX_N;

let currentRunLen = MAX_N;

const promiseTask = (promises) => {

    if (n < currentRunLen || !promises.length) {

        return;

    }

    currentRunLen = Math.min(currentRunLen, promises.length);

    n = 0;

    promises.splice(0, currentRunLen).forEach((p) => {

        p().then((i) => {

            console.log(`resolve:${i}`);

        }).catch((i) => {

            console.log(`reject:${i}`);

        }).finally(() => {

            n += 1;

            promiseTask([...promises]);

        });
    });

};

// 测试
const promises = Array.from({ length: 20 }).map((o, i) => () => new Promise((resolve, reject) => {
    setTimeout(() => {

        if (Math.random() < 0.5) {

            resolve(i);

        } else {

            reject(i);

        }

    }, 1000);

}));

promiseTask(promises);

不知道思否这代码格式化这么回事,你复制到编辑器中格式化一下吧!!
image.png

关注 4 回答 4

李昌义 赞了文章 · 5月13日

一篇文章说清浏览器解析和CSS(GPU)动画优化

相信不少人在做移动端动画的时候遇到了卡顿的问题,这篇文章尝试从浏览器渲染的角度;一点一点告诉你动画优化的原理及其技巧,作为你工作中优化动画的参考。文末有优化技巧的总结。

因为GPU合成没有官方规范,每个浏览器的问题和解决方式也不同;所以文章内容仅供参考。

浏览器渲染

提高动画的优化不得不提及浏览器是如何渲染一个页面。在从服务器中拿到数据后,浏览器会先做解析三类东西:

  • 解析html,xhtml,svg这三类文档,形成dom树。

  • 解析css,产生css rule tree。

  • 解析js,js会通过api来操作dom tree和css rule tree。

解析完成之后,浏览器引擎会通过dom tree和css rule tree来构建rendering tree:

  • rendering tree和dom tree并不完全相同,例如:<head></head>或display:none的东西就不会放在渲染树中。

  • css rule tree主要是完成匹配,并把css rule附加给rendering tree的每个element。

在渲染树构建完成后,

  • 浏览器会对这些元素进行定位和布局,这一步也叫做reflow或者layout。

  • 浏览器绘制这些元素的样式,颜色,背景,大小及边框等,这一步也叫做repaint。

  • 然后浏览器会将各层的信息发送给GPU,GPU会将各层合成;显示在屏幕上。

渲染优化原理

如上所说,渲染树构建完成后;浏览器要做的步骤:

reflow——》repaint——》composite

reflow和repaint

reflow和repaint都是耗费浏览器性能的操作,这两者尤以reflow为甚;因为每次reflow,浏览器都要重新计算每个元素的形状和位置。

由于reflow和repaint都是非常消耗性能的,我们的浏览器为此做了一些优化。浏览器会将reflow和repaint的操作积攒一批,然后做一次reflow。但是有些时候,你的代码会强制浏览器做多次reflow。例如:

var content = document.getElementById('content');
content.style.width = 700px;
var contentWidth = content.offsetWidth;
content.style.backgound = 'red';

以上第三行代码,需要浏览器reflow后;再获取值,所以会导致浏览器多做一次reflow。

下面是一些针对reflow和repaint的最佳实践:

  • 不要一条一条地修改dom的样式,尽量使用className一次修改。

  • 将dom离线后修改

    • 使用documentFragment对象在内存里操作dom。

    • 先把dom节点display:none;(会触发一次reflow)。然后做大量的修改后,再把它显示出来。

    • clone一个dom节点在内存里,修改之后;与在线的节点相替换。

  • 不要使用table布局,一个小改动会造成整个table的重新布局。

  • transform和opacity只会引起合成,不会引起布局和重绘。

从上述的最佳实践中你可能发现,动画优化一般都是尽可能地减少reflow、repaint的发生。关于哪些属性会引起reflow、repaint及composite,你可以在这个网站找到https://csstriggers.com/

composite

在reflow和repaint之后,浏览器会将多个复合层传入GPU;进行合成工作,那么合成是如何工作的呢?

假设我们的页面中有A和B两个元素,它们有absolute和z-index属性;浏览器会重绘它们,然后将图像发送给GPU;然后GPU将会把多个图像合成展示在屏幕上。

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 30px;
 top: 30px;
 z-index: 2;
}

#b {
 z-index: 1;
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

clipboard.png

我们将A元素使用left属性,做一个移动动画:

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 10px;
 top: 10px;
 z-index: 2;
 animation: move 1s linear;
}

#b {
 left: 50px;
 top: 50px;
 z-index: 1;
}

@keyframes move {
 from { left: 30px; }
 to { left: 100px; }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

在这个例子中,对于动画的每一帧;浏览器会计算元素的几何形状,渲染新状态的图像;并把它们发送给GPU。(你没看错,position也会引起浏览器重排的)尽管浏览器做了优化,在repaint时,只会repaint部分区域;但是我们的动画仍然不够流畅。

因为重排和重绘发生在动画的每一帧,一个有效避免reflow和repaint的方式是我们仅仅画两个图像;一个是a元素,一个是b元素及整个页面;我们将这两张图片发送给GPU,然后动画发生的时候;只做两张图片相对对方的平移。也就是说,仅仅合成缓存的图片将会很快;这也是GPU的优势——它能非常快地以亚像素精度地合成图片,并给动画带来平滑的曲线。

为了仅发生composite,我们做动画的css property必须满足以下三个条件:

  • 不影响文档流。

  • 不依赖文档流。

  • 不会造成重绘。

满足以上以上条件的css property只有transform和opacity。你可能以为position也满足以上条件,但事实不是这样,举个例子left属性可以使用百分比的值,依赖于它的offset parent。还有em、vh等其他单位也依赖于他们的环境。

我们使用translate来代替left

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 10px;
 top: 10px;
 z-index: 2;
 animation: move 1s linear;
}

#b {
 left: 50px;
 top: 50px;
 z-index: 1;
}

@keyframes move {
 from { transform: translateX(0); }
 to { transform: translateX(70px); }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

浏览器在动画执行之前就知道动画如何开始和结束,因为浏览器没有看到需要reflow和repaint的操作;浏览器就会画两张图像作为复合层,并将它们传入GPU。

这样做有两个优势:

  • 动画将会非常流畅

  • 动画不在绑定到CPU,即使js执行大量的工作;动画依然流畅。

看起来性能问题好像已经解决了?在下文你会看到GPU动画的一些问题。

GPU是如何合成图像的

GPU实际上可以看作一个独立的计算机,它有自己的处理器和存储器及数据处理模型。当浏览器向GPU发送消息的时候,就像向一个外部设备发送消息。

你可以把浏览器向GPU发送数据的过程,与使用ajax向服务器发送消息非常类似。想一下,你用ajax向服务器发送数据,服务器是不会直接接受浏览器的存储的信息的。你需要收集页面上的数据,把它们放进一个载体里面(例如JSON),然后发送数据到远程服务器。

同样的,浏览器向GPU发送数据也需要先创建一个载体;只不过GPU距离CPU很近,不会像远程服务器那样可能几千里那么远。但是对于远程服务器,2秒的延迟是可以接受的;但是对于GPU,几毫秒的延迟都会造成动画的卡顿。

浏览器向GPU发送的数据载体是什么样?这里给出一个简单的制作载体,并把它们发送到GPU的过程。

  • 画每个复合层的图像

  • 准备图层的数据

  • 准备动画的着色器(如果需要)

  • 向GPU发送数据

所以你可以看到,每次当你添加transform:translateZ(0)will-change:transform给一个元素,你都会做同样的工作。重绘是非常消耗性能的,在这里它尤其缓慢。在大多数情况,浏览器不能增量重绘。它不得不重绘先前被复合层覆盖的区域。

隐式合成

还记得刚才a元素和b元素动画的例子吗?现在我们将b元素做动画,a元素静止不动。

clipboard.png

和刚才的例子不同,现在b元素将拥有一个独立复合层;然后它们将被GPU合成。但是因为a元素要在b元素的上面(因为a元素的z-index比b元素高),那么浏览器会做什么?浏览器会将a元素也单独做一个复合层!

所以我们现在有三个复合层a元素所在的复合层、b元素所在的复合层、其他内容及背景层。

一个或多个没有自己复合层的元素要出现在有复合层元素的上方,它就会拥有自己的复合层;这种情况被称为隐式合成。

浏览器将a元素提升为一个复合层有很多种原因,下面列举了一些:

  • 3d或透视变换css属性,例如translate3d,translateZ等等(js一般通过这种方式,使元素获得复合层)

  • <video><iframe><canvas><webgl>等元素。

  • 混合插件(如flash)。

  • 元素自身的 opacity和transform 做 CSS 动画。

  • 拥有css过滤器的元素。

  • 使用will-change属性。

  • position:fixed。

  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

这看起来css动画的性能瓶颈是在重绘上,但是真实的问题是在内存上:

内存占用

使用GPU动画需要发送多张渲染层的图像给GPU,GPU也需要缓存它们以便于后续动画的使用。

一个渲染层,需要多少内存占用?为了便于理解,举一个简单的例子;一个宽、高都是300px的纯色图像需要多少内存?

300 300 4 = 360000字节,即360kb。这里乘以4是因为,每个像素需要四个字节计算机内存来描述。

假设我们做一个轮播图组件,轮播图有10张图片;为了实现图片间平滑过渡的交互;为每个图像添加了will-change:transform。这将提升图像为复合层,它将多需要19mb的空间。800 600 4 * 10 = 1920000。

仅仅是一个轮播图组件就需要19m的额外空间!

在chrome的开发者工具中打开setting——》Experiments——》layers可以看到每个层的内存占用。如图所示:

clipboard.png

clipboard.png

GPU动画的优点和缺点

现在我们可以总结一下GPU动画的优点和缺点:

  • 每秒60帧,动画平滑、流畅。

  • 一个合适的动画工作在一个单独的线程,它不会被大量的js计算阻塞。

  • 3D“变换”是便宜的。

缺点:

  • 提升一个元素到复合层需要额外的重绘,有时这是慢的。(即我们得到的是一个全层重绘,而不是一个增量)

  • 绘图层必须传输到GPU。取决于层的数量和传输可能会非常缓慢。这可能让一个元素在中低档设备上闪烁。

  • 每个复合层都需要消耗额外的内存,过多的内存可能导致浏览器的崩溃。

  • 如果你不考虑隐式合成,而使用重绘;会导致额外的内存占用,并且浏览器崩溃的概率是非常高的。

  • 我们会有视觉假象,例如在Safari中的文本渲染,在某些情况下页面内容将消失或变形。

优化技巧

避免隐式合成

  • 保持动画的对象的z-index尽可能的高。理想的,这些元素应该是body元素的直接子元素。当然,这不是总可能的。所以你可以克隆一个元素,把它放在body元素下仅仅是为了做动画。

  • 将元素上设置will-change CSS属性,元素上有了这个属性,浏览器会提升这个元素成为一个复合层(不是总是)。这样动画就可以平滑的开始和结束。但是不要滥用这个属性,否则会大大增加内存消耗。

动画中只使用transform和opacity

如上所说,transform和opacity保证了元素属性的变化不影响文档流、也不受文档流影响;并且不会造成repaint。
有些时候你可能想要改变其他的css属性,作为动画。例如:你可能想使用background属性改变背景:

<div class="bg-change"></div>
.bg-change {
  width: 100px;
  height: 100px;
  background: red;
  transition: opacity 2s;
}
.bg-change:hover {
  background: blue;
}

在这个例子中,在动画的每一步;浏览器都会进行一次重绘。我们可以使用一个复层在这个元素上面,并且仅仅变换opacity属性:

<div class="bg-change"></div>
<style>
.bg-change {
  width: 100px;
  height: 100px;
  background: red;
}
.bg-change::before {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  background: blue;
  opacity: 0;
  transition: opacity 20s;
}
.bg-change:hover::before {
  opacity: 1;
}
</style>

减小复合层的尺寸

看一下两张图片,有什么不同吗?

clipboard.png

这两张图片视觉上是一样的,但是它们的尺寸一个是39kb;另外一个是400b。不同之处在于,第二个纯色层是通过scale放大10倍做到的。

<div id="a"></div>
<div id="b"></div>

<style>
#a, #b {
 will-change: transform;
}

#a {
 width: 100px;
 height: 100px;
}

#b {
 width: 10px;
 height: 10px;
 transform: scale(10);
}
</style>

对于图片,你要怎么做呢?你可以将图片的尺寸减少5%——10%,然后使用scale将它们放大;用户不会看到什么区别,但是你可以减少大量的存储空间。

用css动画而不是js动画

css动画有一个重要的特性,它是完全工作在GPU上。因为你声明了一个动画如何开始和如何结束,浏览器会在动画开始前准备好所有需要的指令;并把它们发送给GPU。而如果使用js动画,浏览器必须计算每一帧的状态;为了保证平滑的动画,我们必须在浏览器主线程计算新状态;把它们发送给GPU至少60次每秒。除了计算和发送数据比css动画要慢,主线程的负载也会影响动画; 当主线程的计算任务过多时,会造成动画的延迟、卡顿。

所以尽可能地使用基于css的动画,不仅仅更快;也不会被大量的js计算所阻塞。

优化技巧总结

  • 减少浏览器的重排和重绘的发生。

  • 不要使用table布局。

  • css动画中尽量只使用transform和opacity,这不会发生重排和重绘。

  • 尽可能地只使用css做动画。

  • 避免浏览器的隐式合成。

  • 改变复合层的尺寸。

参考

GPU合成主要参考:

https://www.smashingmagazine....

哪些属性会引起reflow、repaint及composite,你可以在这个网站找到:

https://csstriggers.com/

查看原文

赞 101 收藏 154 评论 9

李昌义 回答了问题 · 5月8日

解决react管理系统如何更好的保存回显查询数据

将筛选的数据放在地址栏,比如: path?name=a&age=18等等这样。然后点入详情在退回来的时候在根据页面的url初始化state,然后在请求数据。。这样还有一个好处就是,你可以把url发给其他,这样其他人可以看到你的相关筛选。

关注 3 回答 2

李昌义 回答了问题 · 5月8日

页面阻止了点击事件,怎么恢复?

没看懂什么问题,可以写个小demo出来看下,是什么被阻止了。以前搞百度文库复制的时候,可以写类似下面的代码阻止:

document.body.addEventListener('click', e => {
      e.stopImmediatePropagation();
    }, true);

关注 3 回答 3

李昌义 回答了问题 · 2019-10-12

直接用比较运算符比较时间是否会有副作用

个人认为没有必要,如果你的时间格式是YYYY-MM-DD HH:mm:ss 的话,首先位数是一样的前提下,那么,最新的时间肯定是有一个位是大于旧的时间,那么大于肯定是正确的,比如"2020-01-01 00:00:00" > "2019-12-31 23:59:59"肯定是true
题目中:"9000">"10000"是因为比较第一位9大于1,如果位数相同的话,那么就是"09000" > "10000"false。所以保证位数相同很重要

关注 2 回答 1

李昌义 回答了问题 · 2019-10-11

今天在做商品规格属性时需要写一个排列组合算法,请大佬帮忙看看

const dealTwo = (arr1, arr2) => {
    let result = [];
    arr1.forEach(val1 => {
        arr2.forEach(val2 => {
            if (typeof val1 === 'number') {
                result.push([val1, val2]);
            } else {
                result.push(val1.concat(val2));
            }
        })
    })
    return result;
}
const permutation = (arr) => {
    const len = arr.length;
    if (len <= 1) {
        return arr;
    }
    let result = arr[0];
    for (let i = 1; i < len; i++) {
        result = dealTwo(result, arr[i]);
    }
    return result;
}
console.log(permutation([[1, 2], [3, 4], [5, 6]]));

思路就是2个2个的进行遍历合并

关注 4 回答 4

李昌义 回答了问题 · 2019-10-11

关于图表x轴显示多余数据的问题

const data = [];
const random = (min, max) => {
    return Math.round(Math.random() * (max - min)) + min;
}
for (let hour = 12; hour < 14; hour++) {
    for (let m = 0; m < 60; m+= 5) {
        data.push({
            date: `2019-10-11 ${hour}:${m}`,
            value:  random(1, 100)
        })
    }
}
const parseData = (data) => {
    let temp = [];
    data.forEach(({date, value}) => {
        // 将 分钟数 分开
        const [s, m] = date.split(':');
        const x = parseInt(Number(m) / 15);
        const key = s + x;
        const index = temp.findIndex(item => item.key === key);
        if (index > -1) {
            temp[index].value += value;
        } else {
            temp.push({
                key,
                value,
                date: s + `:${x * 15}`
            })
        }
    })
    return temp;
}
console.log(data);
console.log(parseData(data))

不知道你的数据结构是什么样子的,我自己模拟了一个数据结构,你大概可以借鉴一下。思路就是,把分钟数提出来,然后除以15分开,这样就每隔15分钟就区分出来了,然后数据累加就行

关注 2 回答 1

李昌义 回答了问题 · 2019-10-11

nginx配置HTTPS后,无法使用HTTS访问?

网页返回的错误信息是什么?是404吗?还是服务器没有响应等等。修改了配置之后需要重启nginx。
其次你这里好像没有指定root的目录,就是说你要挂载网页的目录

关注 2 回答 1

李昌义 回答了问题 · 2019-10-10

解决一个正则判断

const reg = /^((中|中华|中国|汗)\d+)+$/

关注 4 回答 4

认证与成就

  • 获得 89 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-09-20
个人主页被 765 人浏览