<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>推荐 &#8211; 萧邦的博客</title>
	<atom:link href="https://www.zlhlab.top/category/excellent/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.zlhlab.top</link>
	<description>If not me who, If not now when</description>
	<lastBuildDate>Thu, 28 Aug 2025 09:55:25 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.6</generator>
	<item>
		<title>凡人修仙与交易之路：谨小慎微，尽人事听天命</title>
		<link>https://www.zlhlab.top/%e5%87%a1%e4%ba%ba%e4%bf%ae%e4%bb%99%e4%b8%8e%e4%ba%a4%e6%98%93%e4%b9%8b%e8%b7%af%ef%bc%9a%e8%b0%a8%e5%b0%8f%e6%85%8e%e5%be%ae%ef%bc%8c%e5%b0%bd%e4%ba%ba%e4%ba%8b%e5%90%ac%e5%a4%a9%e5%91%bd/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Thu, 28 Aug 2025 09:55:25 +0000</pubDate>
				<category><![CDATA[推荐]]></category>
		<category><![CDATA[交易]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=839</guid>

					<description><![CDATA[引子：韩立的修仙启示 最近重温《凡人修仙传》，韩立缔结元婴的篇章令人感慨万千。一位灵根平凡、谨小慎微的修仙者，<div class="more-link">
				 <a href="https://www.zlhlab.top/%e5%87%a1%e4%ba%ba%e4%bf%ae%e4%bb%99%e4%b8%8e%e4%ba%a4%e6%98%93%e4%b9%8b%e8%b7%af%ef%bc%9a%e8%b0%a8%e5%b0%8f%e6%85%8e%e5%be%ae%ef%bc%8c%e5%b0%bd%e4%ba%ba%e4%ba%8b%e5%90%ac%e5%a4%a9%e5%91%bd/" class="link-btn theme-btn"><span>Read More </span> <i class="fa fa-caret-right"></i></a>
			</div>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">引子：韩立的修仙启示</h2>



<p>最近重温《凡人修仙传》，韩立缔结元婴的篇章令人感慨万千。一位灵根平凡、谨小慎微的修仙者，凭借坚定的道心和不懈的努力，终于踏上修仙巅峰。回望韩立的结婴之路，灵根、炼丹、法宝、符箓、资源、运气，缺一不可。无数修仙者中，能登顶者寥寥无几，韩立的成功离不开天时、地利、人和，更离不开他善于隐藏、把握时机的智慧和刻苦修炼的毅力。</p>



<p>大道是自己走出来的，但成败往往取决于内外因的交织。这与交易之路何其相似！</p>



<h2 class="wp-block-heading">交易之路：从偶然盈利到长期生存</h2>



<p>交易市场如修仙世界，短期盈利或许不难，但要实现持续的成功却需付出巨大努力。交易的成功离不开以下要素：</p>



<ul class="wp-block-list">
<li><strong>稳定的交易策略</strong>：通过实践打磨出可执行、可持续的交易体系。</li>



<li><strong>极致的风险管理</strong>：在市场波动中保持理性和纪律，控制每一笔交易的风险。</li>



<li><strong>情绪的磨砺</strong>：克服贪婪与恐惧，修炼一颗“交易道心”。</li>



<li><strong>持续的学习</strong>：洞悉市场逻辑，紧跟变化，保持竞争力。</li>
</ul>



<p>只有在这些方面下足功夫，才能在变幻莫测的市场中长期生存。</p>



<h2 class="wp-block-heading">个人经历：从迷茫到破局</h2>



<p>在经济下行、互联网寒冬和“35岁危机”的多重压力下，我终于完成了硕士论文，却面临长达两年的职业空窗期。这段时间，我尝试了A股、港股、美股、国内期货市场和外汇交易，学习了CFA一级课程，研究了文华财经、TBQuant、聚宽、QMT、MT4/MT5等平台，编写了数十个交易策略。然而，资源的有限与市场的无限让我深刻体会到个人精力的渺小。</p>



<p>曾几何时，听到新闻报道全国有1亿多人月收入不足1000元，我拿着2万元的月薪觉得不可思议。但当空有理想却换不来“馒头”时，我才明白自己也可能成为“可笑之人”。这让我理解了为何许多中年人会选择进入A股或沉迷赌博——当向上的通道被锁死，搏命似乎成了另一种出路。</p>



<h2 class="wp-block-heading">转机与感悟：回归本职与交易的平衡</h2>



<p>幸运的是，在储蓄几乎耗尽之际，我得到了现在公司的机会，找到了一份稳定的本职工作，解决了现金流的燃眉之急。稳定的收入让我无需通过频繁交易来谋生，从而能更专注于完善交易策略。最近，我完成了一次FTMO模拟账号挑战，收益率曲线颇为优美，这让我对再次启动实盘交易充满信心。</p>



<p>以下是我的最新FTMO模拟账号挑战结果：</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="913" src="https://www.zlhlab.top/wp-content/uploads/2025/08/截屏2025-08-27-15.21.07-1024x913.png" alt="" class="wp-image-840" srcset="https://www.zlhlab.top/wp-content/uploads/2025/08/截屏2025-08-27-15.21.07-1024x913.png 1024w, https://www.zlhlab.top/wp-content/uploads/2025/08/截屏2025-08-27-15.21.07-300x267.png 300w, https://www.zlhlab.top/wp-content/uploads/2025/08/截屏2025-08-27-15.21.07-768x684.png 768w, https://www.zlhlab.top/wp-content/uploads/2025/08/截屏2025-08-27-15.21.07.png 1324w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">凡人交易：与市场共舞，尽人事听天命</h2>



<p>“你我皆凡人”。交易之路是与人性、与市场的博弈。市场瞬息万变，我们只能不断适应、跟随。对于普通人而言，照顾好家庭已需耗费巨大心力，能否实现财富自由往往需要“尽人事，听天命”。</p>



<p>最后，我想与大家分享《天道》中的一首自嘲诗，愿我们都能在平凡中坚守，在市场中砥砺前行：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本是后山人，偶作前堂客。<br>醉舞经阁半卷书，坐井说天阔。<br>大志戏功名，海斗量福祸。<br>论到囊中羞涩时，怒指乾坤错。</p>
</blockquote>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>一位创业猿类的心路历程</title>
		<link>https://www.zlhlab.top/%e4%b8%80%e4%b8%aa%e5%88%9b%e4%b8%9a%e7%8c%bf%e7%b1%bb%e7%9a%84%e5%bf%83%e9%85%b8%e5%8e%86%e7%a8%8b/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Mon, 14 May 2018 16:25:53 +0000</pubDate>
				<category><![CDATA[推荐]]></category>
		<category><![CDATA[情感]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=609</guid>

					<description><![CDATA[鱼在缸内，不知有河流；鱼在河流，不知有江湖；鱼在江湖，不知有世界。-- 萧邦]]></description>
										<content:encoded><![CDATA[<p>鱼在缸内，不知有河流；鱼在河流，不知有江湖；鱼在江湖，不知有世界。&#8211; 萧邦</p>
<h3>前言</h3>
<p>从结束创业合伙人的身份，到裸辞跨城市找工作，已经有两个月的时间了。创业3年的感悟，还不如这两个月来得猛烈。人间酸甜苦辣，都深深的尝了一遍。</p>
<p>第一次深刻感觉病不起，缺钱是多么的无助！这也是为什么我在别人逃离一线城市的时候，反而离开家人，只身前往上海的主要原因。</p>
<p>我只想在下次风暴来临之前，拥有足够的资本来抵抗风险。</p>
<h3>1.裸辞，离开合伙创办的公司</h3>
<p>当你的技术创新促进不了业务的发展，或者业务不足以让你有更好的发挥，那么你就变得可有可无，危机感也就随之而来。</p>
<p>作为技术合伙创办公司的创始人之一，占有少量股份，决定权和主导权都不在自己的手上。公司不上市，股份不会变现，约定也都是一纸空谈。所以创业很考验是否跟对人，和合伙人的职业素养。</p>
<p>当商用的只是你能力的一下部分，自己的能力没有办法进一步提舍，你自己主导的产品一直处于自嗨状态，那么离开只是早晚的事。</p>
<p>因为已经觉得是在浪费时间，所以妻子问我是否后悔裸辞，我说依然坚定的说不后悔。</p>
<h3>2.裸辞，遇上感冒和手术</h3>
<p><b>也许人生就是这么奇妙，你永远不知道下一刻迎接你的是什么？</b></p>
<p>本来决定在金三的月份开启新的旅程。在裸辞之后，决定找外地工作之前，决定先带小孩去复查，得出的诊断结果是：<strong>需要立即住院进行手术</strong>。</p>
<p>然后这段求职之旅就在：<br />
&#8211; 职业选择：ios/前端开发，<br />
&#8211; 区域选择：上海/杭州/武汉，<br />
&#8211; 时间选择：等待手术期间/手术后</p>
<p>这三个选择中徘徊，兼顾家庭(武汉找)，还是父母团聚(杭州)，还是选择发展(上海)。</p>
<p>遇上流感高发，我和小宝都重度感冒了，还相互传染。而手术要避免炎症，再加上医院排队手术，整个时间跨度足足有一个半月。这段时间，整个家庭都处在压抑的阴霾下，无法释怀、无心工作。</p>
<p>长时间的失业担忧，手术风险，金钱的压力，当手术顺利结束的回家的那一刻，压抑的心情释放一大部分，终于可以无顾忌的放手一博。</p>
<h3>3.裸辞找工作</h3>
<p>也许这是找工作最纠结的一次。也暴露了创业带来的严重技术脱节。</p>
<p>这职场从来是凭本事说话。当我在幻想回到2年IOS研发，主推IOS开发，简历却无人问津；职场对3年的高级前端岗位需求和热度明显更高。</p>
<p>前期的准备不足，导致期间进行过蚂蚁金服的两次岗位电话面试，均以惨败收场，只能对推荐人报以深深的歉意。不知ES6、不用框架(ReactJS、VueJS、AngularJS三选其一)、不涉及太多js编码，面试表现得一塌糊涂。这反而激发我在这1个多月的时间内，专门研究React技术栈开发和学习ES6语法。</p>
<p>每一次面试，都是和技术人员的一次深度交流。面试多家公司，持续的积累面经，终于对面试游刃有余。感谢找油网CTO提供的职业建议，唯有选定一个方向的进行技术专精，才有更多的余力进行技术拓展。面试，和面试官聊第一句话就可以决定去留。</p>
<p>很感谢现在的公司提供的平台、负责招聘的架构师和直属领导。从他们身上有很多可以学习的职业化的东西，可以持续自我提升的地方。现在所做的一切，是在为你的明天加分，那么一切都值得。</p>
<p>跨区域找工作这段时间，时间越长，家人和周围人给自己的压力越大。当接到心仪的公司offer的时候，一段时间以来累积的压力瞬间释放了。回首，才发觉在上海逗留才一周时间不到。</p>
<h3>4.总结：创业的失与得</h3>
<p>首先感谢每一段经历和遇见的人。失败，只是因为自己还不足够好，还缺少达到成功的能量！</p>
<p>总结这次合伙创业的失与得：</p>
<p><strong>失：</strong><br />
1、改变了职业轨迹，从IOS研发，转成了前端开发；从研发产品，变成了项目交付驱动，导致技术深度没办法很好的积累和转化；<br />
2、title虽然是开发总监，但品牌溢价不够；<br />
3、长时间的业务驱动，很容易受项目影响。有项目忧心项目质量，没有项目忧心业务；<br />
4、经历过创业，很多公司会将它作为稳定性的考量；<br />
5、对之前拉入伙的同事，没有给到很好的交代。</p>
<p><strong>得：</strong><br />
1、熟悉公司的基础运作流程和操作流程；<br />
2、看穿商业运作的本质(资源、人脉、资金)；<br />
3、掌握了一门新的开发语言；<br />
4、自己的不成熟，需要经过失败来洗礼，才能变得更成熟；<br />
5、更明确自己想要什么，和如何学会拒绝。</p>
<p>生活还在继续，唯一不变的是一颗想要生存和发展的心！<br />
献给正在或即将创业的猿类，且行且珍惜！</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>掌握jQuery插件开发，这篇文章就够了</title>
		<link>https://www.zlhlab.top/%e6%8e%8c%e6%8f%a1jquery%e6%8f%92%e4%bb%b6%e5%bc%80%e5%8f%91%ef%bc%8c%e8%bf%99%e7%af%87%e6%96%87%e7%ab%a0%e5%b0%b1%e5%a4%9f%e4%ba%86/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Sun, 21 Jan 2018 04:22:34 +0000</pubDate>
				<category><![CDATA[推荐]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[jquery插件]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=506</guid>

					<description><![CDATA[在实际开发工作中，总会碰到像滚动，分页，日历等展示效果的业务需求，对于接触过jQuery以及熟悉jQuery使用的人来说，首先想到的肯定是寻找现有的jQuery插件来满足相应的展示需求。]]></description>
										<content:encoded><![CDATA[<p>在实际开发工作中，总会碰到像滚动，分页，日历等展示效果的业务需求，对于接触过<code>jQuery</code>以及熟悉<code>jQuery</code>使用的人来说，首先想到的肯定是寻找现有的<code>jQuery</code>插件来满足相应的展示需求。目前页面中常用的一些组件，都有多种<code>jQuery</code>插件可供选择，网络上也有很多专门收集<code>jQuery</code>插件的网站。利用<code>jQuery</code>插件确实可以给我们的开发工作带来便捷，但是如果只是会简单使用，而对其中的原理不甚了解，那么在使用过程中碰到问题或者对插件进行定制开发时就会有诸多疑惑。本文的目的就是可以快速了解<code>jQuery</code>插件的开发原理以及掌握<code>jQuery</code>开发的基本技能。</p>
<p>进行<code>jQuery</code>插件开发前，首先要知道两个问题：什么是<code>jQuery</code>插件？<code>jQuery</code>插件如何使用？</p>
<p>第一个问题，<code>jQuery</code>插件就是用来扩展<code>jQuery</code>原型对象的一个方法，简单来说就是<code>jQuery</code>插件是<code>jQuery</code>对象的一个方法。其实回答了第一个问题，也就知道第二个问题的答案了，<code>jQuery</code>插件的使用方式就是<code>jQuery</code>对象方法的调用。</p>
<p>我们先看个例子：<code>$("a").css("color","red")</code>。我们知道每个<code>jQuery</code>对象都会包含<code>jQuery</code>中定义的DOM操作方法,这里使用<code>$</code>方法来选择a元素，返回一个a元素的<code>jQuery</code>对象，这个对象就可以使用<code>jQuery</code>中定义的DOM操作方法。那么<code>jQuery</code>对象是如何获取这些方法的呢？其实<code>jQuery</code>内部定义了一个<code>jQuery.fn</code>对象，查看<code>jQuery</code>源码可以发现<code>jQuery.fn=jQuery.prototype</code>，也就是说<code>jQuery.fn</code>对象是<code>jQuery</code>的原型对象,<code>jQuery</code>的DOM操作方法都在<code>jQuery.fn</code>对象上定义的，然后<code>jQuery</code>对象就可以通过原型继承这些方法。</p>
<h4>基础版jQuery插件</h4>
<p>知道了上面这些知识，我们就可以来写一个简单的jQuery插件。假如我现在需要一个jQuery插件用来改变标签内容颜色，就可以按下面的方式来实现这个插件：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">$.fn.changeStyle = function(colorStr){
         this.css("color",colorStr);
}
</code></pre>
<p>然后按下面的方式来使用插件：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">$("p").changeStyle("red");
</code></pre>
<p>插件调用的时候，插件内部的this就是当前调用插件的<code>jQuery</code>对象，这样的话每个使用$()方法选择的标签，在调用<code>changeStyle()</code>插件时都会使用<code>css()</code>方法重设<code>color</code>样式。</p>
<h4>满足链式调用的jQuery插件</h4>
<p>链式调用时<code>jQuery</code>的一大特色，一个通用的插件应该遵循<code>jQuery</code>风格，满足链式调用要求。实现链式调用的方式也很简单：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">$.fn.changeStyle = function(colorStr){
         this.css("color",colorStr);
         return this;
}
</code></pre>
<p>然后使用的时候就可以链式调用其他方法了：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">$("p").changeStyle("red").addClass("red-color");
</code></pre>
<p>实现链式调用的关键点就一行代码<code>return this</code>，插件中加了这行代码，那么在插件执行完之后，就会把当前的<code>jQuery</code>对象返回，然后就可以在插件方法后面继续调用其它<code>jQuery</code>方法。</p>
<h4>防止$符号污染的jQuery插件</h4>
<p>有很多js库都会使用$符号，虽然<code>jQuery</code>可以使用<code>jQuery.noConflict()</code>方法交出<code>$</code>符号的使用权，但是如果定义插件的时候，使用<code>$.fn</code>对象来定义的，那么这些插件使用的时候就会受到其它使用<code>$</code>变量的js库的影响。对于这种情况，我们可以使用立即执行函数通过传参的方式封装插件。形式如下：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">(function($){
     $.fn.changeStyle = function(colorStr){
         this.css("color",colorStr);        
         return this;
     }
}(jQuery));
</code></pre>
<p>因为使用了立即执行函数，所以此时的$只属于这个立即执行函数的函数作用域，这样就可以避免<code>$</code>符号的污染。</p>
<h4>可以接受参数的jQuery插件</h4>
<p>继续上面的例子，假如我还想为这个插件添加一个设置标签元素内容文字大小的功能，那么我可以这么来实现：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">(function($){
     $.fn.changeStyle = function(colorStr，fontSize){
            this.css("color",colorStr).css("fontSize",fontSize+"px");        
         return this;
     }
}(jQuery));
</code></pre>
<p>上面这种插件传参方式适用于参数比较少的情况，如果需要传给插件内部的参数比较多，我们可以定义一个参数对象，然后把需要传给插件的参数放在参数对象中。插件定义时如下：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">(function($){
     $.fn.changeStyle = function(option){
         this.css("color",option.colorStr).css("fontSize",option.fontSize+"px");        
         return this;
     }

}(jQuery));
</code></pre>
<p>使用方式：<code>$("p").changeStyle({colorStr:"red",fontSize:14});</code></p>
<p>把参数放到一个对象中传给插件还有一个好处就是我们可以在插件内部为一些参数定义一些缺省值，例如：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">(function($){

     $.fn.changeStyle = function(option){
          var defaultSetting = { colorStr:"green",fontSize:12};
          var setting = $.extend(defaultSetting,option);
          this.css("color",setting.colorStr).css("fontSize",setting.fontSize+"px");        
         return this;
     }
}(jQuery));
</code></pre>
<p>上面的代码用到了<code>$.extend</code>方法，这个方法在这里的用法就是合并两个对象，即把后面一个对象的存在的属性值赋值给第一个对象，具体用法可以参考这里。<code>$.extend</code>方法还有一种作用是用来扩展<code>jQuery</code>对象本身。</p>
<p>这样定义的插件，我们在使用时如果不传<code>fontSize</code>，那么使用这个插件的<code>jQuery</code>对象标签的内容会被设置成默认的12px。</p>
<p>使用方式：<code>$("p").changeStyle({colorStr:"red"});</code></p>
<p>注意：在为插件定义默认参数时，一定要把默认参数写在插件方法内部，这样默认参数的作用域就在插件内部。</p>
<h4>总结</h4>
<p>定义插件的方式除了上面说的用<code>$.fn</code>来定义，还有另外一种方式来定义插件，那就是使用<code>$.fn.extend</code>方法。类似下面的写法：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">(function($){
     $.fn.extend({         
         changeStyle:function(option){             
         var defaultSetting = { colorStr:"green",fontSize:12};
         var setting = $.extend(defaultSetting,option);
         this.css("color",setting.colorStr).css("fontSize",setting.fontSize+"px");        
         return this; 
          }
     });
}(jQuery));
</code></pre>
<blockquote><p>
  <strong>PS</strong>:<code>$.extend</code>方法和<code>$.fn.extend</code>方法都可以用来扩展<code>jQuery</code>功能，通过阅读<code>jQuery</code>源码我们可以发现这两个方法的本质区别，那就是<code>$.extend</code>方法是在<code>jQuery</code>全局对象上扩展方法，<code>$.fn.extend</code>方法是在<code>$</code>选择符选择的<code>jQuery</code>对象上扩展方法。所以扩展<code>jQuery</code>的公共方法一般用<code>$.extend</code>方法，定义插件一般用<code>$.fn.extend</code>方法。
</p></blockquote>
<h4>参考资料</h4>
<p>jQuery源码<br />
<a href="http://learn.jquery.com/plugins/basic-plugin-creation/" title="How to Create a Basic Plugin" target="_blank">How to Create a Basic Plugin</a></p>
<p>作者：留七七<br />
链接：<a href="https://www.jianshu.com/p/518d424d4994" title="https://www.jianshu.com/p/518d424d4994" target="_blank">https://www.jianshu.com/p/518d424d4994</a><br />
來源：简书<br />
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>前端构建工具gulpjs的使用介绍及技巧</title>
		<link>https://www.zlhlab.top/%e5%89%8d%e7%ab%af%e6%9e%84%e5%bb%ba%e5%b7%a5%e5%85%b7gulpjs%e7%9a%84%e4%bd%bf%e7%94%a8%e4%bb%8b%e7%bb%8d%e5%8f%8a%e6%8a%80%e5%b7%a7/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Thu, 23 Nov 2017 09:02:28 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[推荐]]></category>
		<category><![CDATA[gulp.js]]></category>
		<category><![CDATA[node.js]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=467</guid>

					<description><![CDATA[gulpjs是一个前端构建工具，与gruntjs相比，gulpjs无需写一大堆繁杂的配置参数，API也非常简单，学习起来很容易，而且gulpjs使用的是nodejs中stream来读取和操作数据，其速度更快。]]></description>
										<content:encoded><![CDATA[<p><code>gulpjs</code>是一个前端构建工具，与<a href="https://gruntjs.com/" title="gruntjs" target="_blank">gruntjs</a>相比，gulpjs无需写一大堆繁杂的配置参数，API也非常简单，学习起来很容易，而且gulpjs使用的是nodejs中<a href="https://nodejs.org/api/stream.html" title="stream" target="_blank">stream</a>来读取和操作数据，其速度更快。如果你还没有使用过前端构建工具，或者觉得gruntjs太难用的话，那就尝试一下gulp吧。</p>
<p>本文导航：</p>
<ol>
<li>gulp的安装</li>
<li>开始使用gulp</li>
<li>gulp的API介绍</li>
<li>一些常用的gulp插件</li>
</ol>
<h4>1、gulp的安装</h4>
<p>首先确保你已经正确安装了nodejs环境。然后以全局方式安装gulp：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-shell">npm install -g gulp
</code></pre>
<p>全局安装gulp后，还需要在每个要使用gulp的项目中都单独安装一次。把目录切换到你的项目文件夹中，然后在命令行中执行：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-shell">npm install gulp
</code></pre>
<p>如果想在安装的时候把gulp写进项目package.json文件的依赖中，则可以加上&#8211;save-dev：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-shell">npm install --save-dev gulp
</code></pre>
<p>这样就完成了gulp的安装。至于为什么在全局安装gulp后，还需要在项目中本地安装一次，有兴趣的可以看下stackoverflow上有人做出的回答：<a href="https://stackoverflow.com/questions/22115400/why-do-we-need-to-install-gulp-globally-and-locally" title="why-do-we-need-to-install-gulp-globally-and-locally" target="_blank">why-do-we-need-to-install-gulp-globally-and-locally</a>、<a href="https://stackoverflow.com/questions/25713618/what-is-the-point-of-double-install-in-gulp" title="what-is-the-point-of-double-install-in-gulp" target="_blank">what-is-the-point-of-double-install-in-gulp</a>。大体就是为了版本的灵活性，但如果没理解那也不必太去纠结这个问题，只需要知道通常我们是要这样做就行了。</p>
<h4>2、开始使用gulp</h4>
<h5><strong>2.1 建立gulpfile.js文件</strong></h5>
<p>就像gruntjs需要一个<code>Gruntfile.js</code>文件一样，gulp也需要一个文件作为它的主文件，在gulp中这个文件叫做<code>gulpfile.js</code>。新建一个文件名为<code>gulpfile.j</code>s的文件，然后放到你的项目目录中。之后要做的事情就是在<code>gulpfile.js</code>文件中定义我们的任务了。下面是一个最简单的<code>gulpfile.js</code>文件内容示例，它定义了一个默认的任务。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">var gulp = require('gulp');
gulp.task('default',function(){
    console.log('hello world');
});
</code></pre>
<p>此时我们的目录结构是这样子的：</p>
<p>├── gulpfile.js<br />
├── node_modules<br />
│ └── gulp<br />
└── package.json</p>
<h5><strong>2.2 运行gulp任务</strong></h5>
<p>要运行gulp任务，只需切换到存放<code>gulpfile.js</code>文件的目录(windows平台请使用cmd或者Power Shell等工具)，然后在命令行中执行<code>gulp</code>命令就行了，<code>gulp</code>后面可以加上要执行的任务名，例如<code>gulp task1</code>，如果没有指定任务名，则会执行任务名为<code>default</code>的默认任务。</p>
<h4>3、gulp的API介绍</h4>
<p>使用gulp，仅需知道4个API即可：<code>gulp.task()</code>,<code>gulp.src()</code>,<code>gulp.dest()</code>,<code>gulp.watch()</code>，所以很容易就能掌握，但有几个地方需理解透彻才行，我会在下面一一说明。为了避免出现理解偏差，建议先看一遍<a href="https://github.com/gulpjs/gulp/blob/master/docs/API.md" title="官方文档" target="_blank">官方文档</a>。</p>
<h5><strong>3.1 gulp.src()</strong></h5>
<p>在介绍这个API之前我们首先来说一下Grunt.js和Gulp.js工作方式的一个区别。Grunt主要是以文件为媒介来运行它的工作流的，比如在Grunt中执行完一项任务后，会把结果写入到一个临时文件中，然后可以在这个临时文件内容的基础上执行其它任务，执行完成后又把结果写入到临时文件中，然后又以这个为基础继续执行其它任务&#8230;就这样反复下去。而在Gulp中，使用的是Nodejs中的<strong>stream</strong>(流)，首先获取到需要的stream，然后可以通过stream的<code>pipe()</code>方法把流导入到你想要的地方，比如Gulp的插件中，经过插件处理后的流又可以继续导入到其他插件中，当然也可以把流写入到文件中。所以Gulp是以stream为媒介的，它不需要频繁的生成临时文件，这也是Gulp的速度比Grunt快的一个原因。再回到正题上来，<code>gulp.src()</code>方法正是用来获取流的，但要注意这个流里的内容不是原始的文件流，而是一个虚拟文件对象流(<strong>Vinyl files</strong>)，这个虚拟文件对象中存储着原始文件的路径、文件名、内容等信息，这个我们暂时不用去深入理解，你只需简单的理解可以用这个方法来读取你需要操作的文件就行了。其语法为：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.src(globs[, options])
</code></pre>
<p><strong>globs</strong>参数是文件匹配模式(类似正则表达式)，用来匹配文件路径(包括文件名)，当然这里也可以直接指定某个具体的文件路径。当有多个匹配模式时，该参数可以为一个数组。<br />
<strong>options</strong>为可选参数。通常情况下我们不需要用到。</p>
<p>下面我们重点说说Gulp用到的glob的匹配规则以及一些文件匹配技巧。<br />
Gulp内部使用了<strong>node-glob</strong>模块来实现其文件匹配功能。我们可以使用下面这些特殊的字符来匹配我们想要的文件：</p>
<ul>
<li><code>*</code> 匹配文件路径中的0个或多个字符，但不会匹配路径分隔符，除非路径分隔符出现在末尾</li>
<li><code>**</code> 匹配路径中的0个或多个目录及其子目录,需要单独出现，即它左右不能有其他东西了。如果出现在末尾，也能匹配文件。</li>
<li><code>?</code> 匹配文件路径中的一个字符(不会匹配路径分隔符)</li>
<li><code>[...]</code> 匹配方括号中出现的字符中的任意一个，当方括号中第一个字符为^或!时，则表示不匹配方括号中出现的其他字符中的任意一个，类似js正则表达式中的用法</li>
<li><code>!(pattern|pattern|pattern)</code> 匹配任何与括号中给定的任一模式都不匹配的</li>
<li><code>?(pattern|pattern|pattern)</code> 匹配括号中给定的任一模式0次或1次，类似于js正则中的(pattern|pattern|pattern)?</li>
<li><code>+(pattern|pattern|pattern)</code> 匹配括号中给定的任一模式至少1次，类似于js正则中的(pattern|pattern|pattern)+</li>
<li><code>*(pattern|pattern|pattern)</code> 匹配括号中给定的任一模式0次或多次，类似于js正则中的(pattern|pattern|pattern)*</li>
<li><code>@(pattern|pattern|pattern)</code> 匹配括号中给定的任一模式1次，类似于js正则中的(pattern|pattern|pattern)</li>
</ul>
<p>下面以一系列例子来加深理解</p>
<ul>
<li><code>*</code> 能匹配 <code>a.js</code>,<code>x.y</code>,<code>abc</code>,<code>abc/</code>,但不能匹配<code>a/b.js</code></li>
<li><code>*.*</code> 能匹配 <code>a.js</code>,<code>style.css</code>,<code>a.b</code>,<code>x.y</code></li>
<li><code>*/*/*.js</code> 能匹配 <code>a/b/c.js</code>,<code>x/y/z.js</code>,不能匹配<code>a/b.js</code>,<code>a/b/c/d.js</code></li>
<li><code>**</code> 能匹配 abc,a/b.js,a/b/c.js,x/y/z,x/y/z/a.b,能用来匹配所有的目录和文件</li>
<li><code>**/*.js</code> 能匹配 <code>foo.js</code>,<code>a/foo.js</code>,<code>a/b/foo.js</code>,<code>a/b/c/foo.js</code></li>
<li><code>a/**/z</code> 能匹配 <code>a/z</code>,<code>a/b/z</code>,<code>a/b/c/z</code>,<code>a/d/g/h/j/k/z</code></li>
<li><code>a/**b/z</code> 能匹配 <code>a/b/z</code>,<code>a/sb/z</code>,但不能匹配<code>a/x/sb/z</code>,因为只有单<code>**</code>单独出现才能匹配多级目录</li>
<li><code>?.js</code> 能匹配 <code>a.js</code>,<code>b.js</code>,<code>c.js</code></li>
<li><code>a??</code> 能匹配 <code>a.b</code>,<code>abc</code>,但不能匹配<code>ab/</code>,因为它不会匹配路径分隔符</li>
<li><code>[xyz].js</code> 只能匹配 <code>x.js</code>,<code>y.js</code>,<code>z.js</code>,不会匹配<code>xy.js</code>,<code>xyz.js</code>等,整个中括号只代表一个字符</li>
<li><code>[^xyz].js</code> 能匹配 <code>a.js</code>,<code>b.js</code>,<code>c.js</code>等,不能匹配<code>x.js</code>,<code>y.js</code>,<code>z.js</code></li>
</ul>
<p>当有多种匹配模式时可以使用数组</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">//使用数组的方式来匹配多种文件
gulp.src(['js/*.js','css/*.css','*.html'])
</code></pre>
<p>使用数组的方式还有一个好处就是可以很方便的使用排除模式，在数组中的单个匹配模式前加上<code>!</code>即是排除模式，它会在匹配的结果中排除这个匹配，要注意一点的是不能在数组中的第一个元素中使用排除模式</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.src([*.js,'!b*.js']) //匹配所有js文件，但排除掉以b开头的js文件
gulp.src(['!b*.js',*.js]) //不会排除任何文件，因为排除模式不能出现在数组的第一个元素中
</code></pre>
<p>此外，还可以使用展开模式。展开模式以花括号作为定界符，根据它里面的内容，会展开为多个模式，最后匹配的结果为所有展开的模式相加起来得到的结果。展开的例子如下：</p>
<p><code>a{b,c}d</code> 会展开为 <code>abd</code>,<code>acd</code><br />
<code>a{b,}c</code> 会展开为 <code>abc</code>,<code>ac</code><br />
<code>a{0..3}d</code> 会展开为 <code>a0d</code>,<code>a1d</code>,<code>a2d</code>,<code>a3d</code><br />
<code>a{b,c{d,e}f}g</code> 会展开为 <code>abg</code>,<code>acdfg</code>,<code>acefg</code><br />
<code>a{b,c}d{e,f}g</code> 会展开为 <code>abdeg</code>,<code>acdeg</code>,<code>abdeg</code>,<code>abdfg</code></p>
<h5><strong>3.2 gulp.dest()</strong></h5>
<p>gulp.dest()方法是用来写文件的，其语法为：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.dest(path[,options])
</code></pre>
<p><strong>path</strong>为写入文件的路径<br />
<strong>options</strong>为一个可选的参数对象，通常我们不需要用到</p>
<p>要想使用好<code>gulp.dest()</code>这个方法，就要理解给它传入的路径参数与最终生成的文件的关系。<br />
gulp的使用流程一般是这样子的：首先通过<code>gulp.src()</code>方法获取到我们想要处理的文件流，然后把文件流通过pipe方法导入到gulp的插件中，最后把经过插件处理后的流再通过pipe方法导入到<code>gulp.dest()</code>中，<code>gulp.dest()</code>方法则把流中的内容写入到文件中，这里首先需要弄清楚的一点是，我们给<code>gulp.dest()</code>传入的路径参数，只能用来指定要生成的文件的目录，而不能指定生成文件的文件名，它生成文件的文件名使用的是导入到它的文件流自身的文件名，所以生成的文件名是由导入到它的文件流决定的，即使我们给它传入一个带有文件名的路径参数，然后它也会把这个文件名当做是目录名，例如：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp');
gulp.src('script/jquery.js')
    .pipe(gulp.dest('dist/foo.js'));
//最终生成的文件路径为 dist/foo.js/jquery.js,而不是dist/foo.js
</code></pre>
<p>要想改变文件名，可以使用插件<a href="https://www.npmjs.com/package/gulp-rename" title="gulp-rename" target="_blank">gulp-rename</a></p>
<p>下面说说生成的文件路径与我们给<code>gulp.dest()</code>方法传入的路径参数之间的关系。<br />
<code>gulp.dest(path)</code>生成的文件路径是我们传入的path参数后面再加上<code>gulp.src()</code>中有通配符开始出现的那部分路径。例如：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = reruire('gulp');
//有通配符开始出现的那部分路径为 **/*.js
gulp.src('script/**/*.js')
    .pipe(gulp.dest('dist')); //最后生成的文件路径为 dist/**/*.js
//如果 **/*.js 匹配到的文件为 jquery/jquery.js ,则生成的文件路径为 dist/jquery/jquery.js
</code></pre>
<p>再举更多一点的例子</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.src('script/avalon/avalon.js') //没有通配符出现的情况
    .pipe(gulp.dest('dist')); //最后生成的文件路径为 dist/avalon.js

//有通配符开始出现的那部分路径为 **/underscore.js
gulp.src('script/**/underscore.js')
    //假设匹配到的文件为script/util/underscore.js
    .pipe(gulp.dest('dist')); //则最后生成的文件路径为 dist/util/underscore.js

gulp.src('script/*') //有通配符出现的那部分路径为 *
    //假设匹配到的文件为script/zepto.js    
    .pipe(gulp.dest('dist')); //则最后生成的文件路径为 dist/zepto.js
</code></pre>
<p>通过指定<code>gulp.src()</code>方法配置参数中的<code>base</code>属性，我们可以更灵活的来改变<code>gulp.dest()</code>生成的文件路径。<br />
当我们没有在<code>gulp.src()</code>方法中配置<code>base</code>属性时，<code>base</code>的默认值为通配符开始出现之前那部分路径，例如：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.src('app/src/**/*.css') //此时base的值为 app/src
</code></pre>
<p>上面我们说的<code>gulp.dest()</code>所生成的文件路径的规则，其实也可以理解成，用我们给<code>gulp.dest()</code>传入的路径替换掉<code>gulp.src()</code>中的<code>base</code>路径，最终得到生成文件的路径。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.src('app/src/**/*.css') //此时base的值为app/src,也就是说它的base路径为app/src
     //设该模式匹配到了文件 app/src/css/normal.css
    .pipe(gulp.dest('dist')) //用dist替换掉base路径，最终得到 dist/css/normal.css
</code></pre>
<p>所以改变base路径后，<code>gulp.dest()</code>生成的文件路径也会改变</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.src(script/lib/*.js) //没有配置base参数，此时默认的base路径为script/lib
    //假设匹配到的文件为script/lib/jquery.js
    .pipe(gulp.dest('build')) //生成的文件路径为 build/jquery.js

gulp.src(script/lib/*.js, {base:'script'}) //配置了base参数，此时base路径为script
    //假设匹配到的文件为script/lib/jquery.js
    .pipe(gulp.dest('build')) //此时生成的文件路径为 build/lib/jquery.js
</code></pre>
<p>用<code>gulp.dest()</code>把文件流写入文件后，文件流仍然可以继续使用。</p>
<h5><strong>3.3 gulp.task()</strong></h5>
<p><code>gulp.task</code>方法用来定义任务，内部使用的是Orchestrator，其语法为：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.task(name[, deps], fn)
</code></pre>
<p><strong>name</strong> 为任务名<br />
<strong>deps</strong> 是当前定义的任务需要依赖的其他任务，为一个数组。当前定义的任务会在所有依赖的任务执行完毕后才开始执行。如果没有依赖，则可省略这个参数<br />
<strong>fn</strong> 为任务函数，我们把任务要执行的代码都写在里面。该参数也是可选的。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { //定义一个有依赖的任务
  // Do something
});
</code></pre>
<p><code>gulp.task()</code>这个API没什么好讲的，但需要知道执行多个任务时怎么来控制任务执行的顺序。<br />
gulp中执行多个任务，可以通过任务依赖来实现。例如我想要执行<code>one</code>,<code>two</code>,<code>three</code>这三个任务，那我们就可以定义一个空的任务，然后把那三个任务当做这个空的任务的依赖就行了：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">//只要执行default任务，就相当于把one,two,three这三个任务执行了
gulp.task('default',['one','two','three']);
</code></pre>
<p>如果任务相互之间没有依赖，任务会按你书写的顺序来执行，如果有依赖的话则会先执行依赖的任务。<br />
但是如果某个任务所依赖的任务是异步的，就要注意了，gulp并不会等待那个所依赖的异步任务完成，而是会接着执行后续的任务。例如：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.task('one',function(){
  //one是一个异步执行的任务
  setTimeout(function(){
    console.log('one is done')
  },5000);
});

//two任务虽然依赖于one任务,但并不会等到one任务中的异步操作完成后再执行
gulp.task('two',['one'],function(){
  console.log('two is done');
});
</code></pre>
<p>上面的例子中我们执行two任务时，会先执行one任务，但不会去等待one任务中的异步操作完成后再执行two任务，而是紧接着执行two任务。所以two任务会在one任务中的异步操作完成之前就执行了。</p>
<p>那如果我们想等待异步任务中的异步操作完成后再执行后续的任务，该怎么做呢？<br />
有三种方法可以实现：<br />
第一：在异步操作完成后执行一个回调函数来通知gulp这个异步任务已经完成,这个回调函数就是任务函数的第一个参数。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.task('one',function(cb){ //cb为任务函数提供的回调，用来通知任务已经完成
  //one是一个异步执行的任务
  setTimeout(function(){
    console.log('one is done');
    cb();  //执行回调，表示这个异步任务已经完成
  },5000);
});

//这时two任务会在one任务中的异步操作完成后再执行
gulp.task('two',['one'],function(){
  console.log('two is done');
});
</code></pre>
<p>第二：定义任务时返回一个流对象。适用于任务就是操作gulp.src获取到的流的情况。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.task('one',function(cb){
  var stream = gulp.src('client/**/*.js')
      .pipe(dosomething()) //dosomething()中有某些异步操作
      .pipe(gulp.dest('build'));
    return stream;
});

gulp.task('two',['one'],function(){
  console.log('two is done');
});
</code></pre>
<p>第三：返回一个promise对象，例如</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var Q = require('q'); //一个著名的异步处理的库 https://github.com/kriskowal/q
gulp.task('one',function(cb){
  var deferred = Q.defer();
  // 做一些异步操作
  setTimeout(function() {
     deferred.resolve();
  }, 5000);
  return deferred.promise;
});

gulp.task('two',['one'],function(){
  console.log('two is done');
});
</code></pre>
<p><code>gulp.task()</code>就这些了，主要是要知道当依赖是异步任务时的处理。</p>
<h5><strong>3.4 gulp.watch()</strong></h5>
<p><code>gulp.watch()</code>用来监视文件的变化，当文件发生变化后，我们可以利用它来执行相应的任务，例如文件压缩等。其语法为</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.watch(glob[, opts], tasks)
</code></pre>
<p>glob 为要监视的文件匹配模式，规则和用法与gulp.src()方法中的glob相同。<br />
opts 为一个可选的配置对象，通常不需要用到<br />
tasks 为文件变化后要执行的任务，为一个数组</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.task('uglify',function(){
  //do something
});
gulp.task('reload',function(){
  //do something
});
gulp.watch('js/**/*.js', ['uglify','reload']);
</code></pre>
<p><code>gulp.watch()</code>还有另外一种使用方式：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.watch(glob[, opts, cb])
</code></pre>
<p><strong>glob</strong>和<strong>opts</strong>参数与第一种用法相同<br />
<strong>cb</strong>参数为一个函数。每当监视的文件发生变化时，就会调用这个函数,并且会给它传入一个对象，该对象包含了文件变化的一些信息，<code>type</code>属性为变化的类型，可以是<code>added</code>,<code>changed</code>,<code>deleted</code>；<code>path</code>属性为发生变化的文件的路径</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">gulp.watch('js/**/*.js', function(event){
    console.log(event.type); //变化类型 added为新增,deleted为删除，changed为改变 
    console.log(event.path); //变化的文件的路径
});
</code></pre>
<h4>4、一些常用的gulp插件</h4>
<p>gulp的插件数量虽然没有grunt那么多，但也可以说是应有尽有了，下面列举一些常用的插件。</p>
<h5><strong>4.1 自动加载插件</strong></h5>
<p>使用<a href="https://www.npmjs.com/package/gulp-load-plugins" title="gulp-load-plugins" target="_blank">gulp-load-plugins</a><br />
安装：<code>npm install --save-dev gulp-load-plugins</code><br />
要使用gulp的插件，首先得用<code>require</code>来把插件加载进来，如果我们要使用的插件非常多，那我们的<code>gulpfile.js</code>文件开头可能就会是这个样子的：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    //一些gulp插件,abcd这些命名只是用来举个例子
    a = require('gulp-a'), 
    b = require('gulp-b'),
    c = require('gulp-c'),
    d = require('gulp-d'),
    e = require('gulp-e'),
    f = require('gulp-f'),
    g = require('gulp-g'),
    //更多的插件...
    z = require('gulp-z');
</code></pre>
<p>虽然这没什么问题，但会使我们的<code>gulpfile.js</code>文件变得很冗长，看上去不那么舒服。<code>gulp-load-plugins</code>插件正是用来解决这个问题。<br />
<code>gulp-load-plugins</code>这个插件能自动帮你加载<code>package.json</code>文件里的gulp插件。例如假设你的<code>package.json</code>文件里的依赖是这样的:</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">{
  "devDependencies": {
    "gulp": "~3.6.0",
    "gulp-rename": "~1.2.0",
    "gulp-ruby-sass": "~0.4.3",
    "gulp-load-plugins": "~0.5.1"
  }
}
</code></pre>
<p>然后我们可以在<code>gulpfile.js</code>中使用<code>gulp-load-plugins</code>来帮我们加载插件：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp');
//加载gulp-load-plugins插件，并马上运行它
var plugins = require('gulp-load-plugins')();
</code></pre>
<p>然后我们要使用<code>gulp-rename</code>和<code>gulp-ruby-sass</code>这两个插件的时候，就可以使用<code>plugins.rename</code>和<code>plugins.rubySass</code>来代替了,也就是原始插件名去掉gulp-前缀，之后再转换为驼峰命名。<br />
实质上<code>gulp-load-plugins</code>是为我们做了如下的转换</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">plugins.rename = require('gulp-rename');
plugins.rubySass = require('gulp-ruby-sass');
</code></pre>
<p><code>gulp-load-plugins</code>并不会一开始就加载所有<code>package.json</code>里的gulp插件，而是在我们需要用到某个插件的时候，才去加载那个插件。<br />
最后要提醒的一点是，因为<code>gulp-load-plugins</code>是通过你的<code>package.json</code>文件来加载插件的，所以必须要保证你需要自动加载的插件已经写入到了<code>package.json</code>文件里，并且这些插件都是已经安装好了的。</p>
<h5><strong>4.2 重命名</strong></h5>
<p>使用<a href="https://www.npmjs.com/package/gulp-rename" title="gulp-rename" target="_blank">gulp-rename</a><br />
安装：<code>npm install --save-dev gulp-rename</code><br />
用来重命名文件流中的文件。用<code>gulp.dest()</code>方法写入文件时，文件名使用的是文件流中的文件名，如果要想改变文件名，那可以在之前用<code>gulp-rename</code>插件来改变文件流中的文件名。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    rename = require('gulp-rename'),
    uglify = require("gulp-uglify");

gulp.task('rename', function () {
    gulp.src('js/jquery.js')
    .pipe(uglify())  //压缩
    .pipe(rename('jquery.min.js')) //会将jquery.js重命名为jquery.min.js
    .pipe(gulp.dest('js'));
    //关于gulp-rename的更多强大的用法请参考https://www.npmjs.com/package/gulp-rename
});
</code></pre>
<h5><strong>4.3 js文件压缩</strong></h5>
<p>使用<a href="https://www.npmjs.com/package/gulp-uglify" title="gulp-uglify" target="_blank">gulp-uglify</a><br />
安装：<code>npm install --save-dev gulp-uglify</code><br />
用来压缩js文件，使用的是uglify引擎</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    uglify = require("gulp-uglify");

gulp.task('minify-js', function () {
    gulp.src('js/*.js') // 要压缩的js文件
    .pipe(uglify())  //使用uglify进行压缩,更多配置请参考：
    .pipe(gulp.dest('dist/js')); //压缩后的路径
});
</code></pre>
<h5><strong>4.4 css文件压缩</strong></h5>
<p>使用<a href="https://www.npmjs.com/package/gulp-minify-css" title="gulp-minify-css" target="_blank">gulp-minify-css</a><br />
安装：<code>npm install --save-dev gulp-minify-css</code><br />
要压缩css文件时可以使用该插件</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    minifyCss = require("gulp-minify-css");

gulp.task('minify-css', function () {
    gulp.src('css/*.css') // 要压缩的css文件
    .pipe(minifyCss()) //压缩css
    .pipe(gulp.dest('dist/css'));
});
</code></pre>
<h5><strong>4.5 html文件压缩</strong></h5>
<p>使用<a href="https://www.npmjs.com/package/gulp-minify-html" title="gulp-minify-html" target="_blank">gulp-minify-html</a><br />
安装：<code>npm install --save-dev gulp-minify-html</code><br />
用来压缩html文件</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    minifyHtml = require("gulp-minify-html");

gulp.task('minify-html', function () {
    gulp.src('html/*.html') // 要压缩的html文件
    .pipe(minifyHtml()) //压缩
    .pipe(gulp.dest('dist/html'));
});
</code></pre>
<h5><strong>4.6 js代码检查</strong></h5>
<p>使用<a href="https://www.npmjs.com/package/gulp-jshint" title="gulp-jshint" target="_blank">gulp-jshint</a><br />
安装：<code>npm install --save-dev gulp-jshint</code><br />
用来检查js代码</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    jshint = require("gulp-jshint");

gulp.task('jsLint', function () {
    gulp.src('js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter()); // 输出检查结果
});
</code></pre>
<h5><strong>4.7 文件合并</strong></h5>
<p>使用<a href="https://www.npmjs.com/package/gulp-concat" title="gulp-concat" target="_blank">gulp-concat</a><br />
安装：<code>npm install --save-dev gulp-concat</code><br />
用来把多个文件合并为一个文件,我们可以用它来合并js或css文件等，这样就能减少页面的http请求数了</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    concat = require("gulp-concat");

gulp.task('concat', function () {
    gulp.src('js/*.js')  //要合并的文件
    .pipe(concat('all.js'))  // 合并匹配到的js文件并命名为 "all.js"
    .pipe(gulp.dest('dist/js'));
});
</code></pre>
<h5><strong>4.8 less和sass的编译</strong></h5>
<p>less使用<a href="https://www.npmjs.com/package/gulp-less" title="gulp-less" target="_blank">gulp-less</a>,安装：<code>npm install --save-dev gulp-less</code></p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    less = require("gulp-less");

gulp.task('compile-less', function () {
    gulp.src('less/*.less')
    .pipe(less())
    .pipe(gulp.dest('dist/css'));
});
</code></pre>
<p>sass使用<a href="https://www.npmjs.com/package/gulp-sass" title="gulp-sass" target="_blank">gulp-sass</a>,安装：<code>npm install --save-dev gulp-sass</code></p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    sass = require("gulp-sass");

gulp.task('compile-sass', function () {
    gulp.src('sass/*.sass')
    .pipe(sass())
    .pipe(gulp.dest('dist/css'));
});
</code></pre>
<h5><strong>4.9 图片压缩</strong></h5>
<p>可以使用<a href="https://github.com/sindresorhus/gulp-imagemin" title="gulp-imagemin" target="_blank">gulp-imagemin</a>插件来压缩jpg、png、gif等图片。<br />
安装：<code>npm install --save-dev gulp-imagemin</code></p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant'); //png图片压缩插件

gulp.task('default', function () {
    return gulp.src('src/images/*')
        .pipe(imagemin({
            progressive: true,
            use: [pngquant()] //使用pngquant来压缩png图片
        }))
        .pipe(gulp.dest('dist'));
});
</code></pre>
<p>gulp-imagemin的使用比较复杂一点，而且它本身也有很多<a href="https://www.npmjs.com/browse/keyword/imageminplugin" title="插件" target="_blank">插件</a>，建议去它的<a href="https://github.com/sindresorhus/gulp-imagemin" title="项目主页" target="_blank">项目主页</a>看看文档</p>
<h5><strong>4.10 自动刷新</strong></h5>
<p>使用<a href="https://github.com/vohof/gulp-livereload" title="gulp-livereload" target="_blank">gulp-livereload</a>插件，安装:<code>npm install --save-dev gulp-livereload</code>。<br />
当代码变化时，它可以帮我们自动刷新页面<br />
该插件最好配合谷歌浏览器来使用，且要安装livereload chrome extension扩展插件,不能下载的请自行FQ。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-erlang">var gulp = require('gulp'),
    less = require('gulp-less'),
    livereload = require('gulp-livereload');

gulp.task('less', function() {
  gulp.src('less/*.less')
    .pipe(less())
    .pipe(gulp.dest('css'))
    .pipe(livereload());
});

gulp.task('watch', function() {
  livereload.listen(); //要在这里调用listen()方法
  gulp.watch('less/*.less', ['less']);
});

</code></pre>
<p>仅作技术分享，原文链接地址: <a href="https://www.cnblogs.com/2050/p/4198792.html" title="https://www.cnblogs.com/2050/p/4198792.html" target="_blank">https://www.cnblogs.com/2050/p/4198792.html</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ios11 and iphoneX UI设计规范</title>
		<link>https://www.zlhlab.top/ios11-and-iphonex-ui%e8%ae%be%e8%ae%a1%e8%a7%84%e8%8c%83/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Sun, 22 Oct 2017 14:57:40 +0000</pubDate>
				<category><![CDATA[推荐]]></category>
		<category><![CDATA[iPhone X规范]]></category>
		<category><![CDATA[UI设计规范]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=393</guid>

					<description><![CDATA[ios11 and iphoneX UI设计规范 近期苹果发布了ios11系统、iPhone8、iPhone <div class="more-link">
				 <a href="https://www.zlhlab.top/ios11-and-iphonex-ui%e8%ae%be%e8%ae%a1%e8%a7%84%e8%8c%83/" class="link-btn theme-btn"><span>Read More </span> <i class="fa fa-caret-right"></i></a>
			</div>]]></description>
										<content:encoded><![CDATA[<h3>ios11  and  iphoneX  UI设计规范</h3>
<p>近期苹果发布了ios11系统、iPhone8、iPhone 8 Plus、iPhone X等，but 网上的设计规范零散，于是入行两年的我做了一些总结，如有雷同请各位体谅，此次总结望对各位设计界的同仁有所帮助。（如有错误，请各位指出）！</p>
<p><strong>iOS 11</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01855e59c37226a8012053f81ce835.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01855e59c37226a8012053f81ce835.png" alt=""></a></p>
<p>iOS 11是苹果公司研发的操作系统。北京时间2017年6月6日凌晨1点， WWDC（苹果全球开发者大会）2017在加州圣何塞麦克恩利会议中心召开，苹果系统iOS 11正式亮相。<br />
正式版iOS 11在2017年9月份随着新品iPhone的发布推送更新升级。2017年9月20日，iOS 11正式版发布，增加的特定功能是：二维码扫描、诈骗短信识别、拼音键盘以及上海话语音识别等,其中，二维码扫描还支持“扫码解锁”摩拜单车。</p>
<p><strong>动态效果</strong></p>
<p>Dock 可以出现在任何应用的界面上，只要从屏幕下方划出就可以了。长按一个应用图标，可以将它拖到正在使用的应用之上。<br />
也就是说，屏幕上会同时显示出两个应用，再次拖动一个应用的边缘，界面就演化成 1：1 或者 1：2 的分屏。这个操作的过程有些细节，比如拖动应用边框，界面较大的那个会在边缘向内收缩——这是在告诉你它即将缩小、另一个应用要出现在隔壁了。</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01cda459c37254a8012053f8fe1add.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01cda459c37254a8012053f8fe1add.png" alt=""></a></p>
<p><strong>动态特效</strong></p>
<p>比如录屏，点击它你会看到一个从 3 到 1 的倒计时，然后它才开始录屏；电池图标点一下会变成黄色，用来开启省电模式。更多的情况下，长按图标会进入一个二级页面，比如屏幕明暗的调节。这让你更直观的看到亮度的变化。</p>
<p><strong>锁屏界面</strong></p>
<p>Apple Music 在锁屏上占据的位置更少，它变成了一张卡片，显示歌名和控制按键。原本 Music 会占据一整个锁屏，有些第三方应用还会在这里展示滚动歌词。现在的样子更清新一些了。</p>
<p><strong>搜索顺序</strong></p>
<p>在Spotlight 键入关键词，紧接着它的是推荐的关键词、通讯录、短信内容等等融合在一起的一个下拉菜单，在这下方才是与这个关键词有关的应用程序。在 iOS 10 上，这个顺序是反过来的。以及，Spotlight 会一直顶在主屏左边、也就是摆放各种插件的界面。</p>
<p><strong>模块变样</strong></p>
<p>Apple Music 在控制中心中有点特殊，如果你长按，会看到这个框上不仅显示正在播放的歌曲名称、以及暂停播放和前后首按钮，右上角还有一个小小的信号图标，这里可以让你切换正在使用的蓝牙设备，比如 AirPods、各种音响。相比之下，iOS 10 切换蓝牙设备的时候还需要你到设置中找到“蓝牙”，再一个个查看。</p>
<p><strong>拖动图标</strong></p>
<p>同时拖动多个图标到文件夹，这也是效率提升的体现，如果你要把几个应用拖进某个文件夹，iOS 11 上可以长按一个图标、同时点选其他应用，就可以把它们几个同时拖进目标文件夹之中。手机上也支持这个操作。</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01c48259c37271a8012053f8cd1c07.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01c48259c37271a8012053f8cd1c07.png" alt=""></a></p>
<p><strong>相关UI</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01ffaa59c3727ea8012053f8484299.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01ffaa59c3727ea8012053f8484299.png" alt=""></a></p>
<p><strong><em>一、图标<br />
在比较中寻求进步<br />
iOS 10 VS iOS11</em></strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01df2759c3728ea8012053f8ed7324.jpg@800w_1l_2o_100sh.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01df2759c3728ea8012053f8ed7324.jpg@800w_1l_2o_100sh.jpg" alt=""></a></p>
<p><strong>2PX线性图标</strong></p>
<p>特点：<br />
1. 未选中时图标是以2px线框形展现，选中后的当前页以填充形态展现；<br />
2. 设计、语言一致性，图标精致轻巧。</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/0194d759c3729ca801218e18ffd37a.jpg@800w_1l_2o_100sh.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/0194d759c3729ca801218e18ffd37a.jpg@800w_1l_2o_100sh.jpg" alt=""></a></p>
<p><strong>面性图标</strong></p>
<p>特点：用轻量化的面性图标代替2 PX的线性图标，以较为圆润的外观展现，使整体更加具有亲和力。</p>
<h3><strong>二、字体</strong></h3>
<p><strong>iOS 11粗体标题</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01dfdd59c372daa801218e181de88f.jpg@800w_1l_2o_100sh.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01dfdd59c372daa801218e181de88f.jpg@800w_1l_2o_100sh.jpg" alt=""></a></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01004159c37308a8012053f8eb6e8e.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01004159c37308a8012053f8eb6e8e.png" alt=""></a></p>
<p><strong>粗体</strong><br />
<strong>特点：粗体，且字体像素大，标题醒目</strong>（开个玩笑：典型的老夫设计）</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/011f7c59c37320a8012053f82c5a28.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/011f7c59c37320a8012053f82c5a28.png" alt=""></a></p>
<p>在扁平化设计中的优点：在扁平化设计中文字排版影响着信息层级展示的清晰与否，通过文字的字号、字重、颜色等的对比去建立清晰的信息层级，而不用太多的装饰元素。</p>
<p><strong>三、设计元素</strong></p>
<p><strong>圆角、卡片化、投影元素</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01a6a059c37343a801218e185dfd7a.jpg@800w_1l_2o_100sh.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01a6a059c37343a801218e185dfd7a.jpg@800w_1l_2o_100sh.jpg" alt=""></a></p>
<p>特点：圆角弧度的增大让用户的视觉更轻快，柔和；卡片式设计贯穿在界面各个图文排版中，起到了规整图文内容的作用。投影元素的加入，令设计元素更加独立醒目，强有力的抓住用户的注意力。</p>
<p><strong>四、界面布局中的留白</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/015a6859c37361a801218e189ffcd3.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/015a6859c37361a801218e189ffcd3.png" alt=""></a></p>
<p>特点：大标题文本包含一组带有图片和描素的元素，组元素和组元素之间保持较大的间距。<br />
优点：设计元素和元素之间保持足够的间距，可以减少用来区分层级关系的不必要装饰块元素，使界面简洁、干练。</p>
<p><strong>支持设备</strong></p>
<p>iphone5s及5s以上<br />
ipad air/air2及以上<br />
ipod touch第六代</p>
<p><strong>iPhone X</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01ba1159c37375a801218e18c57e4c.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01ba1159c37375a801218e18c57e4c.jpg" alt=""></a></p>
<p><strong>iPhone界面规范</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01f54e59c3739ea801218e18907930.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01f54e59c3739ea801218e18907930.png" alt=""></a></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/015b7d59c3739ea8012053f88f8071.jpg@800w_1l_2o_100sh.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/015b7d59c3739ea8012053f88f8071.jpg@800w_1l_2o_100sh.jpg" alt=""></a></p>
<p><strong>不同机型升级iOS 11效果展</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01048359c37406a801218e18f08dbd.jpg@800w_1l_2o_100sh.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01048359c37406a801218e18f08dbd.jpg@800w_1l_2o_100sh.jpg" alt=""></a></p>
<p><strong>iPhone X规范</strong></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/0181c759c37454a8012053f80e046c.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/0181c759c37454a8012053f80e046c.png" alt=""></a></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01dfd759c37455a8012053f8e44497.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01dfd759c37455a8012053f8e44497.png" alt=""></a></p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/01860859c37456a801218e18e26d2a.jpg@800w_1l_2o_100sh.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/01860859c37456a801218e18e26d2a.jpg@800w_1l_2o_100sh.jpg" alt=""></a></p>
<p><strong>分享iOS11 GUI视觉规范</strong>（附PSD+Sketch+Adobe XD源文件）</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/iOS-11-GUI%E6%BA%90%E6%96%87%E4%BB%B6.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/iOS-11-GUI%E6%BA%90%E6%96%87%E4%BB%B6.jpg" alt=""></a></p>
<p><strong>百度网盘下载地址：</strong></p>
<ol>
<li>665M    PSD格式下载地址：链接: <a href="https://pan.baidu.com/s/1i5CFD8D" title="https://pan.baidu.com/s/1i5CFD8D" target="_blank">https://pan.baidu.com/s/1i5CFD8D</a> 密码: v3km</li>
<li>38.5M   sketch格式下载地址：链接: <a href="https://pan.baidu.com/s/1c163YcK" title="https://pan.baidu.com/s/1c163YcK" target="_blank">https://pan.baidu.com/s/1c163YcK</a> 密码: j4hs</li>
<li>7.7M    Illustrator格式下载地址： 链接: <a href="https://pan.baidu.com/s/1i5nDgBr" title="https://pan.baidu.com/s/1i5nDgBr" target="_blank">https://pan.baidu.com/s/1i5nDgBr</a> 密码: 5c27</li>
<li>三个打包下载地址：链接: <a href="https://pan.baidu.com/s/1slFNbtV" title="https://pan.baidu.com/s/1slFNbtV" target="_blank">https://pan.baidu.com/s/1slFNbtV</a> 密码: xaqt</li>
</ol>
<p>本文作技术分享，原始文章链接：<br />
1、<a href="http://www.25xt.com/?p=16136" title="iPhone8和iPhoneX发布了，iOS11视觉规范源文件也来了" target="_blank">iPhone8和iPhoneX发布了，iOS11视觉规范源文件也来了</a><br />
2、<a href="http://www.zcool.com.cn/article/ZNTU5Mzky.html" title="ios11 and iphoneX UI设计规范" target="_blank">ios11 and iphoneX UI设计规范</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Threejs学习笔记(一) 基础篇</title>
		<link>https://www.zlhlab.top/threejs%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b8%80-%e5%9f%ba%e7%a1%80%e7%af%87/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Sat, 21 Oct 2017 14:12:43 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[推荐]]></category>
		<category><![CDATA[three.js]]></category>
		<category><![CDATA[webgl]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=383</guid>

					<description><![CDATA[一个完整的3D环境包含以下元素:场景(Scene)、相机（Camera）、物体对象（Mesh）、光源（Light）、渲染器（Renderer）、控制器(Control)]]></description>
										<content:encoded><![CDATA[<h3><strong>基本概念</strong></h3>
<hr>
<p>一个完整的3D环境包含以下元素:</p>
<blockquote><p>
  1.场景(<strong>Scene</strong>)：是物体、光源等元素的容器，<br />
  2.相机（<strong>Camera</strong>）：控制视角的位置、范围以及视觉焦点的位置,一个3D环境中只能存在一个相机<br />
  3.物体对象（<strong>Mesh</strong>）：包括二维物体（点、线、面）、三维物体、粒子<br />
  4.光源（Light）：包括全局光、平行光、点光源<br />
  5.渲染器（<strong>Renderer</strong>）:指定渲染方式，如webGL\canvas2D\Css2D\Css3D等。<br />
  6.控制器(<strong>Control</strong>): 相机控件，可通过键盘、鼠标控制相机的移动
</p></blockquote>
<h3><strong>场景(Scene)</strong></h3>
<hr>
<p>物体、光源、控制器的添加必须使用<code>secen.add(object)</code>添加到场景中才能渲染出来。<br />
一个3D项目中可同时存在多个scene，通过切换render的scene来切换显示场景</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">var scene = new THREE.Scene();
var mesh=scene.getObjectByName("sky");//获取场景中name=sky的物体；
</code></pre>
<h3><strong>相机（Camera）</strong></h3>
<hr>
<p>相机根据投影方式分两种：正交投影相机和透视投影相机</p>
<blockquote><p>
  正交投影相机:THREE.OrthographicCamera(left, right, top, bottom, near, far),大小不因远近而变化<br />
  透视投影相机:THREE.PerspectiveCamera(fov, aspect, near, far)，遵循近大远小的空间规则
</p></blockquote>
<p>一般情况下，我们使用的是透视投影相机，其参数为：</p>
<blockquote><p>
  <strong>fov</strong>: 垂直方向夹角<br />
  <strong>aspect</strong>:可视区域长宽比 width/height<br />
  <strong>near</strong>:渲染区域离摄像机最近的距离<br />
  <strong>far</strong>:渲染区域离摄像机最远的距离，3仅在距离摄像机near和far间的区域会被渲染到 canvas中
</p></blockquote>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/3120010351.png" alt=""></p>
<p>相机有两个重要的参数：</p>
<blockquote><p>
  <strong>camera.position</strong>：控制相机在整个3D环境中的位置（取值为3维坐标对象-THREE.Vector3(x,y,z)）<br />
  <strong>camera.lookAt</strong>：控制相机的焦点位置，决定相机的朝向（取值为3维坐标对象-THREE.Vector3(x,y,z)）
</p></blockquote>
<p><strong>主要应用</strong></p>
<hr>
<p>1.设置相机焦点位置为原点坐标或某物体的位置坐标：<code>camera.lookAt(new THREE.Vector3(0,0,0))</code>，循环改变<code>camera.position</code>的位置，可以实现围绕物体旋转 360度观看物体的动画。</p>
<p>2.同时循环设置<code>camera.lookAt</code>和<code>camera.position</code>,可以实现以第一人称视角在空间自由移动的动画</p>
<h3><strong>物体（Mesh）</strong></h3>
<hr>
<p>一个完整的物体对象<strong>mesh</strong>包括形状<strong>Geometry</strong>和材质<strong>Material</strong></p>
<ul>
<li><strong>Mesh</strong>：三维物体，包括<code>Geometry</code>、<code>Material</code>，设置其name属性可以通过<code>scene.getObjectByName(name)</code>获取该物体对象;</li>
<li><strong>Geometry</strong>：包括平面<code>Plane</code>、圆形<code>Circle</code>、立方体<code>Cube</code>、球体<code>Sphere</code>、圆柱<code>Cylinder</code>、多面体<code>Polyhedron</code>等，拥有以下属性：</li>
<li><strong>Material</strong>：包括基础材质<code>MeshBasicMaterial</code>，<code>深度材质MeshDepthMaterial</code>、法向材质<code>MeshNormalMaterial</code>、<code>面材质MeshFacelMaterial</code>、朗柏材质<code>MeshLambertMaterial</code>、phone材质<code>MeshPhongMaterial</code>、着色器材质<code>ShaderMaterial</code></li>
</ul>
<p>以下代码以创建name为sky一个立方体为例，使用6张图片作为材质贴到立方体内部</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">var path = "resource/sky/";
var format = '.jpg';
var urls = [
        path + 'px' + format, path + 'nx' + format,
        path + 'py' + format, path + 'ny' + format,
        path + 'pz' + format, path + 'nz' + format
    ];
var materials = []; 
for (var i = 0; i &lt; urls.length; ++i) {
    var loader = new THREE.TextureLoader();
    loader.setCrossOrigin( this.crossOrigin );
    var texture = loader.load( urls[i], function(){}, undefined, function(){} );
    materials.push(new THREE.MeshBasicMaterial({
        map: texture, 
        overdraw: true,
        side: THREE.BackSide,
        //transparent: true,
        //needsUpdate:true
    })
    ); 
} 
var cube = new THREE.Mesh(new THREE.CubeGeometry(9000,9000,9000), new THREE.MeshFaceMaterial(materials)); 
cube.name="sky";
scene.add(cube);
</code></pre>
<p>记住以下几点：</p>
<ol>
<li>物体<code>mesh</code>的大小是由构成三维物体形状的顶点坐标<code>mesh.geometry.vertices</code>和缩放级别<code>mesh.scale(vector3)</code>决定的,初始化创建物体时由传入的参数确定了形状顶点坐标<code>mesh.geometry.vertices</code>，后面需要修改物体大小使用mesh.scale进行缩放</li>
<li>物体的透明度是材质的透明度属性<code>mesh.material.opacity</code>决定的，若需要设置透明度，需将材质的是否支持半透明属性<code>mesh.material.transparent</code>设置为true</li>
<li>在页面运行期间需要改变物体的材质或属性需要同步设置<code>mesh.material.needsUpdate=true</code></li>
<li>若物体使用了<code>THREE.MultiMaterial</code>材质（如上面的例子如果多张图作为贴图材质），则会将所有材质放到<code>mesh.meterials</code>数组中，修改材质需要对每一个材质<code>mesh.materials.material</code>进行单独修改</li>
<li>修改<code>mesh.rotation</code>、<code>mesh.position</code>、<code>mesh.visible</code>可设置物体旋转角度、位置、可见性。注意将物体的visible从false改成true,可能会造成画面卡顿</li>
</ol>
<h3><strong>光源（Light）</strong></h3>
<hr>
<blockquote><p>
  <strong>全局光</strong>：THREE.AmbientLight，影响整个scene的光源，一般是为了弱化阴影或调整整体色调，可设置光照颜色，以颜色的明度确定光源亮度<br />
  <strong>平行光</strong>：THREE.DirectionalLight，模拟类似太阳的光源，所有被照射的区域亮度是一致的，可设置光照颜色、光照方向（通过向量确定方向），以颜色的明度确定光源亮度<br />
  <strong>点光源</strong>：THREE.PointLight：单点发光，照射所有方向，可设置光照强度，光照半径和光颜色
</p></blockquote>
<p>以下分别在 scene中添加了全局光，1个平行光及5个点光源，项目中平行光作为主要光源、点光源主要用于暗部补光。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">var ambient = new THREE.AmbientLight( 0xcccccc );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xcccccc );
directionalLight.position.set( -2, 9, 1).normalize();//设置平行光方向
scene.add( directionalLight );
var pointlight = new THREE.PointLight(0xffffff, 1, 3000); 
pointlight.position.set(0, 0, 2000);//设置点光源位置
scene.add( pointlight );
var pointlight2 = new THREE.PointLight(0xffffff, 1, 2000);
pointlight2.position.set(-1000, 1000, 1000);
scene.add( pointlight2 );
var pointlight_back = new THREE.PointLight(0xffffff, 1, 2000); 
pointlight_back.position.set(0, 0, -2000);
scene.add( pointlight_back );
var pointlight_left = new THREE.PointLight(0xffffff, 1, 2000); 
pointlight_left.position.set( -2000, 0,0);
scene.add( pointlight_left );
var pointlight_right = new THREE.PointLight(0xffffff, 1, 2000); 
pointlight_right.position.set(0,  -2000, 0);
scene.add( pointlight_right );
</code></pre>
<h3><strong>渲染器(Renderer)</strong></h3>
<hr>
<p>一般情况下我们使用的是WebGL渲染器。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">var renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );//设置canvas的像素比为当前设备的屏幕像素比，避免高分屏下模糊
renderer.setSize( _container.offsetWidth, _container.offsetHeight );//设置渲染器大小,即canvas画布的大小
container.appendChild( renderer.domElement );//在页面中添加canvas
</code></pre>
<h3><strong>控制器（Controls）</strong></h3>
<hr>
<blockquote><p>
  <strong>FlyControls</strong>:飞行控制，用键盘和鼠标控制相机的移动和转动<br />
  <strong>OrbitControls</strong>::轨道控制器，模拟轨道中的卫星，绕某个对象旋转平移，用键盘和鼠标控制相机位置<br />
  <strong>PointerLockControls</strong>:指针锁定，鼠标离开画布依然能被捕捉到鼠标交互，主要用于游戏<br />
  <strong>TrackballControls</strong>：轨迹球控制器，通过键盘和鼠标控制前后左右平移和缩放场景<br />
  <strong>TransformControls</strong>:变换物体控制器，可以通过鼠标对物体的进行拖放等操作
</p></blockquote>
<p>示例：</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta charset="UTF-8"&gt;
        &lt;title&gt;&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
    &lt;div id="space"&gt;&lt;/div&gt;  
    &lt;script   src="https://code.jquery.com/jquery-1.12.4.min.js"   integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="   crossorigin="anonymous"&gt;&lt;/script&gt;
    &lt;script src="../js/lib/threejs/three.js"&gt;&lt;/script&gt;

    &lt;script src="../js/lib/threejs/OrbitControls.js"&gt;&lt;/script&gt;
    &lt;script&gt;


            var container, stats;

            var camera, scene, renderer;

            var mouseX = 0, mouseY = 0;

            var windowHalfX = window.innerWidth / 2;
            var windowHalfY = window.innerHeight / 2;


            init();
            animate();
            var mesh;

            function init() {

                container = document.getElementById("space")
                camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 5000 );
                camera.position.set(0, 0, 1500);

                scene = new THREE.Scene();

                var ambient = new THREE.AmbientLight( 0xffffff );
                scene.add( ambient );


                var directionalLight = new THREE.DirectionalLight( 0xffffff );
                directionalLight.position.set( -5, 5, 5).normalize();
                scene.add( directionalLight );

                var pointlight = new THREE.PointLight(0x63d5ff, 1, 200); 
                pointlight.position.set(0, 0, 200);
                scene.add( pointlight );                
                var pointlight2 = new THREE.PointLight(0xffffff, 1, 200); 
                pointlight2.position.set(-200, 200, 200);
                scene.add( pointlight2 );
                var pointlight3 = new THREE.PointLight(0xffffff, 1.5, 200); 
                pointlight3.position.set(-200, 200, 0);
                scene.add( pointlight3 );

                var controls = new THREE.OrbitControls(camera,container);
            //controls.maxPolarAngle=1.5;
            //controls.minPolarAngle=1;
            controls.enableDamping=true;
            controls.enableKeys=false;
            controls.enablePan=false;
            controls.dampingFactor = 0.1;
            controls.rotateSpeed=0.1;
    //      controls.enabled = false;
            //controls.minDistance=1000;
            //controls.maxDistance=3000;



                var path = "../resource/sky/";
                var format = '.jpg';
                var urls = [
                        path + 'px' + format, path + 'nx' + format,
                        path + 'py' + format, path + 'ny' + format,
                        path + 'pz' + format, path + 'nz' + format
                    ];
                var skyMaterials = []; 
                for (var i = 0; i &lt; urls.length; ++i) {
                    var loader = new THREE.TextureLoader();
                    loader.setCrossOrigin( this.crossOrigin );
                    var texture = loader.load( urls[i], function(){}, undefined, function(){} );

                    skyMaterials.push(new THREE.MeshBasicMaterial({
                        //map: THREE.ImageUtils.loadTexture(urls[i], {},function() { }), 
                        map: texture, 
                        overdraw: true,
                        side: THREE.BackSide,
                        //transparent: true,
                        //needsUpdate:true,
                        premultipliedAlpha: true
                        //depthWrite:true,

        //              wireframe:false,
                    })
                    ); 

                } 

                var cube = new THREE.Mesh(new THREE.CubeGeometry(500, 500,500), new THREE.MeshFaceMaterial(skyMaterials)); 
                cube.name="sky";
                scene.add(cube);
                renderer = new THREE.WebGLRenderer();
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                container.appendChild( renderer.domElement );

                document.addEventListener( 'mousemove', onDocumentMouseMove, false );
                window.addEventListener( 'resize', onWindowResize, false );

            }

            function onWindowResize() {

                windowHalfX = window.innerWidth / 2;
                windowHalfY = window.innerHeight / 2;

                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize( window.innerWidth, window.innerHeight );

            }

            function onDocumentMouseMove( event ) {

                mouseX = ( event.clientX - windowHalfX ) / 2;
                mouseY = ( event.clientY - windowHalfY ) / 2;

            }

            //

            function animate() {

                requestAnimationFrame( animate );
                render();

            }

            function render() {

//              camera.position.x += ( mouseX - camera.position.x ) ;
//              camera.position.y += ( mouseY - camera.position.y ) ;

                camera.lookAt( scene.position );

                renderer.render( scene, camera );

            }

    &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>手机阅读请扫描下方二维码：</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/10/%E4%B8%8B%E8%BD%BD.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/10/%E4%B8%8B%E8%BD%BD.png" alt=""></a></p>
<p>本文仅供技术分享。原文链接地址： <a href="http://feg.netease.com/archives/266.html" title="http://feg.netease.com/archives/266.html" target="_blank">http://feg.netease.com/archives/266.html</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>微信广告规范与设计&#8211;自定义H5</title>
		<link>https://www.zlhlab.top/%e5%be%ae%e4%bf%a1%e5%b9%bf%e5%91%8a%e8%a7%84%e8%8c%83%e4%b8%8e%e8%ae%be%e8%ae%a1-%e8%87%aa%e5%ae%9a%e4%b9%89h5/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Mon, 16 Oct 2017 07:18:11 +0000</pubDate>
				<category><![CDATA[推荐]]></category>
		<category><![CDATA[H5]]></category>
		<category><![CDATA[广告规范]]></category>
		<category><![CDATA[微信朋友圈]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=366</guid>

					<description><![CDATA[微信广告规范与设计，自定义H5详情页设计规范]]></description>
										<content:encoded><![CDATA[<p><span style="font-family: helvetica, arial, sans-serif;">查看详情页也可支持自定义H5页面，用来展示更多内容。</span></p>
<h3 id="-"><span style="font-family: helvetica, arial, sans-serif;">交互规范</span></h3>
<ul>
<li><span style="font-family: helvetica, arial, sans-serif;">分屏切换，支持横向和竖向，滑动指引需清晰</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">若详情页加载较慢，需设计loading页，给予用户友好的提示</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">如有视频，需在底部加上“建议在Wi-Fi环境下播放”提示</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">建议一个页面不超过两种以上交互操作</span></li>
</ul>
<h3 id="-"><span style="font-family: helvetica, arial, sans-serif;">设计规范</span></h3>
<ul>
<li><span style="font-family: helvetica, arial, sans-serif;">页面尺寸建议：640像素×1100像素</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">最小按钮宽度：160像素</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">最小按钮高度：60像素</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">页面最小字号：不小于24像素</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">二维码最小尺寸：200像素×200像素 ，边距至少为10像素</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">背景音乐规范：必须支持音乐可循环及可关闭，并采用渐强的方式出现</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">音乐播放/关闭按钮，参考下图规范，并放置于页面右上角位置</span></li>
</ul>
<p><span style="font-family: helvetica, arial, sans-serif;"><img decoding="async" src="https://wximg.qq.com/wxp/home-site/markdown-media/GF_H_01.png" alt="音乐按钮规范图片" /></span></p>
<h3><span style="font-family: helvetica, arial, sans-serif;"> </span></h3>
<h3 id="h5-"><span style="font-family: helvetica, arial, sans-serif;">H5页面内嵌入全屏视频规范</span></h3>
<ul>
<li><span style="font-family: helvetica, arial, sans-serif;">建议：使用流媒体播放，并添加可跳过播放按钮</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">时长：≤ 45 秒</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">视频编码：H.264/AVC</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">FPS：推荐 24fps</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">码率：≤ 800 kbit/s</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">音频格式：AAC</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">音频码率：≤ 96 kbit/sec</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">音频采样率：推荐 44.1 kHz</span></li>
<li><span style="font-family: helvetica, arial, sans-serif;">文件大小限制：≤ 5MB ( 实际大小与时长及码率对应，如20秒视频，大小为 20秒×（800码率÷8）÷1024=1.95MB )</span></li>
</ul>
<h3 id="-h5-"><span style="font-family: helvetica, arial, sans-serif;">自定义H5设计规范</span></h3>
<p><span style="font-family: helvetica, arial, sans-serif;">自定义H5页面请严格遵守《<a title="微信广告制作规范" href="https://ad.weixin.qq.com/learn/nav/43">微信广告制作规范</a>》中关于“<a title="广告详情页制作基础规范" href="https://ad.weixin.qq.com/learn/n123">广告详情页制作基础规范</a>”等规范。</span></p>
<p><span style="font-family: helvetica, arial, sans-serif;">更多设计、广告规范，请移步 <a href="https://ad.weixin.qq.com/learn/n8">微信广告</a></span></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>前端图像处理指南</title>
		<link>https://www.zlhlab.top/%e5%89%8d%e7%ab%af%e5%9b%be%e5%83%8f%e5%a4%84%e7%90%86%e6%8c%87%e5%8d%97/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Fri, 29 Sep 2017 02:55:01 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[推荐]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[css3]]></category>
		<category><![CDATA[图像处理]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=333</guid>

					<description><![CDATA[本文会介绍canvas位图处理，SVG矢量图和CSS3图像处理，重点是canvas，并且最后会附上一个[TrimPNG小应用]]]></description>
										<content:encoded><![CDATA[<p>计算机图像处理是一门很成熟的技术，任何一门可操作系统接口的语言都能很轻易的实现各种处理操作。但是前端限于浏览器环境和接口限制，处理起来会有诸多不便，这里所说的前端图像处理，是真的指不借助任何后端服务纯前端实现的图像处理。本文会介绍canvas位图处理，SVG矢量图和CSS3图像处理，重点是canvas，并且最后会附上一个<a href="http://quanfeng.tech/trimpng/" title="TrimPNG小应用" target="_blank">TrimPNG小应用</a>。</p>
<h4><strong>1. canvas位图处理</strong></h4>
<hr>
<p>HTML5 canvas为我们提供了一块画布，让前端也有了操作位图的功能：图片旋转、缩放、滤镜、压缩等都可以通过JS来实现。</p>
<p><strong>图像基本处理</strong></p>
<p>通过设置drawImage参数可以实现图片绘制、缩放、拉伸和裁剪等操作(注意: canvas无法绘制体积过大图片，否则会卡甚至崩掉，大图可以分块读取绘制)：</p>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/09/canvas_drawImage_DEMO_-_JS_Bin.png-q80.png" alt="图像基本处理" title="图像基本处理"></p>
<blockquote><p>
  详细用法参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage" title="drawImage()" target="_blank">drawImage()</a>，DEMO源码戳这里 <a href="http://jsbin.com/nuqojowayi/edit?html,output" title="JS Bin" target="_blank">JS Bin</a>.
</p></blockquote>
<p>只需要drawImage一个方法，就可以实现基本图形处理功能，再结合鼠标或滚轮事件，就可以实现更复杂的局部放大预览，缩放等功能了。</p>
<p><strong>图像滤镜处理</strong></p>
<p>现在的朋友圈发个图都要用滤镜美一下，复古清纯胶片LOMO各种风格应有尽有。canvas提供了getImageData方法来获取图像上每一个像素点的RGBA信息，这样我们就能对图片进行像素级处理了。通过特定算法来重写imageData中的像素信息，然后用setImageData方法把新的数据重新绘制在canvas上，这样就可以实现图像滤镜打码加特效等一系列功能。</p>
<p>比如我们现在要实现一个复古滤镜:</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">// 复古滤镜处理算法：获取每个像素的RGB信息，并按特定权重返回其加权平均值
let sepiaFilter = function(imgData) {
  let d = imgData.data
  for (let i = 0; i &lt; d.length; i += 4) {
    let r = d[i]
    let g = d[i + 1]
    let b = d[i + 2]
    d[i] = (r * 0.393) + (g * 0.769) + (b * 0.189) // red
    d[i + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168) // green
    d[i + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131) // blue
  }
  return imgData
}
//图像地址必须和当前页面同域，否则会报cross-origin错误 
img.src = '/img/logo@2x.png' 
img.onload = () =&gt; {
  ctx.drawImage(img, 0, 0) // 绘制原图
  let imgData = ctx.getImageData(0, 0, img.width, img.height) // 获取图片信息
  ctx.putImageData(sepiaFilter(imgData), 100, 0) // 绘制处理后图片
}
</code></pre>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/09/3834833829-58706ff1760a3_articlex.png" alt="图像滤镜处理" title="图像滤镜处理"></p>
<blockquote><p>
  详细用法参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getImageData" title="getImageData() " target="_blank">getImageData() </a>和 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData" title="putImageData()" target="_blank">putImageData()</a>，DEMO源码戳这里 <a href="https://jsfiddle.net/vghszt61/2/" title="JSFiddle" target="_blank">JSFiddle</a>
</p></blockquote>
<p>滤镜处理关键在于滤镜算法，要想写出更高级的特效需要有计算机图形学基础，对卷积矩阵、拉普拉斯变换、傅里叶变换等数学知识也要了解，这个坑很大我就不挖了。</p>
<p><strong>图像base64存储</strong><br />
加完特效后如果我们想把图像保存下来，就可以用toDataURL方法来对图片进行格式转换、压缩存储了。</p>
<pre class="line-numbers prism-highlight" data-start="1"><code class="language-javascript">// 支持三种文件类型：image/png(默认) | image/jpeg | image/webp(仅Chrome)
canvas.toDataURL() // 默认存储为png
// 仅jpeg/webp支持质量参数(0~1,默认0.92)
canvas.toDataURL("image/jpeg", 0.1) // 存储为质量为0.1的jpeg
</code></pre>
<p>由于存储形式是base64编码，原来图片的每三个字节都会被扩展成4字节，所以整体上编码后数据会比原来多约1/3。以下是通过toDataURL存储后的图片和原图大小相关对比数据(数据仅供参考，不具通用性)：</p>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/09/yy.jpg" alt="图像base64存储" title="图像base64存储"></p>
<p>可见存储后图片体积并不是原来的4/3，实际上处理后的图片都会比原图大好几倍，并且不同的图片增大的体积也是不确定的。如果是要上传图片到服务器，可以把base64转化成Blob二进制数据压缩上传；如果要直接在前端显示或供用户下载，jpg还好可以设置质量参数，要是png就没法压缩了。</p>
<p>只是用toDataURL还不够，用户需要通过手动点击图片-右键图片存储为来保存图片，如果要实现点击下载按钮自动下载图片还需要修改图片类型为octet-stream，然后利用HTML5的download属性强制让浏览器下载。</p>
<blockquote><p>
  详细用法参考<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL" title="toDataURL()" target="_blank">toDataURL()</a>. 自动下载图片DEMO源码快戳我 <a href="https://jsfiddle.net/0326/vghszt61/4/#" title="JSFiddle" target="_blank">JSFiddle</a>.
</p></blockquote>
<p><strong>程序员的备胎</strong></p>
<p>有了以上基础，再结合成熟的图形处理算法，我们可以完成日常工作中大部分图像处理需求，以下列出了一些相关轮子可做备胎。备胎这种东西多多益善，万一以后用到了呢？</p>
<p><a href="http://html2canvas.hertzen.com/documentation.html" title="html2canvas" target="_blank"><strong>html2canvas</strong></a></p>
<p>将web页面通过canvas来实现截屏，其原理就是遍历DOM结构和样式，然后在canvas中绘制出来，通过toDataURL输出图片。但由于canvas图片的同源策略限制，如果图片和网站不同源的话会截取不出来的。另外在微信中测试时，即使用同源的图片截取出来的图片也有问题，所以要想将其用于生产环境，还是得看场景，有很多坑要踩。</p>
<p><a href="https://github.com/akira-cn/code-to-image" title="code-to-image" target="_blank"><strong>code-to-image</strong></a></p>
<p>代码转图片工具。有时候你在不同的平台写文章贴代码，由于不同平台代码格式化规范不一，所以经常会出现代码排版问题，通过这个工具将代码转成图片就可以避免排版问题了。</p>
<p><a href="https://fengyuanchen.github.io/cropper/" title="Cropper" target="_blank"><strong>Cropper</strong></a></p>
<p>一个专门用来做图片裁剪的应用。</p>
<p><a href="https://github.com/eduardolundgren/tracking.js/" title="tracking.js" target="_blank"><strong>tracking.js</strong></a></p>
<p>这是一个专业的计算机视觉处理JS库，包含了大量图形处理算法，可用来做人脸识别，色彩追踪等酷炫功能。</p>
<p><a href="https://github.com/nicolaszhao/qrcode2" title="qrcode2" target="_blank"><strong>qrcode2</strong></a></p>
<p>用JS动态生成二维码，这个库还是很实用的，原理就是qrcode算法+canvas绘图，不支持canvas的用table兼容。</p>
<p><a href="https://github.com/AlloyTeam/AlloyImage" title="AlloyImage" target="_blank"><strong>AlloyImage</strong></a><br />
腾讯出的基于HTML5的专业级图像处理开源引擎，功能很强大，简直就是Web版的PS。</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/09/TB1qqF.PXXXXXXFXXXXXXXXXXXX-1381-597.png" title="AlloyImage" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/09/TB1qqF.PXXXXXXFXXXXXXXXXXXX-1381-597.png" alt="AlloyImage" title="AlloyImage"></a></p>
<p>以上列举部分，更多备胎在此： <a href="https://github.com/0326/canvas" title="https://github.com/0326/canvas" target="_blank">https://github.com/0326/canvas</a></p>
<h4><strong>2. SVG矢量图处理</strong></h4>
<hr>
<p>讲完位图再说矢量图。矢量图在绘制图标、商业LOGO、动画元素上应用非常广范。Web最开始支持的矢量图形并不是现在的SVG，而是微软主推的VML，所以在低版本IE下面只支持VML而不支持SVG，直到后来SVG成为W3C标准并被普及，微软才在IE9中支持SVG。</p>
<p>SVG遵循XML规范，可以很好的集成在HTML里面，同时支持JS脚本控制，还有基于SMIL标准的动态内容支持，做起动画来也是非常方便。目前基于SVG的JS图形库轮子也是非常多，如svg.js, Snap.svg, Velocity.js, D3.js等等，目前暂无SVG应用需求，等用的时候再翻牌子吧。</p>
<h4><strong>3. CSS图像处理</strong></h4>
<hr>
<p>如果你的图像特效只是用来给用户展示，并不需要存储的话，可以直接用CSS处理，基本的调整图片大小、拉伸、旋转、裁切等操作直接几个CSS属性width/height/skew/rotate/clip-path等就能搞定了。如果想加特效，使用CSS滤镜Duang的一下就出来了，不需要任何图形学基础和数学知识：</p>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/09/591D15F1-F2EB-410A-983B-62F698769406.jpg" alt="CSS图像处理" title="CSS图像处理"></p>
<blockquote><p>
  详细说明见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter" title="CSS filter - MDN" target="_blank">CSS filter &#8211; MDN</a>, <a href="http://jsbin.com/cejifa/edit?html,output" title="DEMO 源码见JS Bin" target="_blank">DEMO 源码见JS Bin</a>.
</p></blockquote>
<h4><strong>4. 总结</strong></h4>
<hr>
<p>最后介绍个小工具TrimPNG。大家都知道TinyPNG可以压缩PNG, TrimPNG顾名思义就是去除图片空白和白边的，实现了自动抠图和切图的功能（logo我是直接抄的TinyPNG，都是熊猫，应该不要紧?）</p>
<p><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/09/448077878-5871e8180d77c_articlex.png" alt="TrimPng" title="TrimPng"></p>
<p>用到的技术点上面都说到了，裁切实现原理就是横向纵向分别扫描两次像素点阵，找出上下左右最外面的非透明点，然后定位出有效图像区域。<del>去除白边算法目前还比较傻逼，只要是接近白色的点都被我干掉了，后面再完善好了</del> 去白边算法优化了一下，采用标记清除策略（听着耳熟吧，其实就是从GC受的启发&#8230;），先扫描一遍标记出可疑白点，然后再扫描一遍，凡是与透明区域相邻的可疑白点就清除，当然这个算法也比较死，如果有更专业更智能的请务必告诉我。</p>
<p>体验地址：<a href="http://quanfeng.tech/trimpng/" title="http://quanfeng.tech/trimpng/" target="_blank">http://quanfeng.tech/trimpng/</a></p>
<p>本文仅作技术分享和学习。原文地址：<a href="https://segmentfault.com/a/1190000008044204" title="https://segmentfault.com/a/1190000008044204" target="_blank">https://segmentfault.com/a/1190000008044204</a>。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>H5制作和营销都有哪些免费工具可用或者h5微场景软件可以推荐？</title>
		<link>https://www.zlhlab.top/h5%e5%88%b6%e4%bd%9c%e5%92%8c%e8%90%a5%e9%94%80%e9%83%bd%e6%9c%89%e5%93%aa%e4%ba%9b%e5%85%8d%e8%b4%b9%e5%b7%a5%e5%85%b7%e5%8f%af%e7%94%a8%e6%88%96%e8%80%85h5%e5%be%ae%e5%9c%ba%e6%99%af%e8%bd%af/</link>
		
		<dc:creator><![CDATA[萧邦]]></dc:creator>
		<pubDate>Wed, 20 Sep 2017 07:57:12 +0000</pubDate>
				<category><![CDATA[推荐]]></category>
		<guid isPermaLink="false">https://www.zlhlab.top/?p=314</guid>

					<description><![CDATA[之前做过一些活动宣传推广类的H5，现在网各种H5的编辑器非常多，大部分的都是免费的。 常用的制作平台有：ih5<div class="more-link">
				 <a href="https://www.zlhlab.top/h5%e5%88%b6%e4%bd%9c%e5%92%8c%e8%90%a5%e9%94%80%e9%83%bd%e6%9c%89%e5%93%aa%e4%ba%9b%e5%85%8d%e8%b4%b9%e5%b7%a5%e5%85%b7%e5%8f%af%e7%94%a8%e6%88%96%e8%80%85h5%e5%be%ae%e5%9c%ba%e6%99%af%e8%bd%af/" class="link-btn theme-btn"><span>Read More </span> <i class="fa fa-caret-right"></i></a>
			</div>]]></description>
										<content:encoded><![CDATA[<p>之前做过一些活动宣传推广类的H5，现在网各种H5的编辑器非常多，大部分的都是免费的。</p>
<p><strong>常用的制作平台有：ih5、BlueMP、maka、易企秀、木疙瘩等。</strong></p>
<p>总的来说各家的产品在功能上都差不多，都是将组件拖拽到画布上，然后调节各页的图片、动画之后，就可以发布到微信朋友圈了，大致的操作其实和做PPT差不多。</p>
<p>但对于0基础的朋友，想制作好的H5页面，可能不仅仅需要一个H5制作平台。好的H5，需要创意+设计+制作+推广一系列环节的紧密配合。</p>
<p>对于一个H5页面的制作，首先要有良好的页面设计、良好的用户观感，才能让里面的内容更清晰、更明确的得以展现。</p>
<p>我个人在制作H5页面的过程中，会配合使用这些以下创意工具。有需要的可以自行搜索。</p>
<h4>1. 灵感获取：</h4>
<p>可以到Dribbble、Behance、Pinterest、花瓣等国内外设计师汇聚的设计类聚合平台，借鉴学习顶尖设计师们的优秀设计</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/07/v2-ebf0799887c5501e465813c516d41efd_r.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/07/v2-ebf0799887c5501e465813c516d41efd_r.png" alt=""></a></p>
<h4>2. 图片素材：</h4>
<p>向X度图片搜索说再见，微软的Bing图库、wallhaven、500px都是非常高质量的图库。</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/07/web.jpg" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/07/web.jpg" alt=""></a></p>
<p>需要一些小的图标推荐阿里的iconfont图标库，有大量精美的icon可以免费使用。</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/07/v2-7b9a05cf9cfb3c3b28cb83a56d0a90a7_r.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/07/v2-7b9a05cf9cfb3c3b28cb83a56d0a90a7_r.png" alt=""></a></p>
<h4>3.色彩搭配：</h4>
<p>很多人做H5的时候，很少考虑色彩的搭配，都是挑一些自己喜欢的、看着顺眼的就行了。其实色彩搭配的选择，对H5页面设计感的提升非常大。小白的话推荐coolors，直接从推荐的色彩主题里进行选择，记下色彩的值，在H5制作工具里输入使用就好了。</p>
<p><a href="https://www.zlhlab.top/wp-content/uploads/2017/09/v2-9374c868ce913eceba190ff447f0f03e_r-1.png" target="_blank"><img decoding="async" src="https://www.zlhlab.top/wp-content/uploads/2017/09/v2-9374c868ce913eceba190ff447f0f03e_r-1.png" alt=""></a></p>
<p>作者：老嘴<br />
链接：https://www.zhihu.com/question/45723553/answer/150061371<br />
来源：知乎<br />
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
