<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>UI on I Code It</title>
    <link>https://icodeit.org/categories/ui/</link>
    <description>Recent content in UI on I Code It</description>
    <generator>Hugo -- gohugo.io</generator>
    <lastBuildDate>Wed, 08 Feb 2017 00:00:00 +0000</lastBuildDate><atom:link href="https://icodeit.org/categories/ui/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>前端页面性能调优</title>
      <link>https://icodeit.org/2017/02/frontend-page-performance-tuning/</link>
      <pubDate>Wed, 08 Feb 2017 00:00:00 +0000</pubDate>
      
      <guid>https://icodeit.org/2017/02/frontend-page-performance-tuning/</guid>
      <description>Web页面的性能 我们每天都会浏览很多的Web页面，使用很多基于Web的应用。这些站点看起来既不一样，用途也都各有不同，有在线视频，Social Media，新闻，邮件客户端，在线存储，甚至图形编辑，地理信息系统等等。虽然有着各种各样的不同，但是相同的是，他们背后的工作原理都是一样的：
 用户输入网址 浏览器加载HTML/CSS/JS，图片资源等 浏览器将结果绘制成图形 用户通过鼠标，键盘等与页面交互  这些种类繁多的页面，在用户体验方面也有很大差异：有的响应很快，用户很容易就可以完成自己想要做的事情；有的则慢慢吞吞，让焦躁的用户在受挫之后拂袖而去。毫无疑问，性能是影响用户体验的一个非常重要的因素，而影响性能的因素非常非常多，从用户输入网址，到用户最终看到结果，需要有很多的参与方共同努力。这些参与方中任何一个环节的性能都会影响到用户体验。
 宽带网速 DNS服务器的响应速度 服务器的处理能力 数据库性能 路由转发 浏览器处理能力  早在2006年，雅虎就发布了提升站点性能的指南，Google也发布了类似的指南。而且有很多工具可以和浏览器一起工作，对你的Web页面的加载速度进行评估：分析页面中资源的数量，传输是否采用了压缩，JS、CSS是否进行了精简，有没有合理的使用缓存等等。
如果你需要将这个过程与CI集成在一起，来对应用的性能进行监控，我去年写过一篇相关的博客。
本文打算从另一个角度来尝试加速页面的渲染：浏览器是如何工作的，要将一个页面渲染成用户可以看到的图形，浏览器都需要做什么，哪些过程比较耗时，以及如何避免这些过程（或者至少以更高效的方式）。
页面是如何被渲染的 说到性能优化，规则一就是：
 If you can&amp;rsquo;t measure it, you can&amp;rsquo;t improve it. - Peter Drucker
 根据浏览器的工作原理，我们可以分别对各个阶段进行度量。
图片来源：http://dietjs.com/tutorials/host#backend
像素渲染流水线  下载HTML文档 解析HTML文档，生成DOM 下载文档中引用的CSS、JS 解析CSS样式表，生成CSSOM 将JS代码交给JS引擎执行 合并DOM和CSSOM，生成Render Tree 根据Render Tree进行布局layout（为每个元素计算尺寸和位置信息） 绘制（Paint）每个层中的元素（绘制每个瓦片，瓦片这个词与GIS中的瓦片含义相同） 执行图层合并（Composite Layers）  使用Chrome的DevTools - Timing，可以很容易的获取一个页面的渲染情况，比如在Event Log页签上，我们可以看到每个阶段的耗时细节（清晰起见，我没有显示Loading和Scripting的耗时）：
上图中的Activity中，Recalculate Style就是上面的构建CSSOM的过程，其余Activity都分别于上述的过程匹配。
应该注意的是，浏览器可能会将Render Tree分成好几个层来分别绘制，最后再合并起来形成最终的结果，这个过程一般发生在GPU中。
Devtools中有一个选项：Rendering - Layers Borders，打开这个选项之后，你可以看到每个层，每个瓦片的边界。浏览器可能会启动多个线程来绘制不同的层/瓦片。
Chrome还提供一个Paint Profiler的高级功能，在Event Log中选择一个Paint，然后点击右侧的Paint Profiler就可以看到其中绘制的全过程：</description>
      <content:encoded><![CDATA[<h2 id="web页面的性能">Web页面的性能</h2>
<p>我们每天都会浏览很多的Web页面，使用很多基于Web的应用。这些站点看起来既不一样，用途也都各有不同，有在线视频，<code>Social Media</code>，新闻，邮件客户端，在线存储，甚至图形编辑，地理信息系统等等。虽然有着各种各样的不同，但是相同的是，他们背后的工作原理都是一样的：</p>
<ul>
<li>用户输入网址</li>
<li>浏览器加载<code>HTML/CSS/JS</code>，图片资源等</li>
<li>浏览器将结果绘制成图形</li>
<li>用户通过鼠标，键盘等与页面交互</li>
</ul>
<p><img loading="lazy" src="/images/2017/02/internet-resized.png" type="" alt=""  /></p>
<p>这些种类繁多的页面，在用户体验方面也有很大差异：有的响应很快，用户很容易就可以完成自己想要做的事情；有的则慢慢吞吞，让焦躁的用户在受挫之后拂袖而去。毫无疑问，性能是影响用户体验的一个非常重要的因素，而影响性能的因素非常非常多，从用户输入网址，到用户最终看到结果，需要有很多的参与方共同努力。这些参与方中任何一个环节的性能都会影响到用户体验。</p>
<ul>
<li>宽带网速</li>
<li>DNS服务器的响应速度</li>
<li>服务器的处理能力</li>
<li>数据库性能</li>
<li>路由转发</li>
<li>浏览器处理能力</li>
</ul>
<p>早在2006年，雅虎就发布了提升站点性能的<a href="https://developer.yahoo.com/performance/rules.html">指南</a>，Google也发布了类似的<a href="https://developers.google.com/speed/docs/insights/rules">指南</a>。而且有很多工具可以和浏览器一起工作，对你的Web页面的加载速度进行评估：分析页面中资源的数量，传输是否采用了压缩，JS、CSS是否进行了精简，有没有合理的使用缓存等等。</p>
<p>如果你需要将这个过程与CI集成在一起，来对应用的性能进行监控，我去年写过一篇<a href="http://icodeit.org/2016/02/performance-testing-in-ci/">相关的博客</a>。</p>
<p>本文打算从另一个角度来尝试加速页面的渲染：浏览器是如何工作的，要将一个页面渲染成用户可以看到的图形，浏览器都需要做什么，哪些过程比较耗时，以及如何避免这些过程（或者至少以更高效的方式）。</p>
<h2 id="页面是如何被渲染的">页面是如何被渲染的</h2>
<p>说到性能优化，<strong>规则一</strong>就是：</p>
<blockquote>
<p>If you can&rsquo;t measure it, you can&rsquo;t improve it. - Peter Drucker</p>
</blockquote>
<p>根据浏览器的工作原理，我们可以分别对各个阶段进行度量。</p>
<p><img loading="lazy" src="/images/2017/02/how-browser-works-frame-resized.png" type="" alt=""  /></p>
<p><em>图片来源：http://dietjs.com/tutorials/host#backend</em></p>
<h3 id="像素渲染流水线">像素渲染流水线</h3>
<ol>
<li>下载HTML文档</li>
<li>解析HTML文档，生成<code>DOM</code></li>
<li>下载文档中引用的CSS、JS</li>
<li>解析CSS样式表，生成<code>CSSOM</code></li>
<li>将JS代码交给JS引擎执行</li>
<li>合并DOM和CSSOM，生成<code>Render Tree</code></li>
<li>根据<code>Render Tree</code>进行布局<code>layout</code>（为每个元素计算尺寸和位置信息）</li>
<li>绘制（Paint）每个层中的元素（绘制每个瓦片，瓦片这个词与GIS中的瓦片含义相同）</li>
<li>执行图层合并（Composite Layers）</li>
</ol>
<p>使用Chrome的DevTools - Timing，可以很容易的获取一个页面的渲染情况，比如在<code>Event Log</code>页签上，我们可以看到每个阶段的耗时细节（清晰起见，我没有显示<code>Loading</code>和<code>Scripting</code>的耗时）：</p>
<p><img loading="lazy" src="/images/2017/02/timeline-resized.png" type="" alt="Timeline"  /></p>
<p>上图中的Activity中，<code>Recalculate Style</code>就是上面的构建<code>CSSOM</code>的过程，其余Activity都分别于上述的过程匹配。</p>
<p>应该注意的是，浏览器可能会将Render Tree分成好几个层来分别绘制，最后再合并起来形成最终的结果，这个过程一般发生在<code>GPU</code>中。</p>
<p>Devtools中有一个选项：<code>Rendering - Layers Borders</code>，打开这个选项之后，你可以看到每个层，每个瓦片的边界。浏览器可能会启动多个线程来绘制不同的层/瓦片。</p>
<p><img loading="lazy" src="/images/2017/02/layer-and-tile-resized.png" type="" alt="Layers and Tiles"  /></p>
<p>Chrome还提供一个<code>Paint Profiler</code>的高级功能，在<code>Event Log</code>中选择一个<code>Paint</code>，然后点击右侧的<code>Paint Profiler</code>就可以看到其中绘制的全过程：</p>
<p><img loading="lazy" src="/images/2017/02/paint-in-detial-resized.png" type="" alt="Paint in detail"  /></p>
<p>你可以拖动滑块来看到随着时间的前进，页面上元素被逐步绘制出来了。我录制了一个我的知乎活动页面的视频，不过需要翻墙。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/gley7VZFx_I" frameborder="0" allowfullscreen></iframe>
<h3 id="常规策略">常规策略</h3>
<p>为了尽快的让用户看到页面内容，我们需要快速的完成<code>DOM+CSSOM - Layout - Paint - Composite Layers</code>的整个过程。一切会阻塞DOM生成，阻塞CSSOM生成的动作都应该尽可能消除，或者延迟。</p>
<p>在这个前提下，常见的做法有两种：</p>
<h4 id="分割css">分割CSS</h4>
<p>对于不同的浏览终端，同一终端的不同模式，我们可能会提供不同的规则集：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">media</span> <span class="nt">print</span> <span class="p">{</span>
	<span class="nt">html</span> <span class="p">{</span>
		<span class="k">font-family</span><span class="p">:</span> <span class="s1">&#39;Open Sans&#39;</span><span class="p">;</span>
		<span class="k">font-size</span><span class="p">:</span> <span class="mi">12</span><span class="kt">px</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="p">@</span><span class="k">media</span> <span class="nt">orientation</span><span class="p">:</span><span class="nd">landscape</span> <span class="p">{</span>
	<span class="o">//</span>
<span class="p">}</span>
</code></pre></div><p>如果将这些内容写到统一个文件中，浏览器需要下载并解析这些内容（虽然不会实际应用这些规则）。更好的做法是，将这些内容通过对<code>link</code>元素的<code>media</code>属性来指定：</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;print.css&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">media</span><span class="o">=</span><span class="s">&#34;print&#34;</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">link</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;landscape.css&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">media</span><span class="o">=</span><span class="s">&#34;orientation:landscape&#34;</span><span class="p">&gt;</span>
</code></pre></div><p>这样，<code>print.css</code>和<code>landscape.css</code>的内容不会阻塞Render Tree的建立，用户可以更快的看到页面，从而获得更好的体验。</p>
<h4 id="高效的css规则">高效的CSS规则</h4>
<h5 id="css规则的优先级">CSS规则的优先级</h5>
<p>很多使用<code>SASS/LESS</code>的开发人员，太过分的喜爱嵌套规则的特性，这可能会导致复杂的、无必要深层次的规则，比如：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">#</span><span class="nn">container</span> <span class="p">{</span>
	<span class="err">p</span> <span class="err">{</span>
		<span class="err">.title</span> <span class="err">{</span>
			<span class="err">span</span> <span class="err">{</span>
				<span class="k">color</span><span class="p">:</span> <span class="mh">#f3f3f3</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="err">}</span>
	<span class="err">}</span>
<span class="err">}</span>
</code></pre></div><p>在生成的CSS中，可以看到：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">#</span><span class="nn">container</span> <span class="nt">p</span> <span class="p">.</span><span class="nc">title</span> <span class="nt">span</span> <span class="p">{</span>
  <span class="k">color</span><span class="p">:</span> <span class="mh">#f3f3f3</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>而这个层次可能并非必要。CSS规则越复杂，在构建Render Tree时，浏览器花费的时间越长。CSS规则有自己的优先级，不同的写法对效率也会有影响，特别是当规则很多的时候。这里有一篇关于<a href="https://css-tricks.com/specifics-on-css-specificity/">CSS规则优先级</a>的文章可供参考。</p>
<h5 id="使用gpu加速">使用GPU加速</h5>
<p>很多动画都会定时执行，每次执行都可能会导致浏览器的重新布局，比如：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">keyframes</span> <span class="nt">my</span> <span class="p">{</span>
	<span class="nt">20</span><span class="o">%</span> <span class="p">{</span>
		<span class="k">top</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
	<span class="p">}</span>
	
	<span class="nt">50</span><span class="o">%</span> <span class="p">{</span>
		<span class="k">top</span><span class="p">:</span> <span class="mi">120</span><span class="kt">px</span><span class="p">;</span>
	<span class="p">}</span>
	
	<span class="nt">80</span><span class="o">%</span> <span class="p">{</span>
		<span class="k">top</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>这些内容可以放到GPU加速执行（GPU是专门设计来进行图形处理的，在图形处理上，比CPU要高效很多）。可以通过使用<code>transform</code>来启动这一特性：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">keyframes</span> <span class="nt">my</span> <span class="p">{</span>
	<span class="nt">20</span><span class="o">%</span> <span class="p">{</span>
		<span class="k">transform</span><span class="p">:</span> <span class="nb">translateY</span><span class="p">(</span><span class="mi">10</span><span class="kt">px</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="nt">50</span><span class="o">%</span> <span class="p">{</span>
		<span class="k">transform</span><span class="p">:</span> <span class="nb">translateY</span><span class="p">(</span><span class="mi">120</span><span class="kt">px</span><span class="p">);</span>
	<span class="p">}</span>
		
	<span class="nt">80</span><span class="o">%</span> <span class="p">{</span>
		<span class="k">transform</span><span class="p">:</span> <span class="nb">translateY</span><span class="p">(</span><span class="mi">10</span><span class="kt">px</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div><h4 id="异步javascript">异步JavaScript</h4>
<p>我们知道，JavaScript的执行会阻塞DOM的构建过程，这是因为JavaScript中可能会有DOM操作：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;div&#39;</span><span class="p">);</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="s1">&#39;200px&#39;</span><span class="p">;</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">color</span> <span class="o">=</span> <span class="s1">&#39;blue&#39;</span><span class="p">;</span>

<span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">element</span><span class="p">);</span>
</code></pre></div><p>因此浏览器会等等待JS引擎的执行，执行结束之后，再恢复DOM的构建。但是并不是所有的JavaScript都会设计DOM操作，比如审计信息，WebWorker等，对于这些脚本，我们可以显式地指定该脚本是<strong>不阻塞DOM渲染</strong>的。</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;worker.js&#34;</span> <span class="na">async</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</code></pre></div><p>带有<code>async</code>标记的脚本，浏览器仍然会下载它，并在合适的时机执行，但是不会影响DOM树的构建过程。</p>
<h3 id="首次渲染之后">首次渲染之后</h3>
<p>在首次渲染之后，页面上的元素还可能被不断的重新布局，重新绘制。如果处理不当，这些动作可能会产生性能问题，产生不好的用户体验。</p>
<ul>
<li>访问元素的某些属性</li>
<li>通过JavaScript修改元素的CSS属性</li>
<li>在<code>onScroll</code>中做耗时任务</li>
<li>图片的预处理（事先裁剪图片，而不是依赖浏览器在布局时的缩放）</li>
<li>在其他Event Handler中做耗时任务</li>
<li>过多的动画</li>
<li>过多的数据处理（可以考虑放入<code>WebWorker</code>内执行）</li>
</ul>
<h4 id="强制同步布局回流">强制同步布局/回流</h4>
<p>元素的一些属性和方法，当在被访问或者被调用的时候，会触发浏览器的布局动作（以及后续的Paint动作），而布局基本上都会波及页面上的所有元素。当页面元素比较多的时候，布局和绘制都会花费比较大。</p>
<p>通过Timeline，有时候你会看到这样的警告：</p>
<p><img loading="lazy" src="/images/2017/02/forced-reflow-resized.png" type="" alt=""  /></p>
<p>比如访问一个元素的<code>offsetWidth</code>（布局宽度）属性时，浏览器需要重新计算（重新布局），然后才能返回最新的值。如果这个动作发生在一个很大的循环中，那么浏览器就不得不进行多次的重新布局，这可能会产生严重的性能问题：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
	<span class="nx">list</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="o">+</span> <span class="s1">&#39;px&#39;</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>正确的做法是，先将这个值读出来，然后缓存在一个变量上（触发一次重新布局），以便后续使用：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">parentWidth</span> <span class="o">=</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">offsetWidth</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
	<span class="nx">list</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">parentWidth</span> <span class="o">+</span> <span class="s1">&#39;px&#39;</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>这里有一个完整的列表<a href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a">触发布局</a>。</p>
<h4 id="css样式修改">CSS样式修改</h4>
<h5 id="布局相关属性修改">布局相关属性修改</h5>
<p>修改布局相关属性，会触发<code>Layout - Paint - Composite Layers</code>，比如对位置，尺寸信息的修改：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;#id&#39;</span><span class="p">);</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="s1">&#39;100px&#39;</span><span class="p">;</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="s1">&#39;100px&#39;</span><span class="p">;</span>

<span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">top</span> <span class="o">=</span> <span class="s1">&#39;20px&#39;</span><span class="p">;</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">left</span> <span class="o">=</span> <span class="s1">&#39;20px&#39;</span><span class="p">;</span>
</code></pre></div><h5 id="绘制相关属性修改">绘制相关属性修改</h5>
<p>修改绘制相关属性，不会触发<code>Layout</code>，但是会触发后续的<code>Paint - Composite Layers</code>，比如对背景色，前景色的修改：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;#id&#39;</span><span class="p">);</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundColor</span> <span class="o">=</span> <span class="s1">&#39;red&#39;</span><span class="p">;</span>
</code></pre></div><h5 id="其他属性">其他属性</h5>
<p>除了上边的两种之外，有一些特别的属性可以在不同的层中单独绘制，然后再合并图层。对这种属性的访问（如果正确使用了CSS）不会触发<code>Layout - Paint</code>，而是直接进行<code>Compsite Layers</code>:</p>
<ul>
<li>transform</li>
<li>opacity</li>
</ul>
<p><code>transform</code>展开的话又分为: <code>translate</code>, <code>scale</code>, <code>rotate</code>等，这些层应该放入单独的渲染层中，为了对这个元素创建一个独立的渲染层，你必须提升该元素。</p>
<p>可以通过这样的方式来提升该元素：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">.</span><span class="nc">element</span> <span class="p">{</span>
  <span class="k">will-change</span><span class="p">:</span> <span class="k">transform</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><blockquote>
<p>CSS 属性 will-change 为web开发者提供了一种告知浏览器该元素会有哪些变化的方法，这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。</p>
</blockquote>
<p>当然，额外的层次并不是没有代价的。太多的独立渲染层，虽然缩减了<code>Paint</code>的时间，但是增加了<code>Composite Layers</code>的时间，因此需要仔细权衡。在作调整之前，需要<code>Timeline</code>的运行结果来做支持。</p>
<p>还记得性能优化的规则一吗？</p>
<blockquote>
<p>If you can&rsquo;t measure it, you can&rsquo;t improve it. - Peter Drucker</p>
</blockquote>
<p>下面这个视频里可以看到，当鼠标挪动到特定元素时，由于CSS样式的变化，元素会被重新绘制：</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/c6wKfDpbwu8" frameborder="0" allowfullscreen></iframe>
<p><a href="https://csstriggers.com/">CSS Triggers</a>是一个完整的CSS属性列表，其中包含了会影响布局或者绘制的CSS属性，以及在不同的浏览器上的不同表现。</p>
<h3 id="总结">总结</h3>
<p>了解浏览器的工作方式，对我们做前端页面渲染性能的分析和优化都非常有帮助。为了高效而智能的完成渲染，浏览器也在不断的进行优化，比如资源的预加载，更好的利用GPU（启用更多的线程来渲染）等等。</p>
<p>另一方面，我们在编写前端的HTML、JS、CSS时，也需要考虑浏览器的现状：如何减少DOM、CSSOM的构建时间，如何将耗时任务放在单独的线程中（通过<code>WebWorker</code>）。</p>
<h3 id="参考资料">参考资料</h3>
<ul>
<li>Google出品的<a href="https://developers.google.com/web/fundamentals/performance/?hl=zh-cn">Web基础</a></li>
<li>一篇关于如何<a href="https://varvy.com/pagespeed/optimize-css-delivery.html">优化CSS的文章</a></li>
<li><a href="https://varvy.com/performance/cssom.html">CSSOM</a>的介绍</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Node Webkit 101</title>
      <link>https://icodeit.org/2014/09/get-started-with-node-webkit/</link>
      <pubDate>Sun, 21 Sep 2014 00:00:00 +0000</pubDate>
      
      <guid>https://icodeit.org/2014/09/get-started-with-node-webkit/</guid>
      <description>Web前端的现状 目前的Web前端的现状较之5-6年前，简直不能同日而语：从所使用的技术、工具、框架到开发一个产品所需要付出的工作量，从前端开发从业人员的数量到Web应用的数量，从企业对于Web前端的重要程度的认识到Web实际上为企业带来的回报，一切都有了翻天覆地的变化。
借助HTML5+CSS3的普及，加上一些开箱即用的CSS框架（如bootstrap，foundation等）支持，人们已经可以非常容易的从零开始搭建一个Web应用的前端。一个在UI方面非常业余的程序员也可以很快的做出一个像模像样的用户界面。而另一方面，基于操作系统原生API，要想设计并实现一个桌面应用，需要的付出则远远超过超过同水平的Web界面。
webkit浏览器内核 Webkit作为最受欢迎的浏览器内核，自然有非常多的port。比如GTK+对它的port &amp;ndash; WebkitGTK，以及构建在WebkitGTK之上的Python的bind。使用WebkitGTK的Python版本，开发人员可以用HTML+CSS来开发应用，然后写一点Python脚本，最后将其运行在桌面上。
这里有个早期的例子来教你如何写一个所见即所得的编辑器。桌面应用开发中，对于用户界面的复杂性一直是一个难题，而这种方式可以减轻很多的用户界面开发的复杂性，将界面开发交给另外更加灵活，更加容易编写和调试方式：HTML+CSS。
这种模式下基本的开发流程是编写一个HTML页面（作为程序入口），然后在这个页面上引入额外的CSS（界面风格）和JavaScript（动作），然后将这些资源交给工业级浏览器内核Webkit来渲染 &amp;ndash; 这个过程和在浏览器中访问该文件并无二致，但是有两个额外的好处：
 页面运行在一个“桌面应用程序”中 没有地址栏，状态栏，菜单栏等，看起来更像是一个桌面应用 用户界面开发的复杂性被“外包”给一个更简单的环境  这就是传说中的混合（hybrid）开发模式，比如现在移动开发中的cordova就是采用这种模式，使得本来被视为天堑的原生的用户界面开发变为坦途。
node-webkit node-webkit是一个基于chromium和node.js的应用程序开发工具。它不但支持你使用传统的HTML5+CSS3+JS方式来开发你的应用程序，还支持无缝的与Node.js集成，也就是说，所有的Node支持的与操作系统交互的功能，如网络连接，文件系统，操作系统资源访问等，以及Node之上的第三方库都可以在node-webkit中进行使用。
更好的是，node-webkit是一个跨平台的工具，你可以使用它构建出运行在Mac OS，Linux以及Windows下的应用程序。应用程序通过Node.js来进行与系统相关的访问，而用HTML5+CSS3进行用户界面部分的设计。
node-webkit未必是未来桌面应用的唯一方式，但是却是一个非常好的选择，特别对于已经熟知Web前端开发技术栈的众多开发者来说，无需学习一门新的语言，一切都被很大程度的简化了。
第一个node-webkit应用程序 开发node-webkit应用程序非常简单。在这里下载系统对应的版本。并确保对应的二进制文件(nwnw.exe)在系统的PATH之中。
创建一个新的目录，然后在该目录中创建一个package.json文件和一个index.html文件：
$ mkdir -p hello-node-webkit $ cd hello-node-webkit $ touch package.json index.html package.json文件的内容如下：
{ &amp;#34;name&amp;#34;: &amp;#34;hello-node-webkit&amp;#34;, &amp;#34;version&amp;#34;: &amp;#34;0.1.0&amp;#34;, &amp;#34;main&amp;#34;: &amp;#34;index.html&amp;#34; } index.html文件的内容如下：
&amp;lt;html&amp;gt; &amp;lt;head&amp;gt; &amp;lt;title&amp;gt;Hello node-webkit&amp;lt;/title&amp;gt; &amp;lt;/head&amp;gt; &amp;lt;body&amp;gt; &amp;lt;div&amp;gt; &amp;lt;h1&amp;gt;Hello node-webkit&amp;lt;/h1&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt; 然后将这两个文件打在一个zip格式压缩包中：
$ zip -r hello-node-webkit.zip * 然后将这个文件重命名为hello-node-webkit.nw，最后使用node-webkit来启动这个应用程序。
$ ~/Tools/node-webkit.app/Contents/MacOS/node-webkit hello-node-webkit.nw 添加外部JS/CSS 接下来我们为这个页面添加一些外部的引用：CSS/JavaScript文件。首先创建两个目录style和script，然后分别创建文件如下：</description>
      <content:encoded><![CDATA[<h3 id="web前端的现状">Web前端的现状</h3>
<p>目前的Web前端的现状较之5-6年前，简直不能同日而语：从所使用的技术、工具、框架到开发一个产品所需要付出的工作量，从前端开发从业人员的数量到Web应用的数量，从企业对于Web前端的重要程度的认识到Web实际上为企业带来的回报，一切都有了翻天覆地的变化。</p>
<p>借助HTML5+CSS3的普及，加上一些开箱即用的CSS框架（如bootstrap，foundation等）支持，人们已经可以非常容易的从零开始搭建一个Web应用的前端。一个在UI方面非常业余的程序员也可以很快的做出一个像模像样的用户界面。而另一方面，基于操作系统原生API，要想设计并实现一个桌面应用，需要的付出则远远超过超过同水平的Web界面。</p>
<h3 id="webkit浏览器内核">webkit浏览器内核</h3>
<p><a href="https://www.webkit.org/">Webkit</a>作为最受欢迎的<em>浏览器内核</em>，自然有非常多的port。比如GTK+对它的port &ndash; <a href="http://webkitgtk.org/">WebkitGTK</a>，以及构建在WebkitGTK之上的Python的<a href="https://code.google.com/p/pywebkitgtk/">bind</a>。使用WebkitGTK的Python版本，开发人员可以用HTML+CSS来开发应用，然后写一点Python脚本，最后将其运行在桌面上。</p>
<p>这里有个早期的例子来教你<a href="http://arstechnica.com/information-technology/2009/07/how-to-build-a-desktop-wysiwyg-editor-with-webkit-and-html-5/1/">如何写一个所见即所得的编辑器</a>。桌面应用开发中，对于用户界面的复杂性一直是一个难题，而这种方式可以减轻很多的用户界面开发的复杂性，将界面开发交给另外更加灵活，更加容易编写和调试方式：HTML+CSS。</p>
<p>这种模式下基本的开发流程是编写一个HTML页面（作为程序入口），然后在这个页面上引入额外的CSS（界面风格）和JavaScript（动作），然后将这些资源交给工业级浏览器内核Webkit来渲染 &ndash; 这个过程和在浏览器中访问该文件并无二致，但是有两个额外的好处：</p>
<ol>
<li>页面运行在一个“桌面应用程序”中</li>
<li>没有地址栏，状态栏，菜单栏等，看起来更像是一个桌面应用</li>
<li>用户界面开发的复杂性被“外包”给一个更简单的环境</li>
</ol>
<p>这就是传说中的混合（hybrid）开发模式，比如现在移动开发中的cordova就是采用这种模式，使得本来被视为天堑的原生的用户界面开发变为坦途。</p>
<h4 id="node-webkit">node-webkit</h4>
<p><a href="https://github.com/rogerwang/node-webkit">node-webkit</a>是一个基于<a href="http://www.chromium.org/">chromium</a>和node.js的应用程序开发工具。它不但支持你使用传统的HTML5+CSS3+JS方式来开发你的应用程序，还支持无缝的与Node.js集成，也就是说，所有的Node支持的与操作系统交互的功能，如网络连接，文件系统，操作系统资源访问等，以及Node之上的第三方库都可以在node-webkit中进行使用。</p>
<p>更好的是，node-webkit是一个跨平台的工具，你可以使用它构建出运行在Mac OS，Linux以及Windows下的应用程序。应用程序通过Node.js来进行与系统相关的访问，而用HTML5+CSS3进行用户界面部分的设计。</p>
<p>node-webkit未必是未来桌面应用的唯一方式，但是却是一个非常好的选择，特别对于已经熟知Web前端开发技术栈的众多开发者来说，无需学习一门新的语言，一切都被很大程度的简化了。</p>
<h4 id="第一个node-webkit应用程序">第一个node-webkit应用程序</h4>
<p>开发node-webkit应用程序非常简单。在<a href="https://github.com/rogerwang/node-webkit">这里下载</a>系统对应的版本。并确保对应的二进制文件(nwnw.exe)在系统的PATH之中。</p>
<p>创建一个新的目录，然后在该目录中创建一个<code>package.json</code>文件和一个<code>index.html</code>文件：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ mkdir -p hello-node-webkit
$ <span class="nb">cd</span> hello-node-webkit
$ touch package.json index.html
</code></pre></div><p><code>package.json</code>文件的内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="p">{</span>
    <span class="s2">&#34;name&#34;</span><span class="o">:</span> <span class="s2">&#34;hello-node-webkit&#34;</span><span class="p">,</span>
    <span class="s2">&#34;version&#34;</span><span class="o">:</span> <span class="s2">&#34;0.1.0&#34;</span><span class="p">,</span>
    <span class="s2">&#34;main&#34;</span><span class="o">:</span> <span class="s2">&#34;index.html&#34;</span>
<span class="p">}</span>
</code></pre></div><p><code>index.html</code>文件的内容如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
	<span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Hello node-webkit<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
	<span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
		<span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Hello node-webkit<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
	<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><p>然后将这两个文件打在一个<code>zip</code>格式压缩包中：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ zip -r hello-node-webkit.zip *
</code></pre></div><p>然后将这个文件重命名为<code>hello-node-webkit.nw</code>，最后使用node-webkit来启动这个应用程序。</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$  ~/Tools/node-webkit.app/Contents/MacOS/node-webkit hello-node-webkit.nw
</code></pre></div><p><img loading="lazy" src="/images/2014/09/hello-node-webkit-resized.png" type="" alt="image"  /></p>
<h4 id="添加外部jscss">添加外部JS/CSS</h4>
<p>接下来我们为这个页面添加一些外部的引用：CSS/JavaScript文件。首先创建两个目录<code>style</code>和<code>script</code>，然后分别创建文件如下：</p>
<pre><code>├── index.html
├── package.json
├── script
│   ├── app.js
│   └── jquery.min.js
└── style
    └── style.css
</code></pre><p>其中，style.css定义了<code>h1</code>的简单样式：</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="nt">h1</span> <span class="p">{</span>
	<span class="k">font-size</span><span class="p">:</span> <span class="mi">20</span><span class="kt">px</span><span class="p">;</span>
	<span class="k">color</span><span class="p">:</span> <span class="mh">#999999</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>而app.js则注册了一个简单的事件处理器：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">$</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
	<span class="nx">$</span><span class="p">(</span><span class="s2">&#34;h1&#34;</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;click&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
		<span class="nx">alert</span><span class="p">(</span><span class="s2">&#34;Heading 1 is clicked&#34;</span><span class="p">);</span>
	<span class="p">});</span>
<span class="p">});</span>
</code></pre></div><p>此时的index.html修改如下：</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
	<span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Hello node-webkit<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
	<span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/css&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;style/style.css&#34;</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
	<span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
		<span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Hello node-webkit<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
	<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
	<span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;script/jquery.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
	<span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;script/app.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div><p>还是按照上一小节的命令完成打包，改名，启动之后。点击<code>h1</code>元素时，会弹出对话框如下：</p>
<p><img loading="lazy" src="/images/2014/09/node-webkit-clicked-resized.png" type="" alt="image"  /></p>
<p>在这个例子中，我们使用了外部的<code>css</code>文件来添加样式，还引入了<code>jQuery</code>作为访问DOM元素的工具，最后还使用了一段调用<code>jQuery</code>的JavaScript代码。</p>
<h4 id="构建脚本">构建脚本</h4>
<p>你可能已经注意到了，使用node-webkit开发非常方便。但是这一系列的动作（修改HTML+CSS，压缩打包，改名，启动）等有一部分重复工作，我们可以将其自动化。</p>
<p>好在已经有了一个很好用的<code>grunt</code>的插件：<a href="https://github.com/mllrsohn/grunt-node-webkit-builder">grunt-node-webkit-builder</a>，这个插件可以帮助我们自动执行压缩打包这些动作。</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ npm install grunt-node-webkit-builder
</code></pre></div><p>然后定义一个Gruntfile.js，这个文件中指定源文件（所有的HTML，JavaScript代码，CSS文件）所在目录，目标文件所在目录，需要构建的应用程序指定的操作系统平台等：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">grunt</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">grunt</span><span class="p">.</span><span class="nx">initConfig</span><span class="p">({</span>
        <span class="nx">pkg</span><span class="o">:</span> <span class="nx">grunt</span><span class="p">.</span><span class="nx">file</span><span class="p">.</span><span class="nx">readJSON</span><span class="p">(</span><span class="s1">&#39;package.json&#39;</span><span class="p">),</span>
        <span class="nx">nodewebkit</span><span class="o">:</span> <span class="p">{</span>
            <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
                <span class="nx">platforms</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;osx&#39;</span><span class="p">],</span>
                <span class="nx">buildDir</span><span class="o">:</span> <span class="s1">&#39;builds&#39;</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="nx">src</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;app/**/*&#39;</span><span class="p">]</span>
        <span class="p">},</span>

    <span class="p">});</span>

    <span class="nx">grunt</span><span class="p">.</span><span class="nx">loadNpmTasks</span><span class="p">(</span><span class="s1">&#39;grunt-node-webkit-builder&#39;</span><span class="p">);</span>
    <span class="nx">grunt</span><span class="p">.</span><span class="nx">registerTask</span><span class="p">(</span><span class="s1">&#39;default&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;nodewebkit&#39;</span><span class="p">]);</span>
<span class="p">};</span>
</code></pre></div><p>这样，我们修改之后，就可以很简单的执行：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ grunt
</code></pre></div><p>来进行打包了。比如在Mac下，构建出来的应用位于<code>builds/&lt;app-name&gt;/osx</code>目录下，要启动该应用只需要在命令行输入</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ open builds/hello-node-webkit/osx/hello-node-webkit.app
</code></pre></div><p>或者在Finder中双击打开即可。</p>
<p>可以看到上例中的应用程序还有浓重的浏览器痕迹，比如地址栏，刷新按钮，甚至还有一个<code>DevTools</code>的按钮。我们可以通过修改<code>package.json</code>来指定：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="p">{</span>
    <span class="s2">&#34;name&#34;</span><span class="o">:</span> <span class="s2">&#34;hello-node-webkit&#34;</span><span class="p">,</span>
    <span class="s2">&#34;version&#34;</span><span class="o">:</span> <span class="s2">&#34;0.1.0&#34;</span><span class="p">,</span>
    <span class="s2">&#34;main&#34;</span><span class="o">:</span> <span class="s2">&#34;index.html&#34;</span><span class="p">,</span>
    <span class="s2">&#34;window&#34;</span><span class="o">:</span> <span class="p">{</span>
        <span class="s2">&#34;toolbar&#34;</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
        <span class="s2">&#34;width&#34;</span><span class="o">:</span> <span class="mi">800</span><span class="p">,</span>
        <span class="s2">&#34;height&#34;</span><span class="o">:</span> <span class="mi">600</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div><p>这样的界面就更像是一个桌面应用了：</p>
<p><img loading="lazy" src="/images/2014/09/hello-without-address-resized.png" type="" alt="image"  /></p>
<p>到目前为止，这个小的<em>应用程序</em>并没有什么有趣的特性，用户界面也毫无美感，但是有了这些基本知识和工具之后，我们就可以开始更进一步的开发。除了使用既有的CSS框架来完成用户界面的美化，我们还会使用node.js访问系统资源来构建真实的应用程序。</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
