<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[范明非的Blog 开发技术分享网]]></title><description><![CDATA[范明非的Blog]]></description><link>https://fanmingfei.com/</link><generator>RSS for Node</generator><lastBuildDate>Mon, 03 Mar 2025 05:12:19 GMT</lastBuildDate><atom:link href="https://fanmingfei.com/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 03 Mar 2025 05:12:19 GMT</pubDate><language><![CDATA[zh-cn]]></language><ttl>60</ttl><item><title><![CDATA[前女（男）友，吃饭啦！智能硬件的第一步，使用手机控制esp32/esp8266开发板]]></title><description><![CDATA[<p>在完成垃圾桶的<a href="https://fanmingfei.com/posts/Embeded_Start.html">超声波传感器开关控制以后</a>（这次的设备和环境配置都是基于之前文章的基础上的），我在想是不是可以通过语音打开垃圾桶，例如：“xxx，吃饭啦！”，垃圾桶就可以自动打开。</p>
<p>我本来还以为需要自己去训练语音AI模型，后来发现有现成的语音识别模块，后来想了想，我直接接入智能音箱不就好了。</p>
<p>我通过米家官网发现需要通过公司资质才能接入，所以我从搜索引擎随便搜了搜，发现有一些IoT平台可以直接接入智能音箱。比如<a href="https://diandeng.tech/home">点灯</a>，<a href="https://bemfa.com/">巴法云</a>。</p>
<p>我一开始用了巴法云，发现我的设备没有办法在他的后台上线，所以我就转用了点灯。</p>
<p>点灯是可以接入小爱、天猫进来、小度的。</p>
<p><img src="https://src.fanmingfei.com/images/bc403698bae82f394820f91774231b9c.jpg" alt=""></p>
<p>下载下来，在 Arduino IDE 里面导入库</p>
<p><img src="https://src.fanmingfei.com/images/a47530fae8910d2ac38564ee73cd43b3.jpg" alt=""></p>
<p>这样我们就可以在文件&gt;示例里面找到一个使用手机点亮LED灯的实例。</p>
<p><img src="https://src.fanmingfei.com/images/38d2a21cc015e6d818e04dee61e1ced2.jpg" alt=""></p>
<p>得到如下代码</p>
<pre><code>
#define BLINKER_WIFI

#include &lt;Blinker.h&gt;

char auth[] = &quot;Your Device Secret Key&quot;;
char ssid[] = &quot;Your WiFi network SSID or name&quot;;
char pswd[] = &quot;Your WiFi network WPA password or WEP key&quot;;

// 新建组件对象
BlinkerButton Button1(&quot;btn-abc&quot;);
BlinkerNumber Number1(&quot;num-abc&quot;);

int counter = 0;

// 按下按键即会执行该函数
void button1_callback(const String &amp; state)
{
    BLINKER_LOG(&quot;get button state: &quot;, state);
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

// 如果未绑定的组件被触发，则会执行其中内容
void dataRead(const String &amp; data)
{
    BLINKER_LOG(&quot;Blinker readString: &quot;, data);
    counter++;
    Number1.print(counter);
}

void setup()
{
    // 初始化串口
    Serial.begin(115200);
    BLINKER_DEBUG.stream(Serial);
    BLINKER_DEBUG.debugAll();

    // 初始化有LED的IO
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);
    // 初始化blinker
    Blinker.begin(auth, ssid, pswd);
    Blinker.attachData(dataRead);

    Button1.attach(button1_callback);
}

void loop() {
    Blinker.run();
}

</code></pre><p>我们打开手机上的点灯-blinker的App（注册点灯需要先下载App），在App里面点添加设备&gt;点灯&gt;独立设备。</p>
<p><img src="https://src.fanmingfei.com/images/55e655d9de235e8247f7da63e8c18350.jpg" alt=""></p>
<p>然后他会给我们一个 Secret Key，把Key复制到代码里面，替换代码里的 <code>Your Device Secret Key</code>。</p>
<p>然后在代码里输入我们的wifi的账号密码。</p>
<p>然后把代码烧录到硬件里。</p>
<p>回到手机APP里面，点击新添加的设备，选择载入示例。</p>
<p><img src="https://src.fanmingfei.com/images/d92da603ae2b9c93755711d7842ad00a.jpg" alt=""></p>
<p>可以看到一个可以控制器，这个控制器我们是可以点击右上角进行编辑的。</p>
<p>可以看到里面的按钮和信息会对应到我们代码的内容。</p>
<p>比如，Button1对应的就是UI界面里面的点我开灯，可以看到左上角的btn-abc对应的就是代码里面的<code>Button1(&#39;btn-abc&#39;)</code>
下面这句话代表按钮点击后触发的回调函数</p>
<pre><code>Button1.attach(button1_callback);
</code></pre><p>目前为止我们的效果是这样的：
<img src="https://src.fanmingfei.com/images/20230404-192548.gif" alt=""></p>
<p>我们只需要把<code>button1_callback</code>函数替换成打开垃圾桶的方法就可以了。</p>
<p>虽然看起来可以用手机控制，但是距离我们使用语音控制还有一段距离。</p>
<p>我在官网找到了一个<a href="https://diandeng.tech/doc/xiaoai">接入小爱同学的例子</a>。</p>
<p>代码编译烧录以后，发现我手头没有小爱同学，小爱同学被我放到新家了。</p>
<p>所以这也是这篇文章为什么不叫使用小爱同学控制开发板的原因，手动狗头</p>
<p>等我搬到新家，就试一试。</p>
]]></description><link>https://fanmingfei.com/posts/Control_by_Platform.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/Control_by_Platform.html</guid></item><item><title><![CDATA[来自智能垃圾桶的诱惑，嵌入式开发初探]]></title><description><![CDATA[<h2 id="-">背景原因</h2>
<p><img src="https://src.fanmingfei.com/images/cb91970ca322769d7073b63379658635.jpg" alt=""></p>
<p>最近裸辞离职在老家，正在收拾老家的房子准备住进去，但迫于经济压力，只能先装非常有必要的东西,例如床、马桶、浴室柜等。</p>
<p><img src="https://src.fanmingfei.com/images/20230327-221040.gif" alt=""></p>
<p>但是垃圾桶是生活必备品，我发现大家现在都在用智能垃圾桶，那种可以感应开盖的，还有那种自动封袋的。我极其的羡慕。但是迫于经济压力，我没有足够的资金购买智能垃圾桶！</p>
<p><img src="https://src.fanmingfei.com/images/1cc0d8418a824bc8923ab5f264ebf7e8.jpg" alt=""></p>
<p>作为一个学美术的程序员，我在想我要不要去少儿美术培训教小孩，或者去街头卖艺，甚至去给人画遗照赚点钱呢？我想了想算了，给别人留点活路吧，我这么专业的美术人才去了，那些老师、街头艺人、画师不得喝西北风去。我想了想，我除了是学美术的，我还是个程序员啊！</p>
<p>虽然我没有学过嵌入式、硬件、IOT等等技术，但是入门应该不太困难吧！已经好多年没有从0开始学习一门技术了，非常怀念当时学习Web技术的那种感觉，可以没日没夜的去探索新知识，学习东西。之前上班的时候根本没有精力去学习或者搞一些有趣的东西，下班只想躺着看动漫，听爽文。既然现在赋闲在家，那就搞一搞吧！</p>
<h2 id="-">项目启动</h2>
<p>俗话说得好，找到一个好师傅就是成功的一半！在这方面我有着得天独厚的优势，我的前公司，北京犬安科技就是一个做车联网安全的公司，这家公司的Slogan是：从硅到云，守护网联汽车安全。可以看出，犬安的硬件软件安全能力都非常的强。像我，天天和测试组的同事去山西面馆吃鱼香肉丝盖饭，喝青岛雪花，他们组里面都是硬件大佬，甚至有人造过火箭。此时不向他们学习，更待何时？</p>
<p>他们告诉我，我需要一个开发板、一个超声波传感器、一个舵机。</p>
<p>我还在研究什么是stm32/esp32的时候，他们告诉我用Arduino，特别简单。我就去研究Arduino，在B站上搜了 Arduino，有一个播放量比较高的系列教程看了一下。我顿时就悟了！</p>
<p>我本来以为我需要买烙铁，焊电路板的，后来我发现，只需要买以上提到的三个东西，外加一个面包版，一些杜邦线就可以，甚至可以不需要面包版。</p>
<p><img src="https://src.fanmingfei.com/images/3f06e8bee4babca7a8daba3a23cbe81d.jpg" alt=""></p>
<p>当然为了学习，我在淘宝上买了一些乱七八糟的东西，比如按钮、各种电阻、各种颜色led灯、面包版。买各种颜色的LED灯的时候，购买欲泛滥了，就想每个颜色都买一些，什么白发红，白发黄，白发翠绿，后来我买回来发现，不亮的情况下都是白色的，我根本分不清颜色。原来那个白发黄的意思就是不亮的时候是白色，亮了以后发黄色的光～我还买了接受RGB三色的LED灯，不知道为啥不是RGBA，难道不能让灯透明吗？</p>
<p><img src="https://src.fanmingfei.com/images/bb83b3aee0190ad57e71bfaee22b9334.jpg" alt=""></p>
<p>我还发现不同欧姆的电阻他上面的“杠杠”是不一样的。</p>
<p><img src="https://src.fanmingfei.com/images/59dde279dc201815944381f3a73a9b8d.jpg" alt=""></p>
<p>主要是这玩意太便宜了，两块钱就买好多，但是作为新手玩家来说，根本用不了。我只买了公对公的杜邦线，然后我又单独下单了其他杜邦线，果然两块多就能包邮。</p>
<p><img src="https://src.fanmingfei.com/images/76f41517d2644f1fca69a1f8e4f8fddc.jpg" alt=""></p>
<p>我在不同的店铺里面选了很多配件，包括我使用的主要配件：Arduino uno 开发板、SG90舵机、HC-SR04 超声波传感器。</p>
<h2 id="-">开搞</h2>
<p>设备买回来以后，废了9牛2虎之力才成功的能把我写的程序上传到开发板里。</p>
<p>一开始一直报下面这个错误，网上众说纷纭，始终没有找到解决方案。</p>
<pre><code>avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x30
</code></pre><p>后来我去淘宝详情页仔细研究了一番才搞明白，我本以为我买的是Arduino的开发板，其实是esp8266，只是兼容Arduino。我从淘宝详情页找到了正确姿势，成功的刷入了程序。</p>
<p>看了 Arduino 的视频，我主要悟出来了什么？</p>
<p>开发板、舵机、传感器上有很多小尖尖或者小洞洞，他叫引脚，我们需要把舵机、传感器的引脚，通过一种名为杜邦线的线（通常有公对公/母对母/公对母，代表线两头是小尖尖还是小洞洞），连接到开发板的引脚上。然后使用程序控制某个引脚的电平向舵机发送信号，或者接受传感器的信号。</p>
<h2 id="-">第一个项目</h2>
<p>我举一个最简单的LED灯闪烁的例子：
线是这么接的：</p>
<p><img src="https://src.fanmingfei.com/images/e8e01dd6518240b5ecb56deae5fe9d96.jpg" alt=""></p>
<p>面包版上面，竖着的五个小洞洞里面其实是连在一起的。我的电路是从一个引脚出来，到了一个电阻上，然后连接一个LED灯，然后接地。
接地的概念应该和负极的概念很像，但是不是，不过我暂且不关心它。
为什么不直接从引脚出来接LED，再接地呢，因为视频上说，LED几乎是没有电阻的，如果直接从串口出来接到LED，直接接地的话，开发板就废了，所以加了个电阻。</p>
<p>接下来我让ChatGPT帮我写一个小LED灯闪烁的代码：</p>
<p><img src="https://src.fanmingfei.com/images/40c3715329ce5e3d1e23bb695791a790.jpg" alt=""></p>
<p>我们可以看到在loop里面，调用了<code>digitalWrite</code>，把那个引脚进行了高低电平的切换，这样就实现了小LED灯闪烁的效果，高电平就相当于给小LED灯供电了。</p>
<p>我们只需要把他写的代码中的引脚的编号改成我们的编号就好了。</p>
<p>每种开发板的引脚编号都不一样！我在淘宝上买的这个板，他没有给我资料，我干了一件特别愚蠢的事情，分别给不同的数字编号供电，插线看哪个亮，就记录对应开发板上的引脚号和数字。</p>
<p>第二天我在网上稍微搜了一下资料就找到了。后来我发现，竟然还有一个编号对应两个引脚的，具体为什么我也不知道。</p>
<p>不过具体怎么给舵机传信号，或者接受传感器的信号呢？如果在 Arduino IDE 里面的话就是简单的调用API就好了。这一块我没写代码，都是ChatGPT帮我写的。</p>
<p><img src="https://src.fanmingfei.com/images/25284a9b074f601c31432f4481204800.jpg" alt=""></p>
<h2 id="-">实现目标的主要部分</h2>
<p>我使用同样的方法，制作了垃圾桶的功能的电路和代码。
使用超声波传感器的时候，我发现这玩意都好神奇啊，仿佛是魔法一样。
比如超声波传感器，要先发送一个超声波信号出去，然后等待回波信息，拿到发送和接收的时间差，通过计算得到距离。</p>
<p><img src="https://src.fanmingfei.com/images/924cb4c3245aca2e8c4cb72a679e05a5.jpg" alt=""></p>
<p>我在调试超声波传感器的时候，我发现我能听到超声波传感器发出的声音，只要耳朵对着它不用靠太近就可以听得到。我朋友说我是超级耳。</p>
<p>有了LED灯的经验以后，超声波识别距离，调用舵机旋转很快就实现了。</p>
<p><img src="https://src.fanmingfei.com/images/20230328-001242.gif" alt=""></p>
<p>最开始我在考虑这个舵机的力道到底能不能支撑起来垃圾盖子，不过现在感觉力道还是挺大的。不过现在没有办法试，因为我还没有做出来合适的垃圾桶。</p>
<h2 id="-">接下来</h2>
<p>接下来我要去找到一个合适的垃圾桶，我也不确定需要用什么方式去打开垃圾桶的盖子，是不是需要其他的比如初中学过的滑轮、齿轮、杠杆原理什么的？这块还没有开始涉猎，等我接下来研究研究来写第二部分。</p>
<p>感谢大家阅读～</p>
]]></description><link>https://fanmingfei.com/posts/Embeded_Start.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/Embeded_Start.html</guid></item><item><title><![CDATA[一个大型图编辑应用的实现方案]]></title><description><![CDATA[<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<h2 id="-">背景</h2>
<p>目前市场上有很多软件，通过使用图形连线加上一些编辑的功能去实现一些业务需求，比如说蓝图软件、流程图的软件、PPT 软件和电子电路设计图软件等等。从五月份到现在，我们在公司开发了一个车联网安全应用，并对它进行了一些思考设计和落地实施。</p>
<p>开发大型图编辑应用软件，我们需要明确哪些问题需要解决，这些问题可以从我们目前的产品效果入手。由于网联汽车安全的项目比较复杂，涉及到非常多的行业概念，比较晦涩难懂，为了让大家能够更加清晰地了解我们项目的大致内容，我设计了另外一个项目（如下方所描述），从而有利于理解：</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>我们要组建一个前端培训学校来招收学生，第一个学员明非，就职于犬安科技，是一名拥有 15 年编程经验的前端工程师。我们给他添加了“架构师”，“本科“标签；第二个学员张三，大三在读，前端初学者，本科学历。此时可以直接使用之前的“本科”标签，从左边拖入标签，也可以在右侧添加新的标签“小白”，来描述张三的技术水平，最后我们总计招收到两个学员。项目的第一步流程结束。</p>
<p>我们需要解决以下问题：</p>
<ul>
<li>创建图形 / 编辑画布内容（实现图编辑）</li>
<li>画布/右侧表单/底部列表数据实时同步 （数据同步）</li>
<li>不同对象右侧表单的生成 （表单生成）</li>
</ul>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>明非的需求: 学习node JS并达到精通水平；张三的需求：学习前端三门语言并达到熟悉水平；我们为每位学员选择相应的关联技术和关联目标，后台统计技术和目标数据并在右侧展示。项目的第二步流程结束。</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060099450-f8a6fdda-f7de-47fd-9ae5-b90c34aad078.png" alt="img"></p>
<p>我们发现，各个数据是分布在各个步骤中使用的。例如：成员数据可以在图编辑和关联技术中使用，成员数据和技术数据组成个人计划数据，并在关联目标中使用，关联目标关联着目标数据。</p>
<p>我们需要解决以下问题：</p>
<ul>
<li>多个步骤同一份数据的数据存储（数据存储）</li>
<li>多数据之间如何进行关联（数据关联）</li>
</ul>
<p>我们要解决的问题可以分为三类：</p>
<ul>
<li><p>图编辑实现</p>
<ul>
<li>ReactFlow/X6的选择</li>
<li>如何解决复杂图编辑需求</li>
<li>遇到问题怎么办？</li>
<li>如何与业务进行结合</li>
</ul>
</li>
<li><p>流畅的全局数据和事件系统</p>
<ul>
<li>统一的数据列表</li>
<li>统一的事件通讯</li>
</ul>
</li>
<li><p>表单生成和数据变化监听</p>
<ul>
<li>表单生成方案</li>
<li>绘图数据的无缝对接</li>
<li>步骤数据的实时修改</li>
</ul>
</li>
</ul>
<h2 id="-">图编辑实现</h2>
<ul>
<li>创建图形/编辑画布内容（实现图编辑）</li>
</ul>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060099588-c69a006f-24df-4243-8f2b-2d39e4ef13dc.png" alt="img"></p>
<p>我们在日常开发中遇到的需求可能是做一个流程图属性图，使用的一些图编辑框架的基本的编辑能力即可实现。但更多情况，我们会结合我们的业务需求实现各种能力。</p>
<h3 id="-">如何解决复杂图编辑需求?</h3>
<p>例如：</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>实现连接桩的移动，并且避免移动中两个连接桩重合；移动到四个边角，圆角位置会发生变化。</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>实现连接桩的业务逻辑：不同属性的连接桩有不同的颜色，颜色不同，无法连接；连接状态的连接桩，其他属性的连接桩会变暗，从而表示不能连接。</p>
<h3 id="-">如何选择框架?</h3>
<p>我们需要找到一个十分靠谱的框架来实现上述复杂需求，ReactFlow和X6是市场上比较火的两个框架。我们根据自身所需要的功能，又根据两个软件的官网介绍可以提供的解决方案，如下图所示：</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060099658-3e0086e5-ba48-4dec-bb1a-9451538cdc05.png" alt="img"><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060099441-625e7071-6d23-4b3a-8f29-d1e43ec4d04c.png" alt="img"></p>
<p>通过调研，我们得出以下结论：</p>
<ol>
<li>react flow 实现了更符合前端的一些开发逻辑，但多数功能没有，需要自己开发；</li>
<li>X6 编辑能力比较完善，虽然增加底层功能会相对麻烦，但X6 现有的底层功能几乎满足我们所有定制化需求。</li>
</ol>
<p>我们最终选择X6，事实证明我们的选择是正确的。</p>
<p>例如，解决我们项目中的一个复杂场景：</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>从一个内部连接桩连接到外部的连接桩：在每个跨越的元素上创建一个连接桩，然后把连接桩相互连接，在最后的连接线上创建一个圆点，然后把连接线拆成两根连到圆点上。</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060099535-8a38f168-2273-4096-bc40-9426b2666190.png" alt="img"></p>
<p>我们基于X6提供的一些API， 抽离了一些常用方法，并结合我们自己的业务逻辑去应对。</p>
<h3 id="-">遇到问题怎么办？</h3>
<p>使用开源库会发生很多问题：有些是我们的使用姿势问题，有些是开源库的一些bug；在使用一些普通功能，例如流程图，由于 X6经过很多项目的考验，基本没有什么问题；但我们的项目深度使用X6的各种功能，因此会出现一些问题；</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100149-8bd993ba-bbab-4304-99ef-2ff7c5913843.png" alt="img"></p>
<p>图左边是我在仓库提的问题，X6的官方人员响应十分及时。但有些问题是我们独有的，需要自己动手解决。右边其实是我提PR 解决问题的一个截图，我向 X6 提了三次PR ，每次都在两天之内就合并。它会自动去发布一个新版本，并可以直接在项目里投入使用。</p>
<h3 id="-">如何与业务进行结合</h3>
<p>业务逻辑不仅仅是编辑图像，我们还需要将图像结合到业务中去。下面我将用车联网安全产品来展示这一个demo的数据关系图。</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>元素可以包含子元素，元素上面会有连接桩，可以移动；连接桩中间有连接线；连接线中会有数据的流动。</p>
<p>由此可见，我们不仅需要X6帮助我们实现图的编辑和展示，还需要底层的数据引擎来实现绑定关系。</p>
<p>实现了一个数据引擎：</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100214-a45bdd34-381e-429d-92b4-3e8907bb64aa.jpeg" alt="img"></p>
<p>只看代码就是节点、连接桩、边、数据，比较简单；</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100181-360bac33-87ba-48a6-bdc2-5a6024107a40.png" alt="img"></p>
<p>用面向对象的方式去做一些数据连接关系来绑定关系，只需要利用X6的事件和钩子，然后来调用。</p>
<p>例如：</p>
<p>添加一个node、连接桩和编的时候，监听X6的事件，来创建我们自己数据引擎的元素数据、连接桩和边。然后通过X6的一些功能，例如在一个元素里面添加一个子元素，我们的数据也会进行同步更新。</p>
<p>也可以调用我们数据引擎的一些方法，反过来操作X6上面图的一些变化。通过这种方式，我们可以解决非常多的图和数据绑定的问题，可以用在数据流程图、电子软件自动化、电子设计自动化软件、游戏引擎低代码工具等等工程中。</p>
<h2 id="-">数据存储和数据关联</h2>
<ul>
<li>多个步骤使用同一份数据的数据存储（数据存储）</li>
<li>多数据之间如何进行关联（数据关联）</li>
</ul>
<p>由于前端图编辑的逻辑比较复杂，我们强依赖前端的各种操作交互，因此图编辑步骤完全由前端自行管理；关联技术和关联目标是由前端调用服务端（后端）去完成的。</p>
<p>下面是一个图编辑画布中数据变化修改的数据流程图</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>当图编辑的节点数据发生变化，一方面要保存到前端的数据存储里面，另外一方面要把它保存到服务端，具体流程是通过一个事件系统告诉事件系统的一个模型，去保存图编辑的一个信息，事件系统会调用请求模块，通过数据服务把图编辑的节点信息存储到服务端。</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>当我们进入一个关联目标的步骤的时候，这个步骤就是一个React 文件。进入文件以后，我们需要个人计划数据和目标数据，走到事件系统，告诉个人计划，取个人计划数据，告诉个人目标，取个人目标数据，然后通过请求回到服务端。返回服务端以后，需要进行一个数据处理（后面会讲到数据处理的原因）。数据处理结束以后，生成一个前端可用的数据，并存放在数据存储器里面。数据存储器发生变化，里面的React 文件会响应它的变化。</p>
<p>我们需要下面的三个要点：</p>
<ol>
<li>将业务进行拆分，区分出多个模型；</li>
<li>每个模型只负责自己的事情，但是它可以相互调用；</li>
<li>通过命名空间区分模型，并且提供类型提示。</li>
</ol>
<p>模型的区分最好是由前端后端共同理解业务需求，一起探讨总结得出，这样才能更好的理解业务，有助于我们的系统设计概念清晰、结构合理，从而更加可理解、可维护。</p>
<p>例如：</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100189-3bdab8c6-0fc6-42c8-885e-ecca35e74166.jpeg" alt="img"></p>
<p>将培训学校的这个项目分成了四种数据、四个步骤 、五个模型，分布在各个步骤和各个数据当中。</p>
<p>数据设计：</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100695-43da2b91-208f-4d4f-b794-dfb6f42bcb49.png" alt="img"></p>
<p>使用store 的一个对象，store 对象里面会有命名空间，使用 set, on, get 分别去设置数据、监听数据的变化和获取数据。然后在React里面，直接使用useStore来获取这个数据和监听它的变化。</p>
<p>事件设计：</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100799-5a032646-ca30-4078-b9f6-6a14db59a6bb.png" alt="img"></p>
<p>使用常用的on和emit，前面用命名空间区分。</p>
<p>将个人计划去绑定目标</p>
<p>step 1 事件绑定 数据设置</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100793-4d467941-8bab-4175-9e4b-deb60f65e281.png" alt="img"></p>
<p>给Plan和Goal定义getList获取个人计划和所有目标。</p>
<p>监听获取个人计划的一个事件，设置个人计划数据，设置成功后，触发一次 Goal 的 getList 更新最新的个人计划列表。</p>
<p>step 2 绑定目标步骤的JSX</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060100892-349b43c1-05fd-4a3a-a783-0ffb7bf84490.png" alt="img"></p>
<p>我们要监听获取目标的一个事件。右边是我们的React ，由于当前页面也需要计划和目标，所以我们要获取计划目标。需要在UC fact 里面，当刚刚进入这个步骤时，就要去触发服务端的计划和目标数据；我们会有一个方法是设置目标，我们左边去定义功能，右边在React 里面使用这些功能。</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101276-7dee1dc4-c40d-44d2-8725-432398362e3f.gif" alt="img"><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101387-8cd452cc-8810-470f-8794-6f3938a74495.png" alt="img"></p>
<p>最后我们使用TypeScript 提供类型提示，提升开发效率，降低出错率。</p>
<h2 id="-">表单生成和数据监听</h2>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101303-cb43b335-56de-4a06-91f5-335c3e7fc9ae.png" alt="img"></p>
<p>说到表单生成我会想到：通过JSON Schema 的方式来生成表单、通过MobX的方式来监听数据的变化</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101370-9a52172f-cfc3-420f-be31-f22ff9293179.png" alt="img"></p>
<p>JSON Schema用来定义JSON格式，可以校验或者生成JSON，通过这样定义，我们可以得出右上角的数据格式，也通过这种定义的方式来定义右下角的表单。</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>对一个对象进行绑定，在React 里面直接使用被实例化的一个对象，或一个实例，里面的属性发生变化的时候，React 里面的数据也会发生变化，从而解决掉我们刚才的那个数据绑定的问题。</p>
<h3 id="-">表单生成</h3>
<p>阿里巴巴Formily 的解决方案可以解决我们的需求，下面是各个竞品的一个数据对比：</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101495-88b545a7-71a8-47cb-b51b-ed56f45350cb.jpeg" alt="img"></p>
<p>Formily 学习成本比较高，但其他性能方面良好，另外作为一个中文工具，便于理解，例如可以直接在github 上发一些中文的issue。</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101851-036e8f5c-c478-42a8-8f98-511034c7851b.jpeg" alt="img"></p>
<p>使用Formily ，我们可以通过左侧的一个JSON 数据，然后生成右侧的一个表单，解决我们表单自动生成问题。</p>
<p>他可以直接将生成的数据实例和表单绑定起来，表单发生变化的时候，我们的数据也会发生变化，这是解决了我们的数据引擎里面的数据和表单的数据同步问题。</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101983-18c5b58e-f6f0-4953-b427-c10c068ab18d.jpeg" alt="img"></p>
<p>Fomily提供了一个可视化的表单生成器，不过我们在项目中没有运用。</p>
<p>我们在使用Formily 的时候会去实现一些自定义的功能，如何自定义功能呢？</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060101933-693f818e-76e8-460f-b4f5-bc89dca9d8cc.gif" alt="img"></p>
<p>比如我们点击上面的标题就能创建一个新的标签。</p>
<p>我们实现的方法是：</p>
<ol>
<li>我们在原组件的基础上去进行优化</li>
</ol>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060102119-696b2bbb-2e18-409e-a0fd-a803c1856fcb.jpeg" alt="img"></p>
<p>将原组件的代码直接复制出来，在标题上面添加了一个schema 里面的一个X-titleClick 方法。</p>
<ol>
<li>注册自定义组件</li>
</ol>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060102373-0e4a1252-0003-4b9c-b6a8-51d64d07f8f2.jpeg" alt="img"></p>
<p>把刚才我们复制出来的一个组件注册到schema field 里面。</p>
<ol>
<li>使用自定义组件，传入所需参数</li>
</ol>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060102558-4251b3fd-671b-4608-9420-c1a52584c9db.jpeg" alt="img"></p>
<p>由于我们定义了一个schema 上面的一个属性。然后我们需要在写schema 的时候输入我们的方法。 </p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060102479-d6e0a918-95a6-4785-8d78-e23a02110682.png" alt="img"></p>
<p>这样的话我们就可以通过schema 的方式去生成表单，schema 目录下面有各种需要生成表单的文件，文件里面写的都是schema。拿出来以后，当在下面列表里面进行数据选择，或者在画布里面点一下这个节点，我们就能监听到节点的变化，然后找到对应的schema 并把它放到这个React 里面显示出来。</p>
<h3 id="-">数据同步</h3>
<ul>
<li>画布/右侧表单/底部列表数据实时同步（数据同步）</li>
</ul>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060102785-53660dfe-c95f-47cc-a86b-6f38e56764a9.jpeg" alt="img"></p>
<p>由于图编辑是前端自主管理数据，绑定技术和绑定目标需请求服务端，这两个方案其实是不同的。</p>
<p>前端自主管理数据，即图编辑中，Formily解决了表单和这个数据之间的一个同步。我们还需要做另一个同步：图画布里面的元素的同步，以及底部列表的同步。</p>
<blockquote>
<p>博客有一些视频资源未展示，可到移步<a href="https://zhuanlan.zhihu.com/p/585004939">知乎文章</a>，查看完整内容</p>
</blockquote>
<p>MobX可以去监听数据变化，并做一些响应，Formily 提供了reactive 的一个库，效果和MobX相同。所以我们直接用Formily的reactive解决了这个问题。</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060102677-89498bef-fb80-4da5-813d-8d08fd3dd1e8.jpeg" alt="img"></p>
<p>我们刚才的这个数据引擎里面的node的代码中刚刚添加了name 和 description 。我们在构造函数中去定义名字和描述需要被监听。在表单里面修改的时候，会自动修改到这个 node 实例里的属性。然后我们的这个React 代码里面就可以去监听到这个属性的变化。</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060102943-bbccd746-acc1-4ebd-b3f8-41dba00b6d3e.jpeg" alt="img"></p>
<p>绑定技术和绑定目标这两个步骤如何用服务端接口去请求数据？</p>
<p><img src="./HOW_to_Create_Graph_Editor_Tool.assets/1668060103081-f2b154cc-b8be-49dc-ad5f-6484da002008.jpeg" alt="img"></p>
<p>我们来看一下如何实现动态更新个人计划吧。左边有一个获取计划列表的功能，右边我们实现了一个方法叫getPlanList。首先我们去服务端获取这个列表，然后我们去通过Formily reactive 的observe 这个方法，把它变成了可被监听的一个对象。然后通过监听 observe 方法去监听这个对象。当其发生变化时，我们就去调用更新计划方法。更新完了以后，我们接着去把计划列表刷新一下，这样的话我们就可以实现在选择计划以后去触发服务端，再接着调用getList更新最新的计划列表。</p>
<h2 id="-">总结</h2>
<ul>
<li>使用 X6 实现图编辑功能</li>
<li>创建数据引擎与 X6 进行融合</li>
<li>定义数据模型，通过Store 和Event 实现数据联动，</li>
<li>使用Formily 实现表单生成，使用@Formily/reactive 实现数据实时同步</li>
</ul>
<p>我们通过解决以上问题来实现大型图编辑应用的基础框架和能力，当然大型应用内部细节非常复杂，往往伴随着相关的专业知识，本篇文章只是给到了一些基础问题的解决方案，可能也仅仅能够解决我在开发过程中遇到的问题。如果想要进一步与作者交流，可以联系作者微信：maydayfantast</p>
]]></description><link>https://fanmingfei.com/posts/How_to_Create_Graph_Editor_Tool.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/How_to_Create_Graph_Editor_Tool.html</guid></item><item><title><![CDATA[淘系前端互动引擎Eva.js架构与生态实现]]></title><description><![CDATA[<h2 id="-">背景</h2>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598542615607-f9069b9c-2ead-4fb2-adbf-efe58ca20437.png" alt="image.png"></p>
<p>漫画中的三个人物，左边的人在玩金币庄园，金币庄园中金币可抵扣现金，用户每天都会来收金币，中间的人在玩天猫农场，通过助力可以升级果树，种成可拿到真正的水果，右边的人通过扫码得到一定权益，促使消费。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598542673367-302243ec-94f8-44cd-b9d2-fa07d9d5a063.png" alt="image.png"></p>
<p>结合平台业务属性，利用用户的目标感、成就感和社交欲打造互动游戏产品，从而达到拉新、留存、转化的效果。</p>
<p>我们需要基于一套互动引擎来打通阿里巴巴经济体互动研发工作流，未来会基于引擎实现编辑器、逻辑编排以及互动模板。另外，我们需要把控互动领域的核心技术，所以，我们需要开发一套互动引擎。</p>
<h2 id="-">要解决的问题</h2>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598542727001-45438f97-7229-49e9-b9ca-100465db0ddd.png" alt="image.png"></p>
<p>集团目前有很多互动业务，很多BU也在陆续开始进行互动业务的探索，我们需要给各个BU提供一套上手快、开发效率高、能力完善的互动开发链路，因此互动引擎至少要完成以上漫画中的三个点，沉淀最佳实践、支持编辑器、支持常用资源。
互动引擎面临以下三大挑战，以及对应策略。</p>
<ul>
<li>实现易于开发的引擎架构<ul>
<li>提供高扩展性的架构</li>
<li>支持场景编辑器</li>
</ul>
</li>
<li>支持渲染与动画能力<ul>
<li>渲染能力完备</li>
<li>动画资源类型支持完善</li>
</ul>
</li>
<li>友好的用户体验<ul>
<li>提供性能优化方案</li>
<li>提供最佳实践<h2 id="-">实现易于开发的引擎架构</h2>
</li>
</ul>
</li>
</ul>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598542940217-a2a98923-2253-4a74-9077-5cdbd8e619c9.png" alt="image.png"></p>
<p>从目前的项目中，我们发现，每个项目都有一些共同的能力，比如说动画、图片、物理碰撞、交互等能力，我们需要通过组合的方式，将这些能力拼接到游戏对象上面，然后将游戏对象放入场景中，这个过程中尽量减少重复代码编写。所以，我们的引擎架构需要以组合的方式进行，并且需要对编辑器友好。</p>
<h3 id="-">引擎架构</h3>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598542961128-0200f204-06cb-4788-bf1c-2775c701b681.png" alt="image.png"></p>
<p>通过调研，我们发现ECS模式是业界游戏引擎开发的最佳实践，因此EVA采用了ECS的设计模式作为底层架构。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598543004594-9139f7fa-d1ac-4703-bd98-2ac393bae42b.png" alt="image.png"></p>
<p>ECS具有高扩展性和高性能的特点，接下来详细看一下引擎的架构设计，我们通过游戏对象，组件和系统三层架构来实现游戏，游戏对象代表游戏中的物体，组件给予物体一些属性值，通过系统，读取组件中的属性值来给物体设置能力。这样，我们通过组件随时添加/删除游戏能力；通过组件和系统扩展游戏的功能，实现引擎架构的高扩展性。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598543125656-f55e562e-613e-4923-adc1-c0fc35ad5d1a.png" alt="image.png"></p>
<p>ECS设计中，组件是存放数据的，将组件数据存放到CPU缓存当中，实现了天然的高性能读写，游戏循环中去遍历CPU缓存中的数据非常的高效，但是由于JS无法直接控制CPU缓存，因此无法享受到ECS设计中的这种优势，但是，我通过JS的getter/setter能力，实现了属性变化收集方案，当属性变化时通知系统进行相应操作，无需对数组进行遍历，大大提升运行性能。未来，如果我们能够使用WebAssembly的能力，或者将组件数据存放到ArrayBuffer中，是否能够更好到利用CPU缓存能力呢？这非常值得我们去探索。</p>
<h3 id="-">编辑器支持</h3>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598543226166-e1c00254-b3c5-4296-a136-a108149d3ac8.png" alt="image.png"></p>
<p>ECS天然支持编辑器设计，在图中编辑器案例中，左侧是游戏对象管理器，右侧是组件管理器，游戏对象和组件组合后，通过系统能力将其渲染到画布上。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598543250244-d3b06704-773d-4c1c-96a4-2a28a72523a5.png" alt="image.png"></p>
<p>对于编辑器而言，需要获取到对应的组件和系统的参数以及属性，提供给开发者进行可视化编辑，我在ECS之上做了一层装饰器，将属性、事件、类型、参数暴露给编辑器进行使用。</p>
<p>通过ECS架构和装饰器，我们实现了高扩展性、高性能、编辑器友好的引擎架构。</p>
<h2 id="-">支持渲染与动画能力</h2>
<p>我们知道，计算机游戏都要在屏幕上显示游戏内容来与用户进行交互，通过炫酷的效果能够提升游戏体验。
这些效果是如何实现的呢？</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598543350742-73d51a30-9f2d-4a1c-a564-b588768b4b1a.png" alt="image.png"></p>
<p>首先我们通过资源生产工具导出资源文件，然后使用渲染能力将其呈现在画布上。因此，我们需要支持常用的资源文件，并且实现完备的渲染能力。</p>
<p>为了快速的支持业务，我动画与渲染的实现过程分成了三步。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598543404762-62916bdd-7ecb-4180-a547-375cd2b32837.png" alt="image.png"></p>
<p>第一步：使用业界渲染引擎，业界渲染引擎一般将常用的动画能力和渲染能力放在一起。通过这种方案，快速的实现了引擎的渲染动画能力，并且落地到业务中使用。但是我们发现，动画能力是内置到渲染引擎中的，不符合我们引擎的架构模式，我们支持新的动画也不应该内置到渲染引擎当中。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598543415546-511a611d-b49c-4376-942b-e348f519eec8.png" alt="image.png"></p>
<p>第二步，抽离动画能力，将骨骼动画，帧动画，抽离成单独的系统，通过修改渲染组件上的属性进行动画实现，保障了引擎的统一性，扩展新的动画更方便。在渲染引擎上层，实现渲染适配器，通过统一的API进行绘制，更换渲染引擎只需要实现对应的API即可，为自研渲染引擎做准备。</p>
<p>我们发现，抽离了动画能力后，业界渲染引擎中有很多能力是冗余的，我们需要更轻量级的渲染引擎，另外，随着业务的发展，业界的渲染引擎能力已经没办法支持当下的业务场景了，业务中会用到更复杂的渲染效果，比如漫画的渲染效果、在2D场景里面实现更加立体的效果，接下来我们需要会对渲染引擎做简化、改造或自研渲染引擎。</p>
<h2 id="-">友好的用户体验</h2>
<p>对于互动引擎来讲，用户分为两部分，一部分是客户端用户，一部分是开发者用户。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598544171486-af13947a-8641-4eed-aaef-880a87879c66.png" alt="image.png"></p>
<p>客户端用户中，移动设备有好有坏，用户所处的环境、国家都对用户体验有非常大的影响，例如一些低端机上，如何优化动画卡顿问题，对于国际化、无障碍、夜间模式如何支持，因此，对于客户端用户来说我们需要做到端上更稳定，效果更丰富。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598544202131-c42acd9b-4fd1-4e2e-8bb9-5614fe14ff9f.png" alt="image.png"></p>
<p>对于开发者用户，希望能够拿到一个可以快速开发互动游戏的引擎，关注引擎的学习门槛，系统健壮性，调试工具是否齐全。因此，我们需要做开发更高效，能力更完善。</p>
<p>接下来我会给大家分享三个解决方案，客户端用户的端稳定性、无障碍化、开发者的编码实践。我们来看一下具体问题和解决方案。</p>
<h3 id="-">性能/稳定性问题</h3>
<p>首先，我们来看最棘手的端稳定性问题，在互动业务开发中，最常见的问题就是首屏渲染时长和Crash问题。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598544346695-8ec7e7e5-6f28-4c77-bcfe-abad40684ed1.png" alt="image.png"></p>
<p>首屏渲染时长主要是因为互动资源非常多，资源在使用之前需要实例化，骨骼动画、帧动画的实例化时间相对较长。
目前的加载流程，一般游戏都是先加载资源，再创建对象再实例化资源，最后再渲染，以这种线性的模式进行的。
我在引擎中做了三个事情。
第一，创建游戏对象和资源加载可以并行，我们的架构中，加载和对象的创建是完全分开的，天然做到了这一点。
第二，某个资源加载成功立即实例化，我在资源管理器中实现了实例创建器，各种资源注册实例化方法进去，每当一个资源加载回来，立即进入实例创建器，这时也不影响其他资源加载。
第三，实例化后立即渲染。因为游戏对象已经被创建了，所以当实例化后会瞬间被使用。</p>
<p>通过优化，我们实现了CPU线程和加载线程同时工作，可降低首屏30%-50%的加载时长。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598544490821-1d474262-b125-408b-9b3f-886630c4e751.png" alt="image.png"></p>
<p>对于Crash的问题，骨骼动画、帧动画都运用一些合并的图片，尺寸偏大，内存占用比较高，低端机承受不住内存压力，带来了很多crash，因此，我开发了分级体验组件，通过客户端等级判断，获取当前客户端是高端机还是低端机，针对高端机低端机选择图片还是骨骼动画，通过分级体验组件，我们可以在低端机中将骨骼动画替换成图片，从而降低内存占用，在数据中看到，使用这种方法内存占用降低30-60%。但是这种方法对视觉效果是有损害的，未来，我们会探索更多对业务和视觉无伤的能力去降低内存占用。</p>
<p>我们使用了引擎提供的资源管理能力和分级体验组件解决客户端用户的体验问题，接下来我们看一下如何解决开发者体验问题。</p>
<h3 id="-">无障碍化</h3>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598544937535-33bfc002-728e-4daf-9387-e7f30edfd3ff.gif" alt="image.png"></p>
<p>我们在游戏区域之上创建了A11y层，在该层上设计符合AOM（Accessbility Object Model）标准的接口，提供给开发者用户进行无障碍信息设置，从而达到标准化。无障碍插件将会自动识别手机是否开启了系统旁白，实现自动设置无障碍能力。无障碍的应用设计并非一个全自动化的过程，而是需要投入产品设计、开发人力的，我们需要针对具体场景进行无障碍设计，在开发中我们需要针对产品的无障碍设计去写无障碍化所需的代码。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598545017826-fd84d3a0-3c74-46c0-9e43-1d546f33e49f.png" alt="image.png"></p>
<p>我们知道 Canvas 上目前是没有办法实现无障碍操作的，目前，最有效的方案是在DOM中创建对应的可操作、可阅读的DOM元素，并且设置对应的无障碍化属性。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598853684512-1ed79b41-a01b-4b41-a8a3-055ce229cefa.png" alt="image.png"></p>
<p>利用在架构中设计的组件监听器，监听A11y/Transform/Event组件的变化/添加/删除。只用被添加A11y组件的游戏对象才会被A11y系统接管，这样不会造成不必要的DOM生产和无意义的屏幕阅读，另外需要读取A11y组件上面的无障碍属性设置给DOM。监听Transform组件信息，判断游戏对象是否在游戏区域，并且读取Transform组件上面的信息设置DOM的定位以及宽高，直接读取Transform上面存储的Matrix信息设置给CSS的transform属性。最后监听Event组件的添加删除来给DOM添加事件。</p>
<p>目前无障碍化插件有几个特点：</p>
<ul>
<li>在手淘/支付宝可自动识别是否开启无障碍能力</li>
<li>自动识别无障碍对象上的交互事件</li>
<li>debug模式将无障碍对象展示出来</li>
</ul>
<p>目前，无障碍化插件提供出了符合标准的接口设计以及自动识别的能力，能够实现互动游戏的无障碍化能力。</p>
<h3 id="-">开发者体验问题</h3>
<p>对于开发者，最紧迫的事情就是如何快速开始写代码，在开发者体验中，优先做了最佳实践。
我们知道，上手一个互动游戏类引擎是有一定门槛的，对于前端开发者而言，我们更希望通过前端的思路和解决方案处理问题。
<img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598853695421-a0ef5cd3-c7ed-4a11-b782-03c4bd9554ce.png" alt="渲染层级.gif"></p>
<p>在基于H5的互动开发中，我们通常在Canvas区域实现炫酷的动画效果、游戏逻辑，使用DOM实现一些布局类操作界面。
根据这个最佳实践，我们提供了基于Rax的互动开发解决方案，根据Rax的组件规范，将互动引擎的组件进行封装，提供给开发者使用，通过统一的Rax的开发规范来开发游戏区域和DOM区域的内容。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1598946027997-ecf92386-9448-42f8-beea-7be3c91de9cb.png" alt="image.png"></p>
<p>RaxEVA提供背景、操作界面、游戏区域三个区域，我们通过Rax提供的Driver对不同对区域中的组件进行不同形式的渲染，在背景和操作界面中使用的组件将会被渲染成DOM，在游戏区域使用的组件将会被渲染到Canvas上。</p>
<p>通过这种方式，可以降低前端开发的理解成本，使用JSX+Hooks的方案进行互动游戏的开发，另外，我们可以使用完善的Rax生态进行开发，更加便利。</p>
<h2 id="-">总结</h2>
<p>我们实现了一个易扩展的引擎架构，让游戏能力解偶，并且提供了编辑器的底层支持。
官方沉淀20+常用组件，包含动画、渲染、性能、数据状态以及常用的脚本组件。降低开发成本，提高开发效率。
针对首屏加载和内存问题，提供针对性的优化方案，提升C端用户体验。针对业务开发，提供解决方案和最佳实践，提升开发者体验。
目前EVAJS还未开源，可以参考下方内容进行实践。</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2F1599552316925-8f15a459-7395-48a4-a85f-7de7c8995b44.gif" alt="gieto-82jb1.gif">
扫描二维码查看DEMO</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2Fimage.png" alt="image.png"></p>
<p><a href="https://eva.js.org/playground">https://eva.js.org/playground</a></p>
<p>文档二维码</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2Fimage_1.png" alt="image.png"></p>
<p><a href="https://eva.js.org">https://eva.js.org</a>
欢迎志同道合的同学加入，一起共建阿里巴巴互动生态体系，这里有我们的团队介绍</p>
<p><img src="https://src.fanmingfei.com/blog%2Fdoc1%2Fimage_2.png" alt="image.png"></p>
<p>如果希望进一步与作者沟通交流，可以联系作者微信：
微信：maydayfantast</p>
]]></description><link>https://fanmingfei.com/posts/Eva_js_Structure.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/Eva_js_Structure.html</guid></item><item><title><![CDATA[《第五届中国CSSConf开发者大会》参会总结]]></title><description><![CDATA[<p><a name="df368884"></a></p>
<h2 id="-">前言</h2>
<p>对于我来讲，虽然做前端那么多年，但对 CSS 的探索非常少，很多 CSS 知识还是在毕业之前积累下来。揣着一本《精通CSS:高级Web标准解决方案(第二版)》（现在出第三版了，这是我在 CSS 道路上的启蒙书）在前端路上走了这么几年。这次怀着对 CSS 的伪热爱，狠了狠心买了一张 CSSConf 的票上了车。<br /><br /><br />我个人经常会参加一些前端的会议，参加大会主要是能看看业界都在用什么方案，处理一些什么问题，虽然这些在会后都会有分享，但是实际体感还是不一样的，其实这也不是最主要原因，更重要的是能在会议上认识一些朋友，相互交流，听一些解决方案和他们遇到的问题，或许以后就是同事了。</p>
<p><a name="c6001ce1"></a></p>
<h2 id="-">开场</h2>
<p>开场的时候，严肃而又诙谐主持人，周裕波，做了开场白，描述了这可能是最后一次办 CSSConf 了，其实我是很不解的，只是说行业里有一些不同的声音，并没有具体说明原因，其实会后我问了一下他，为什么以后不再办 CSSConf 了，他回答找到好的主题比较难，大家参会意愿不强。</p>
<p>后面的议题就正式开始了。</p>
<p><a name="1b21b2af"></a></p>
<h2 id="-css-">新时代 CSS 布局 - 陈慧晶</h2>
<p>Slide：<a href="https://www.chenhuijing.com/slides/53-cssconfcn-2019/">https://www.chenhuijing.com/slides/53-cssconfcn-2019/</a><br />大会第一个议题正式开始之前，讲师上台准备 Slide，我就想这个讲师好娘啊，难道是女的么？我还发消息给朋友，说这个讲师好娘哦，朋友说：“她是个女的吧！”哈哈，真是误会了误会了，跟陈慧晶老师远程道个歉。陈慧晶老师曾经是个专业篮球运动员，后来慢慢的转向了 CSS 技术领域，在布局和中文排版方面研究非常深入。</p>
<p>陈慧晶老师主要讲解了一些关于最新的 CSS 布局方面的知识，细节讲的比较多，讲解了一些属性，并做了一些演示，让我们能够非常直观的感受到各个属性的效果。</p>
<p><a name="c12b3275"></a></p>
<h3 id="css-">CSS 显示模块</h3>
<p>陈慧晶老师讲到，之所以纵向比横向难排是因为，网页一开始是为了展示文字所产生。随着浏览器的发展，才慢慢开始支持弹性盒子、网络布局、盒模型。</p>
<p>目前已经有很多关于布局的属性，通常布局我们是使用配置 display 属性，这个属性有很多可选的值，分为<strong>内部显示模型</strong>和<strong>外部显示模型</strong>。<br /><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262454300-d4f6b38d-1a93-4872-a489-9719b828fdf4.png#align=left&amp;display=inline&amp;height=508&amp;name=image.png&amp;originHeight=1016&amp;originWidth=2296&amp;size=281065&amp;status=done&amp;width=1148" alt="image.png"></p>
<p>CSS 的 display 属性值对应不同的显示类型，下面是我从<a href="https://www.w3.org/TR/css-display-3/#outer-role">规范</a>里面复制来的：</p>
<ul>
<li>外部显示模型的值：<a href="https://www.w3.org/TR/css-display-3/#valdef-display-block">block</a>, <a href="https://www.w3.org/TR/css-display-3/#valdef-display-inline">inline</a>, <a href="https://www.w3.org/TR/css-display-3/#valdef-display-run-in">run-in</a></li>
<li>内部显示模型的值：<a href="https://www.w3.org/TR/css-display-3/#valdef-display-flow">flow</a>, <a href="https://www.w3.org/TR/css-display-3/#valdef-display-flow-root">flow-root</a>, <a href="https://www.w3.org/TR/css-display-3/#valdef-display-table">table</a>, <a href="https://www.w3.org/TR/css-display-3/#valdef-display-flex">flex</a>, <a href="https://www.w3.org/TR/css-display-3/#valdef-display-grid">grid</a>, <a href="https://www.w3.org/TR/css-display-3/#valdef-display-ruby">ruby</a> </li>
</ul>
<p>后面讲了一些 CSS flex 布局的具体用法和现象，这些在网上就可以学得到，不过我感觉 grid 布局很神奇。flex 和grid 会结合使用，小孩子才做选择题，成年人全要了。</p>
<p><a name="45dcee17"></a></p>
<h3 id="-">以内容为主的尺寸计算方式</h3>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262479074-cf84b191-fc10-4ad7-b521-1060f773eca3.png#align=left&amp;display=inline&amp;height=521&amp;name=image.png&amp;originHeight=1042&amp;originWidth=1842&amp;size=300119&amp;status=done&amp;width=921" alt="image.png"></p>
<p><a name="0f0e9524"></a></p>
<h3 id="-">灵活性尺寸</h3>
<p>这种布局方案比较灵活，可以优先使用某个尺寸，达到某个临界点才会变化，或者固定某个尺寸不变化。描述起来可能有些困难，可以参考陈慧晶老师的 <a href="https://www.chenhuijing.com/slides/53-cssconfcn-2019/#/36">Slide</a>，里面有视频。</p>
<p><a name="74cb856d"></a></p>
<h3 id="-">旧版浏览器的支持</h3>
<p>旧版浏览器使用 <code>@supports()</code> 方案，可以查看当前浏览器是否支持该属性。</p>
<p>最后陈慧晶老师问了大家一个问题，我们做的在所有浏览器上显示效果必须是一样的吗？答案是否定的，不同的浏览器显示的样式可能不一样，我们可以针对不同尺寸的浏览器进行不同的布局，当然我们也可以针对不同 CSS 支持度的浏览器进行不同的降级处理。</p>
<p><a name="af319241"></a></p>
<h2 id="-css-tricks-">剖析css-tricks新版，为你所用 - 大漠</h2>
<p>第二个议题是大漠老师的，大漠老师是<a href="https://www.w3cplus.com/"> www.w3cplus.com</a> 的站长，著有《图解CSS3：核心技术与案例实战》。他的议题是对最新版的 css-tricks 进行剖析，看看最新版 css-tricks 都用了哪些有意思的东西。大漠老师也提到了一些无障碍化的事情，讲到 css-trickers 使用黑色或许是出于对无障碍化的考虑，其实这一块也是目前业界比较关注的一方面。大漠老师已经将演讲内容发表到自己的博客：<a href="https://www.w3cplus.com/css/css-css-conf-5.html">https://www.w3cplus.com/css/css-css-conf-5.html</a>，感兴趣可以到这里来看一下。下面，我可能会根据我自己的理解来拓展一些其他的想法。</p>
<p><a name="f90b9b2a"></a></p>
<h3 id="svg-">SVG的使用</h3>
<p>在 css-tricks 中使用 SVG 比较多的地方是图标，大漠老师从性能的角度分析了，图标从小图片到 CSS Sprites 然后到 icon-font，后来使用 SVG，到 SVG Sprites 的演变，分析了每种方案的优劣。<br />QA环节有一个人问，现在 SVG 这种格式出来以后，还有其他格式的图片，是否其他格式就不需要再用了，SVG和WebP哪个好？其实，每种图片格式解决了不同的问题，SVG 是一种矢量图，解决一些简单的几何图形可以解决的图像表达问题，WebP、JPG、PNG等是一基于像素形成的位图，可以展示一些颜色和图形比较复杂的图像信息。其实他们的定位不一样。</p>
<p><a name="e16d4d95"></a></p>
<h3 id="-">滚动特性</h3>
<p><a name="e511c900"></a></p>
<h4 id="-">滚动条样式</h4>
<p>在不同的浏览器或者系统版本下，滚动条的样式其实是不同的，滚动条样式在几年前就已经可以使用了，只不过需要加 -webkit- 前缀，所以目前只支持 chrome 和 safari 浏览器，目前不支持手机上的 safari。</p>
<p><a name="e562b436"></a></p>
<h4 id="-">滚动捕捉</h4>
<p>比如在 banner 轮滚这种场景下，我们希望自动停到对应的位置，大漠老师还举了一个特别有意思的<a href="https://codepen.io/airen/pen/VRgevr">人物换装的demo</a>。</p>
<p><a name="ec47b5a2"></a></p>
<h3 id="-">自定义属性</h3>
<p>大漠老师多次强调，这叫做 CSS 自定义属性，不叫 CSS 变量。在 LESS 和 SCSS 中，也实现了类似的概念，但是在 LESS/SCSS 中，这真是一个变量，先定义了一个变量，在后续的 LESS/SCSS 中使用，但是这样的问题在于，在编译以后，这些变量对应的值就固定到了 CSS 代码中，不会再“变”。</p>
<p>例如：</p>
<pre><code class="lang-css">/* SCSS */ 
$color: red;
a { color: $color; }
</code></pre>
<p>编译后生成的代码</p>
<pre><code class="lang-css">a {
    color: red;
}
</code></pre>
<p>所以，网页上我们无法对 $color 进行修改。</p>
<p>CSS 自定义属性和一些普通 CSS 属性一样，可以被继承，也有作用域的概念。</p>
<pre><code class="lang-html">&lt;body&gt;
  Hello,
  &lt;div class=&quot;box&quot;&gt;
    CSS
    &lt;span&gt;World!&lt;/span&gt;
  &lt;/div&gt;
&lt;/body&gt;
</code></pre>
<pre><code class="lang-css">body {
    --color: red;
  color: var(--color);
}
.box {
  --color: blue;
}
span {
  color: var(--color);
}
</code></pre>
<p>demo 地址：<a href="https://codepen.io/fanmingfei/pen/mgJOvP">https://codepen.io/fanmingfei/pen/mgJOvP</a></p>
<p>body 的 –color 属性red，并且 body 的 color 属性设置成了 var(–color)，所以，body 的 color 属性是 red；</p>
<p>.box 没有设置 color，所以继承了 body 的红色，但是设置 –color 为 blue，所以 .box 里面使用 var(–color) 获取到的是 blue。</p>
<p><a name="56c64d53"></a></p>
<h4 id="-">条件判断</h4>
<p>基于自定义属性和 CSS 的一些特性，我们可以做到以前 CSS 做不到的一些条件判断的能力，例如条件判断，其实是基于自定义属性的变化引发的状态变化，后面张鑫旭的议题中也有用到这一点。<br />具体可以参考大漠老师这篇文章<a href="https://www.w3cplus.com/css/dry-switching-with-css-variables-the-difference-of-one-declaration.html">如何通过CSS自定义属性给CSS属性切换提供开关</a>。</p>
<p><a name="2ba1872b"></a></p>
<h4 id="js-in-css-houdini-">JS in CSS (Houdini)</h4>
<p>其实是基于 CSS Paint API 来定义 CSS 自定义属性。使用一个类似 Canvas2D API 的上下文，可以直接绘制图形，大漠老师的demo是将 JS 代码写在了 CSS 自定义属性后面，然后用 JS 去获取了 CSS 自定义属性，拿到了这个方法的字符串，然后用 eval 去执行了，这样显得像是在 CSS 里面写了 JS。不过这样写也是一个思路，因为我们可以直接在属性后面面定义属性的样式，看起来也是合理的，但是如果用来渲染的 JS 量比较大，并不是一个好的方案。其实我们可以直接将用于绘制的 JS 代码写在 JS 文件中。</p>
<p>有了 CSS Paint API 我们用 CSS 做的事情有可以变得更多了，这是一个很令人兴奋的 API！</p>
<p><a name="0d98c747"></a></p>
<h4 id="-">其他</h4>
<p>看到这里，大漠老师再三强调的，没有 CSS 变量，只有 CSS 自定义属性，是非常合理的。我们其实是定义了一个 CSS 属性，并且给于它一个值，真正用到它的时候才是真正描述这个值去做什么事情的时候，用 var() 函数，它就变成了一个变量，用 paint() 函数，它就变成了 CSS Houdini.</p>
<p><a name="4ed6d2c8"></a></p>
<h3 id="web-layout">Web Layout</h3>
<p>陈慧晶老师全篇都在讲 Web 布局，大漠老师讲了一些 Web Layout 相关的一些属性和函数，并且指出了一些关于Web Layout 的一些误区和社区争论。</p>
<p><a name="6534943c"></a></p>
<h3 id="-">混合模式和滤镜</h3>
<p>简单讲解了一些滤镜的效果和一些属性，而且这些功能已经可以在线上跑了，我在去年项目中已经用过 CSS 滤镜了。</p>
<p><a name="685d1694"></a></p>
<h3 id="-_-">其他(^_^)</h3>
<p>最后讲了一些零散的点，有一些在项目里都可以用到，我们的项目也有用到过~而且都是一些小技巧，挺有意思的。</p>
<p><a name="3dd2685b"></a></p>
<h2 id="css-">CSS创意与视觉表现 - 张鑫旭</h2>
<p>张鑫旭老师，经常会在搜 CSS 问题的时候搜到他的博客，<a href="https://www.zhangxinxu.com/">张鑫旭-鑫空间-鑫生活</a>，他写文章有时候特别幽默，比如<a href="https://www.zhangxinxu.com/wordpress/2012/06/css3-transform-matrix-%E7%9F%A9%E9%98%B5/">《理解 CSS3 transform 中的 Matrix - 矩阵》</a>中。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262510375-4621d575-5e09-4caa-9ba3-9ddf32ef98bf.png#align=left&amp;display=inline&amp;height=78&amp;name=image.png&amp;originHeight=156&amp;originWidth=972&amp;size=28714&amp;status=done&amp;width=486" alt="image.png"></p>
<p>张老师这次分享了很多的非常实用的案例。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262494923-2bedd60f-584e-41e3-b46b-2da658f8335b.png#align=left&amp;display=inline&amp;height=399&amp;name=image.png&amp;originHeight=798&amp;originWidth=2024&amp;size=194744&amp;status=done&amp;width=1012" alt="image.png"></p>
<p>CSS 属性放在那里，具体怎么用？有些人真的可以使用一些属性加上一些思维做出很好的效果，这个可能是要看天分的。这次大会混入了一个产品经理，她提了一个问题，如何提升前端的这种创意思维，张鑫旭老师给出的答案是，可以招聘新的符合你要求的前端，这种创意不是每个人都有的，业界有很多优秀的作品已经出来了，大家不需要自己去研究，直接用现有的就好。</p>
<p>张鑫旭老师的 Slide 里面每个效果都有demo，感兴趣的话可以直接下载查看 <a href="https://www.yuque.com/office/yuque/0/2019/pdf/168578/1554174734641-ebc178ce-df8a-4bd7-ac4c-ad75c8b339f0.pdf">《CSS 创意与视觉表现.pdf》</a></p>
<p>其实有很多实现布局、特效的技巧，都是一些大师发明出来的，我们可以在项目中使用这些技巧，CSS 提供了很多属性，达到效果的方式不是唯一的，我们在日常工作中，其实可以多去思考这个效果如何实现，可能会发明出自己的奇淫技巧。</p>
<p><a name="0184d147"></a></p>
<h2 id="css-">CSS 生成艺术 - 袁川</h2>
<p>Slide: <a href="https://yuanchuan.name/talk/generative-art-with-css/">https://yuanchuan.name/talk/generative-art-with-css/</a><br />看了这个议题，很是震撼，袁川老师把浏览器当做了他的画板，CSS 当成他的画笔，生成了很多震撼的艺术作品，整个议题过程中，会场多次响起掌声。具体可以看 Slide，也可以看他的<a href="https://codepen.io/yuanchuan/">Codepen</a>。
<a name="929a88a0"></a></p>
<h4 id="-">几张图片</h4>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262749009-d670eccf-7433-4032-b41c-d1a5d29ad680.png#align=left&amp;display=inline&amp;height=529&amp;name=image.png&amp;originHeight=1058&amp;originWidth=1418&amp;size=2035334&amp;status=done&amp;width=709" alt="image.png"></p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262788409-b02ac613-e8da-4a12-9c84-bce9ab889040.png#align=left&amp;display=inline&amp;height=684&amp;name=image.png&amp;originHeight=1368&amp;originWidth=2874&amp;size=167452&amp;status=done&amp;width=1437" alt="image.png"><br /><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262827546-df0b8f53-5183-42d1-ba1f-706e3dbb095f.png#align=left&amp;display=inline&amp;height=900&amp;name=image.png&amp;originHeight=1800&amp;originWidth=2880&amp;size=9278806&amp;status=done&amp;width=1440" alt="image.png"></p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262888922-a94e29d8-74d5-47b0-94dd-bf704edadb8e.png#align=left&amp;display=inline&amp;height=900&amp;name=image.png&amp;originHeight=1800&amp;originWidth=2880&amp;size=5785767&amp;status=done&amp;width=1440" alt="image.png"><br /><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262856441-af237eaa-a430-4f16-8434-6c74465efa96.png#align=left&amp;display=inline&amp;height=900&amp;name=image.png&amp;originHeight=1800&amp;originWidth=2880&amp;size=4813141&amp;status=done&amp;width=1440" alt="image.png"></p>
<p>上面这两张图，都是用逗号做的。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554262936276-8a3d43b6-45f5-4e5e-a896-cdc0a02e2257.png#align=left&amp;display=inline&amp;height=787&amp;name=image.png&amp;originHeight=1574&amp;originWidth=2876&amp;size=429325&amp;status=done&amp;width=1438" alt="image.png"></p>
<p>CSS 有着天然生成树的特性。</p>
<p><a name="f2fa74a0"></a></p>
<h4 id="-">现场视频</h4>
<p>放一个现场的视频感受一下，在文末大会官网上可以看全部视频。<br /><a href="https://www.yuque.com/attachments/yuque/0/2019/mp4/107226/1554266268967-6fae981d-ef51-493e-b072-31c81a05cceb.mp4?_lake_card=%7B%22src%22%3A%22https%3A%2F%2Fwww.yuque.com%2Fattachments%2Fyuque%2F0%2F2019%2Fmp4%2F107226%2F1554266268967-6fae981d-ef51-493e-b072-31c81a05cceb.mp4%22%2C%22name%22%3A%22cssconf.mp4%22%2C%22size%22%3A9605094%2C%22type%22%3A%22video%2Fmp4%22%2C%22progress%22%3A%7B%22percent%22%3A0%7D%2C%22status%22%3A%22done%22%2C%22percent%22%3A0%2C%22card%22%3A%22file%22%7D">cssconf.mp4</a>
<a name="d41d8cd9"></a></p>
<p>## 
<a name="87d3c473"></a></p>
<h2 id="-css-">你不知道的五个 CSS 新特性 - 勾三股四</h2>
<p>勾股介绍了几个 CSS 的新特性，感觉实用性没有那么好，还有很多在草案阶段。勾股的 Slide 地址：<a href="https://jinjiang.github.io/slides/five-css-features/#70">https://jinjiang.github.io/slides/five-css-features/</a>
<a name="float"></a></p>
<h3 id="float">float</h3>
<p><a name="aeb85d17"></a></p>
<h4 id="float-css-shapes">float &amp; CSS Shapes</h4>
<p>我们使用 float 可以进行文字环绕图片、多列布局，但是现在我们已经很少用 float 了。大家已经开始使用更新的布局方案。但是如果想实现图文混排 float 还是少不了的。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554281282352-d9e40a59-1c13-40eb-8db5-ae792459a71d.png#align=left&amp;display=inline&amp;height=657&amp;name=image.png&amp;originHeight=1314&amp;originWidth=2368&amp;size=3384872&amp;status=done&amp;width=1184" alt="image.png"><br /></p>
<p>如果想让文字根据图片内容进行排版，单单只用 float 是不够的。需要配合 CSS Shapes 实现。</p>
<p><a name="1b7e5572"></a></p>
<h4 id="-">其他效果</h4>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554281418689-1d747176-b5ca-4237-8b6b-1222031b48a7.png#align=left&amp;display=inline&amp;height=766&amp;name=image.png&amp;originHeight=1532&amp;originWidth=2624&amp;size=1275125&amp;status=done&amp;width=1312" alt="image.png"><br /></p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554281429084-7247e7cc-a6a4-44e4-9362-5b58885204db.png#align=left&amp;display=inline&amp;height=758&amp;name=image.png&amp;originHeight=1516&amp;originWidth=2608&amp;size=312320&amp;status=done&amp;width=1304" alt="image.png"></p>
<p><a name="page"></a></p>
<h3 id="page">page</h3>
<p>网页里面有个打印的功能，可以针对打印状态下的页面进行样式设置，里面拓展了一些和打印相关的属性。<br /><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554281701131-48582d79-3754-428b-9eb1-5e11c6f5fbb2.png#align=left&amp;display=inline&amp;height=779&amp;name=image.png&amp;originHeight=1558&amp;originWidth=2328&amp;size=250033&amp;status=done&amp;width=1164" alt="image.png"></p>
<p>更多内容可以参考 <a href="https://www.pagedmedia.org/">https://www.pagedmedia.org/</a></p>
<p><a name="scroll"></a></p>
<h3 id="scroll">scroll</h3>
<p>勾股也讲了一些滚动条相关的内容。</p>
<p><a name="font"></a></p>
<h3 id="font">font</h3>
<p>Adobe, Apple, Google, Microsoft 4大巨头企业联合宣布了全新的字体规范Variable Font，字体在设计过程中可以提供出一些参数，CSS 中可以对参数进行配置。</p>
<p><a name="viewport"></a></p>
<h3 id="viewport">viewport</h3>
<p>以后可以使用 CSS 来设置页面的 viewport 啦！</p>
<p><a name="275d3987"></a></p>
<h2 id="css-10-brian-birtles">CSS动画你应该知道的10件事 - Brian Birtles</h2>
<p>这个议题提到了关于 CSS 动画的一些知识。<br />中文版Slide：<a href="https://birtles.github.io/cssconf2019/index.zh.html">https://birtles.github.io/cssconf2019/index.zh.html</a><br />英文版Slide：<a href="https://birtles.github.io/cssconf2019/#/title">https://birtles.github.io/cssconf2019/#/title</a><br /><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554284091955-606fe4c0-3689-4d54-a9e8-1fe4203e2261.png#align=left&amp;display=inline&amp;height=751&amp;name=image.png&amp;originHeight=1502&amp;originWidth=2388&amp;size=346132&amp;status=done&amp;width=1194" alt="image.png"><br /></p>
<p>我都不用去总结啦！对某个点感兴趣的话，可以看一下他的 Slide，每一个点，都对应了一些解释和实践。</p>
<p><a name="5c8929f9"></a></p>
<h2 id="css-time-">CSS TIME - 陈在真</h2>
<p>陈在真老师的 CSS TIME 这个话题，介绍了一些他做了很多 CSS 动画后总结的一些方法论。后面我和他有过一些交流，我在想，是否能有什么工具可以按照他的这种方法论产生的规则设计动画，直接产生线上可用的 CSS 动画效果，其实这也是从工程化上要考虑的事情。</p>
<p><a name="763b6632"></a></p>
<h3 id="-">简单案例</h3>
<p>使用了几个简单的案例，总结出在做 CSS 动画时候，如何让多个动画联动起来，如何实现循环时间统一。</p>
<p>讲了一个复杂案例的实现，他做的动画很多都是他自己来设计的，所以在实现动画的设计理论上也有一些介绍。</p>
<p><a name="870cc1b5"></a></p>
<h3 id="-">各种设备下的时间</h3>
<p>在不同的设备下，比如 pad、手机、穿戴设备，用户对时间的体感不同，一个动画使用的时间可能是不同的。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/107226/1554285071963-7f450c7b-23aa-481a-81f9-0dc1ca09ca24.png#align=left&amp;display=inline&amp;height=632&amp;name=image.png&amp;originHeight=1262&amp;originWidth=1070&amp;size=236707&amp;status=done&amp;width=536" alt="image.png"></p>
<p><a name="2658b14d"></a></p>
<h2 id="-">后语</h2>
<p>这里只是按照我的理解和记忆总结了一些在大会上的所见所闻，列出了各位讲师分享的一些点，如果大家对哪些点感兴趣可以深入到 Slide 中学习。大会视频后续也会放出，可以进入大会官网<a href="https://css.w3ctech.com/">中国第五届 CSS 开发者大会</a>查看议题、Slide 以及视频。</p>
]]></description><link>https://fanmingfei.com/posts/Fifth_CSSConf.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/Fifth_CSSConf.html</guid></item><item><title><![CDATA[大话 JavaScript 动画]]></title><description><![CDATA[<h2 id="-">背景</h2>
<p>138.2亿年前，世界上没有时间和空间，或许世界都不存在，在一个似有似无的点上，汇集了所有的物质，它孕育着无限的能量与可能性。</p>
<h2 id="-">宇宙大爆炸</h2>
<p>巨大的内力已无法被抑制，瞬间爆发，它爆炸了！世界上有了时间和空间，随着岁月的变迁，时光的流逝，无数的星系、恒星、卫星、彗星形成。我们生活的地球，只是茫茫宇宙中的一个小小的天体，或许在遥远的宇宙的另一边，会有平行世界的存在，或许在那里，我们可能是医生、老师、公务员。科学家说我们的宇宙正在加速度的膨胀，暗能量在无限吞噬着暗物质，未来的世界将会变得虚无缥缈。</p>
<p><img src="https://p4.ssl.qhimg.com/t01d8909596dde78d20.jpg" alt=""></p>
<h2 id="-">人类起源</h2>
<p>宇宙的形成，带来了无限可能性，人类释放着欲望和克制，对宇宙的渴望产生于公元前五世纪，古巴比伦人通过观察天体的位置以及外观变化，来预测人世间的各种事物。在遥远的古罗马，人们也舞弄着灵魂，把不羁的想象赋予肉体。Anim，来源于拉丁语，代表着灵魂与生命，代表着所有与生俱来。似乎世间万物都存在联系，宇宙、自然都存在灵魂。</p>
<p><img src="https://p2.ssl.qhimg.com/t018418fe1df058643c.jpg" alt=""></p>
<h2 id="-">动画的形成</h2>
<p>两万五前年钱的石器时代，石洞中的野兽奔跑分析图，这是人类开始试图捕捉动作最早证据。文艺复兴时期的达芬奇画作上，用两只手臂两条腿来标识上下摆动的动作，在一张画作上做出不同时间的两个动作。
直到1906年，世界上第一部动画片《<a href="https://www.bilibili.com/video/av6265790/">滑稽脸的幽默相</a>》问世。</p>
<p><img src="https://p5.ssl.qhimg.com/t01469153556cf26a97.jpg" alt=""></p>
<p>所以动画是否就是将多个画面连起来播放呢？</p>
<p>时间是连续的吗？是可以无线分割的吗？我也不太清楚，你看到的流星、人们的动作是连续的吗？或许是吧，毕竟现实生活中还没有像瞬间移动这种事情发生吧。</p>
<p>神经可能不是连续的，生物课学过，神经的传递是一个电信号传递过程，并且是颗粒的(神经信号)，那么我们看到的东西在我们脑海里的成像一定不是连续的。</p>
<p><img src="https://p2.ssl.qhimg.com/t0176ac32fc9404dbfd.gif" alt=""></p>
<p>那么我们为什么能看到连续的动作呢？</p>
<p>视觉暂留（Persistence of vision），让我们看到了连续的画面，视神经反应速度大约为1/16秒，每个人不太一样，有些人高一点，一些人低一点。上一次视神经传递的图像将会在大脑中存留，直到下一次神经信号到达。维基百科上说，日常用的日光灯每秒钟大约会熄灭100次，但是你并没有感觉。</p>
<p>一般电影的在帧率在24FPS以上，一般30FPS以上大脑会认为是连贯的，我们玩的游戏一般在30FPS，高帧率是60FPS。</p>
<p>小时候一定看过翻页动画吧，可以看一看<a href="https://www.bilibili.com/video/av3049">翻页动画-地球进化史</a></p>
<h2 id="-">前端动画实现</h2>
<p>Atwood 定律：Any application that can be written in JavaScript will eventually be written in JavaScript.</p>
<p>前端做动画不是什么新鲜事了，从jQuery时代，到当下，无不是前端动画横行的时代。</p>
<p>我们知道多张不同的图像连在一起就变成了动态的图像。</p>
<p>在前端的世界里，浏览器在视觉暂留时间内，连续不断的<strong>逐帧</strong>输出图像。每一帧输出一张图像。</p>
<p>提及动画一定会讨论到帧率(FPS, Frame Per Second)，代表每秒输出帧数，也就是浏览器每秒展示出多少张静态的图像。</p>
<h3 id="dom-css3">DOM动画中的 CSS3</h3>
<p>CSS3 动画是当今盛行的 Web 端制作动画的方式之一，对于移动设备来说覆盖率已经非常广泛，在日常开发中可以使用。CSS3 动画只能通过对 CSS 样式的改变控制 DOM 进行动画</p>
<ul>
<li><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Animations">CSS3 Animation MDN</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/translate">CSS3 Translate MDN</a></p>
</li>
</ul>
<h3 id="dom-webanimation">DOM动画中的 WebAnimation</h3>
<p>WebAnimation 还在草案阶段，在Chrome可以尝试使用一下。移动设备还是相当惨烈，iOS 并没有开始支持。</p>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Animation">Element Animation MDN</a></p>
<p><img src="https://p0.ssl.qhimg.com/t01a0eb75de86b52934.jpg" alt="Can I Use"></p>
<p>CSS3 和 WebAnimation 都只能作用于DOM，那么，如果我们想让 Canvas 上的对象产生动画，那我们该怎么办呢？</p>
<h3 id="javascript">JavaScript</h3>
<p>既然我们知道动画的原理，其实就是让用户看到连续的图片，并且每一张图片是有变化的。</p>
<p>对于事物来讲，我们可以通过改变某些数值来修改他的属性，从来改变他的外在展示。比如正方形的边长，颜色的RGB值，台风的位置（世界坐标），在每一帧去改变这些数值，根据这些数值将对象绘依次制到屏幕上，将会产生动画。</p>
<p>通过上面的描述，我们知道，实现一个动画，其实是数值随时间变化，以帧为时间单位。</p>
<p>在很久很久以前，JavaScript 使用 <code>setInterval</code> 进行定时调用函数。所以可以使用setInterval来进行数值的改变。</p>
<p>为了更好的让各位前端小哥哥小姐姐们做动画，出现了<code>requestAnimationFrame</code>，<code>requestAnimationFrame</code> 接收一个函数，这个函数将在下一帧渲染之前执行，也就是说，不需要太多次的计算，只要在下一帧渲染之前，我们将需要修改的数值修改掉即可。<code>requestAnimationFrame</code> 的帧率和硬件以及浏览器有关，一般是60FPS(16.66666666ms/帧)。</p>
<p>我们利用 Dom 进行动画的演示~</p>
<h4 id="-">元素移动</h4>
<p>创建一个方块</p>
<pre><code class="lang-html">&lt;div class=“box”&gt;&lt;/div&gt;
</code></pre>
<p>设置宽高和背景颜色</p>
<pre><code class="lang-css">.box {
    width: 100px;
    height: 100px;
    background: red;
}
</code></pre>
<pre><code class="lang-javascript">const box = document.querySelector(&#39;.box&#39;) // 获取方块元素
let value = 0 // 设置初始值
// 创建每一帧渲染之前要执行的方法
const add = () =&gt; {
    requestAnimationFrame(add) // 下一帧渲染之前继续执行 add 方法
    value += 5 // 每帧加数值增加5
    box.style.transform = `translateX(${value}px)` // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移
}
requestAnimationFrame(add) // 下一帧渲染之前执行 add 方法
</code></pre>
<p>这样，方块每帧向右移动 5 像素，每秒移动<code>60*5=300</code>像素，不是每秒跳动一下，而是一秒在300像素内均匀移动哦。</p>
<h4 id="-">补间动画</h4>
<p>上一个demo实现了小方块从左到右的移动，但是貌似他会永无止境的移动下去，直到数值溢出，小时候学过flash的朋友都知道补间动画，其实就是让小方块0px到300px平滑移动。其实就是固定的时间点，有固定的位置。</p>
<p>所以我们只需要根据运动的已过时间的百分比去计算数值。</p>
<p>保持之前的 HTML 和 CSS 不变</p>
<pre><code class="lang-javascript">/**
 *  执行补间动画方法
 *
 * @param      {Number}    start     开始数值
 * @param      {Number}    end       结束数值
 * @param      {Number}    time      补间时间
 * @param      {Function}  callback  每帧的回调函数
 */
function animate(start, end, time, callback) {
    let startTime = performance.now() // 设置开始的时间戳
    let differ = end - start // 拿到数值差值
    // 创建每帧之前要执行的函数
    function loop() {
        raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数
        const passTime = performance.now() - startTime // 获取当前时间和开始时间差
        let per = passTime / time // 计算当前已过百分比
        if (per &gt;= 1) { // 判读如果已经执行
              per = 1 // 设置为最后的状态
              cancelAnimationFrame(raf) // 停掉动画
        }
        const pass = differ * per // 通过已过时间百分比*开始结束数值差得出当前的数值
        callback(pass) // 调用回调函数，把数值传递进去
    }
    let raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数
}

</code></pre>
<p>我们调用一下补间动画，让数值经过1秒匀速从0变成400。</p>
<pre><code class="lang-javascript">let box = document.querySelector()
animate(0, 400, 1000, value =&gt; {
    box.style.transform = `translateX(${value}px)` // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移
})
</code></pre>
<p>一个简单的匀速补间动画就这么被我们做好了。</p>
<h4 id="-">非匀速动画</h4>
<p>那万一，这个动画不是非匀速的，比如抖一抖啊，弹一弹，那该怎么办呢？</p>
<p>当然也是一样，根据已过时间的百分比去计算数值</p>
<p>时间是匀速的，但是数值不是，如果数值变化是有规律的，那么我们就可以使用时间来表示数值，创建一个接收时间比例（当前时间百分比），返回当前位置比例（当前位置百分比）的方法。</p>
<p>我们称这个方法叫做缓动方法。</p>
<p>如果速度从慢到快，我们可以把时间和数值的图像模拟成以下的样子。</p>
<p><img src="https://p5.ssl.qhimg.com/t01e205e795742cf4f7.jpg" alt=""></p>
<p>公式为 <code>rate = time ^ 2</code></p>
<p>对应的函数应该是</p>
<pre><code class="lang-javascript">function  easeIn(time) { // 接收一个当前的时间占总时间的百分比比
    return time ** 2
}
</code></pre>
<p><img src="https://p5.ssl.qhimg.com/t01bf617d9b51a9b95f.jpg" alt=""></p>
<p>这个实现加速后抖动结束的效果，在Time小于0.6时是一个公式，time大于0.6是另外一个公式。</p>
<p>Time &lt; 0.6 时： Rate = Time / 0.6 ^ 2</p>
<p>Time &gt; 0.6 时： Rate = Math.sin((Time-0.6) <em> ((3 </em> Math.PI) / 0.4)) * 0.2 + 1</p>
<p>最终实现的函数是</p>
<pre><code class="lang-javascript">function shake(time) {
    if (time &lt; 0.6) {
        return (time / 0.6) ** 2
    } else {
        return Math.sin((time-0.6) * ((3 * Math.PI) / 0.4)) * 0.2 + 1
    }
}
</code></pre>
<p>我们改造一下之前的 <code>animate</code> 函数，接收一个 easing 方法。</p>
<pre><code class="lang-javascript">/**
 *  执行补间动画方法
 *
 * @param      {Number}    start     开始数值
 * @param      {Number}    end       结束数值
 * @param      {Number}    time      补间时间
 * @param      {Function}  callback  每帧回调
 * @param      {Function}  easing    缓动方法，默认匀速
 */
function animate(start, end, time, callback, easing = t =&gt; t) {
    let startTime = performance.now() // 设置开始的时间戳
    let differ = end - start // 拿到数值差值
    // 创建每帧之前要执行的函数
    function loop() {
        raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数
        const passTime = performance.now() - startTime // 获取当前时间和开始时间差
        let per = passTime / time // 计算当前已过百分比
        if (per &gt;= 1) { // 判读如果已经执行
            per = 1 // 设置为最后的状态
            cancelAnimationFrame(raf) // 停掉动画
        }
        const pass = differ * easing(per) // 通过已过时间百分比*开始结束数值差得出当前的数值
        callback(pass)
    }
    let raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数
}

</code></pre>
<p>测试一下，将我们刚刚创建的 easing 方法传进来</p>
<p>加速运动</p>
<pre><code class="lang-javascript">let box = document.querySelector(&#39;.box&#39;)
animate(0, 500, 400, value =&gt; {
    box.style.transform = `translateX(${value}px)` // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移
}, easeIn)
</code></pre>
<p>加速后抖一抖</p>
<pre><code class="lang-javascript">let box = document.querySelector(&#39;.box&#39;)
animate(0, 500, 400, value =&gt; {
    box.style.transform = `translateX(${value}px)` // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移
}, shake)
</code></pre>
<h2 id="-">总结</h2>
<p>这些只是 JavaScript 动画基础中的基础，理解动画的原理后，再做动画就更得心应手了。</p>
<p>市面上有很多JS动画库，大家可以开箱即用。有一些是针对DOM操作的，也有一些是针对 JavaScript 对象。实现原理你都已经懂了。</p>
<p>上述代码已发布到Github: <a href="https://github.com/fanmingfei/animation-base">https://github.com/fanmingfei/animation-base</a></p>
<p>我的另外两篇关于动画的文章：</p>
<ul>
<li><a href="https://fanmingfei.com/posts/Why_The_Canvas_Is_Shake.html">为何 Canvas 内元素动画总是在颤抖？</a></li>
<li><a href="https://fanmingfei.com/posts/RequestAnimationFrame_Lock_Frame.html">前端动画/游戏开发 requestAnimationFrame 之 锁帧</a></li>
</ul>
]]></description><link>https://fanmingfei.com/posts/Animation_Base.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/Animation_Base.html</guid></item><item><title><![CDATA[大学没学过数学也要理解 CSS3 transform 中的 matrix]]></title><description><![CDATA[<h2 id="-">前言</h2>
<p>CSS3 中使用 transform 可以对元素进行变换。其中包含：位移、旋转、偏移、缩放。
transform 可以使用 translate/rotate/skew/scale 的方式来控制元素变换，也可以使用 matrix 的方式来控制元素变换。</p>
<p>比如：</p>
<pre><code class="lang-html">&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;
</code></pre>
<p>通过transform属性进行变换。</p>
<p>首先演示使用 translate/rotate/skew/scale 的方式：</p>
<pre><code class="lang-css">.box {
    width: 100px;
    height: 100px;
    background: #00C487;
    transform: translate(10px, 20px) rotate(30deg) scale(1.5, 2);
}
</code></pre>
<p>也可以使用 matrix 的方式：</p>
<pre><code class="lang-css">.box {
    width: 100px;
    height: 100px;
    background: #00C487;
    transform: matrix(0.75, 0.8, -0.8, 1.2, 10, 20);
}
</code></pre>
<p><a href="//codepen.io/fanmingfei/pen/pVYdpO">查看demo</a></p>
<p>Matrix 的中文是矩阵，是一个数学术语，在计算机科学中，会用矩阵来对象量进行变换，在 CSS3 的 transform 属性中，可以使用矩阵对图像进行变换。</p>
<h2 id="-">矩阵长什么样子？</h2>
<p>矩阵可以分为一个形容词+一个名字，矩是形容词，阵是名词。</p>
<p>如果你喜欢看战争片，不管是古代战争还是现代战争，都需要有阵势，打仗没阵型，等于耍流氓；或者是开一局农药，可能也要考虑各个英雄的站位，各种球类运动、各种棋类都需要有阵型。</p>
<p>阵型中的每一个个体对整体的都会产生影响。比如打王者荣耀射手时候，射手应该猥琐在一个位置输出，站错位置，输掉整个游戏。</p>
<p><img src="//gw.alicdn.com/mt/TB1AfLokiCYBuNkHFCcXXcHtVXa-1200-800.jpeg" alt=""></p>
<p>那，其实矩阵就是一些列的数字按照矩形排列。</p>
<p>在数学中，矩阵用方括号包裹起来。</p>
<p><img src="//gw.alicdn.com/mt/TB1dvt3sL5TBuNjSspmXXaDRVXa-1296-700.png" alt=""></p>
<p>上图就是一个矩阵。</p>
<h2 id="css3-matrix-">CSS3 里的 matrix 如何和矩阵对应呢？</h2>
<p>为什么要用矩阵来表示转换呢？因为在计算机科学中，矩阵可以对向量进行转换。矩阵中的每一个数字，对向量的转换都会产生影响。</p>
<p>CSS3 里面可以用矩阵表示 2D 和 3D 转换，这里只讲 2D。</p>
<pre><code class="lang-css">selector {
    transform: matrix(a, b, c, d, e, f);
}
</code></pre>
<p><img src="//gw.alicdn.com/mt/TB14SYUsTJYBeNjy1zeXXahzVXa-997-700.png" alt=""></p>
<p>2D 的转换是由一个 3*3 的矩阵表示的，前两行代表转换的值，分别是 a b c d e f，要注意是竖着排的，第一行代表 x 轴发生的变化，第二行代表 y 轴发生的变化，第三行代表 z 轴发生的变化，因为这里是 2D 不涉及 z 轴，所以这里是 0 0 1。</p>
<h2 id="-">假设一个问题</h2>
<p>创建一个宽高为 200px 的div，div 里面有一个红色的点，位置是<code>{x:181px y:50px}</code>。
<img src="//gw.alicdn.com/mt/TB1BnpysHuWBuNjSszgXXb8jVXa-409-409.png" alt=""></p>
<p>倘若将这个div 向右平移 10px，x 轴向下平移 20px，旋转37°，x轴缩放 1.5 倍，y 轴缩放 2 倍：</p>
<p>transform: translate(10px, 20px) rotate(37deg) scale(1.5, 2);</p>
<p><img src="//gw.alicdn.com/mt/TB1Ew6KsQKWBuNjy1zjXXcOypXa-983-1024.png" alt=""></p>
<p>那么红色点的变化后的位置在哪里呢？</p>
<p>既然我们知道矩阵可以对向量进行转换那么我们只要把上面的信息转换成矩阵信息，通过矩阵信息可以将我们的原始坐标转换到新的坐标。</p>
<h2 id="-scale-x-y-">缩放 scale(x, y)</h2>
<p>缩放对应的是矩阵中的 a 和 d，x 轴的缩放比例对应 a，y 轴的缩放比例对应 d。</p>
<p>transform: scale(x,y);</p>
<p>a=x
d=y</p>
<p>所以 scale(1.5, 2) 对应的矩阵是：</p>
<p>transform: matrix(1.5, <del>0, 0,</del> 2, <del>0, 0</del>);</p>
<p><img src="//gw.alicdn.com/mt/TB1t764sH1YBuNjSszhXXcUsFXa-1249-700.png" alt=""></p>
<p>如果一个没有元素没有被缩放，默认a=1 d=1。</p>
<h2 id="-translate-10-20-">平移 translate(10, 20)</h2>
<p>平移对应的是矩阵中的 e 和 f，平移的 x 和 y 分别对应 e 和 f。</p>
<p>transform: translate(10, 20)</p>
<p>e=10</p>
<p>f=20</p>
<p>对应：
transform: matrix(<del>a, b, c, d</del>10, 20);</p>
<p>结合缩放：
transform: matrix(<del>1.5 0, 0, 2, </del>10, 20);</p>
<p>平移只会影响 e 和 f 值。</p>
<h2 id="-rotate-deg-">旋转 rotate(θdeg)</h2>
<p>旋转影响的是a/b/c/d四个值，分别是什么呢？</p>
<p>transform: rotate(θdeg)</p>
<p>a=cosθ</p>
<p>b=sinθ</p>
<p>c=-sinθ</p>
<p>d=cosθ</p>
<p>这个是高中学的哦~</p>
<p>如果要计算 30° 的sin值：</p>
<p>首先我们要将 30° 转换为弧度，传递给三角函数计算。用 JS 计算就是下面的样子了。</p>
<pre><code class="lang-javascript">
// 弧度和角度的转换公式：弧度=π/180×角度 

const radian = Math.PI / 180 * 30 // 算出弧度

const sin = Math.sin(radian) // 计算 sinθ
const cos = Math.cos(radian) // 计算 cosθ

console.log(sin, cos) // 输出 ≈ 0.5, 0.866

</code></pre>
<p>这样我们算出了 sin 和 cos，分别是 0.5 和 0.866</p>
<p>如果我们不考虑缩放和偏移，只旋转30°，矩阵应该表示为</p>
<p>transform: rotate(30deg)</p>
<p>a=0.866</p>
<p>b=0.5</p>
<p>c=-0.5</p>
<p>d=0.866</p>
<p>transform: matrix(0.866, 0.5, -0.5, 0.866, <del>0, 0</de>);</p>
<p><img src="//gw.alicdn.com/mt/TB1xon.sH9YBuNjy0FgXXcxcXXa-1320-700.png" alt=""></p>
<h2 id="-skew-20deg-30deg-">偏移 skew(20deg, 30deg)</h2>
<p>上面的题目中没有出现出现偏移值，偏移值也是由两个参数组成，x 轴和 y 轴，分别对应矩阵中的 c 和 b。是 x 对应 c，y 对应 b， 这个对应并不是相等，需要对 skew 的 x 值 和 y 值进行 tan 运算。</p>
<p>transform: skew(20deg, 30deg);</p>
<p>b=tan30°</p>
<p>c=tan20°</p>
<p>注意 x 对应的是 c，y 对应的是 b。</p>
<p>transform: matrix(<del>a,</del> tan(30deg), tan(20deg), <del>d, e, f</del>)</p>
<p>使用 JS 来算出 tan20 和 tan30</p>
<pre><code class="lang-javascript">// 先创建一个方法，直接返回角度的tan值
function tan (deg) {
    const radian = Math.PI / 180 * deg
    return Math.tan(radian)
}

const b = tan(30)
const c = tan(20)
console.log(b, c) // 输出 ≈ 0.577, 0.364
</code></pre>
<p>b=0.577
c=0.364</p>
<p>transform: matrix(<del>1,</del> 0.577, 0.364, <del> 1, 0, 0</del>)</p>
<h2 id="-">旋转+缩放+偏移+位移怎么办？</h2>
<p>如果我们既要旋转又要缩放又要偏移，我们需要将旋转和缩放和偏移和位移多个矩阵相乘，<strong>要按照transform里面rotate/scale/skew/translate所写的顺序相乘</strong>。</p>
<p>这里我们先考虑旋转和缩放，需要将旋转的矩阵和缩放的矩阵相乘</p>
<p>实在是用语言解释不清楚如何去乘，用一张图解释吧：</p>
<p>这里我用小写字母代表第一个矩阵中的值，大写字母代表第二个矩阵里的值</p>
<p><img src="//gw.alicdn.com/mt/TB1w50ktL5TBuNjSspcXXbnGFXa-6355-700.png" alt=""></p>
<p>将我们的已经得到的矩阵带入到公式</p>
<p><img src="//gw.alicdn.com/mt/TB1rIL4sFuWBuNjSspnXXX1NVXa-8124-2014.png" alt=""></p>
<p>得出：</p>
<p>transform: rotate(30) scale(1.5 2);</p>
<p>转换为 matrix 表示为：</p>
<p>transform: matrix(1.299, 0.75,  -1, 1.732, <del>0, 0</de>);</p>
<h2 id="-">找到这次转换的矩阵</h2>
<p>div 的 transform 值如下</p>
<p>transform: translate(10px, 20px) rotate(37deg) scale(1.5, 2);</p>
<h3 id="translate-10px-20px-">translate(10px, 20px)</h3>
<p>x 平移 10px，y 平移 20px，所以 e=10，f=20。</p>
<p><img src="//gw.alicdn.com/mt/TB1VIL5sKuSBuNjSsplXXbe8pXa-1249-700.png" alt=""></p>
<h3 id="rotate-37deg-">rotate(37deg)</h3>
<p>sin37° ≈ 0.6</p>
<p>cos37° ≈ 0.8</p>
<p>根据 a 对应 cos b，对应 sin，c 对应 -sin，d 对应 cos 的值</p>
<p>得到：</p>
<p>a=0.8，b=0.6，c=-0.6，d=0.8</p>
<p><img src="//gw.alicdn.com/mt/TB1d27LsUR1BeNjy0FmXXb0wVXa-1249-700.png" alt=""></p>
<h3 id="scale-1-5-2-">scale(1.5, 2)</h3>
<p>x 轴缩放 1.5，y 轴缩放 2，所以 a=1.5，d=2</p>
<p><img src="//gw.alicdn.com/mt/TB1vh9UkyCYBuNkSnaVXXcMsVXa-1249-700.png" alt=""></p>
<h3 id="-">结合</h3>
<p>transform: translate(10px, 20px) rotate(37deg) scale(1.5, 2);</p>
<p>我们使用  位移矩阵 <em> 旋转矩阵 </em> 缩放矩阵（根据transform中的变换类型书写的顺序）</p>
<p>可以使用<a href="//zh.numberempire.com/matrixbinarycalculator.php">矩阵计算器</a>进行计算</p>
<p>从左往右依次计算</p>
<p><img src="//gw.alicdn.com/mt/TB1imHdsY1YBuNjSszeXXablFXa-6549-727.png" alt=""></p>
<p>所以最终得到矩阵</p>
<p><img src="//gw.alicdn.com/mt/TB1emrqkDdYBeNkSmLyXXXfnVXa-997-700.png" alt=""></p>
<p>matrix(1.2, 0.9, -1.2 1.6, 10, 20)</p>
<p><a href="//codepen.io/fanmingfei/pen/derzxK">验证一下</a></p>
<p>transform: matrix(1.2, 0.9, -1.2 1.6, 10, 20)</p>
<p>和</p>
<p>transform: translate(10px, 20px) rotate(37deg) scale(1.5, 2);</p>
<p>效果是一样的</p>
<h2 id="-">如何对一个坐标进行矩阵变换</h2>
<p>我们已经知道了这个矩阵，如何通过矩阵对一个坐标进行变化，找到这个坐标变化后的位置呢？</p>
<p>我们用之前得出的变换矩阵去乘以这一个坐标组成的3*1（三排一列）矩阵。</p>
<p><img src="//gw.alicdn.com/mt/TB1WBO4sHSYBuNjSspfXXcZCpXa-1861-700.png" alt=""></p>
<p>上面已经介绍过如何进行矩阵乘法了，这里在介绍一遍</p>
<p><img src="//gw.alicdn.com/mt/TB1OSJVsHSYBuNjSspiXXXNzpXa-1861-700.png" alt=""></p>
<p>上图中左右两个矩阵颜色相同的位置相乘后相加，每一行都进行这样的计算：</p>
<p><img src="//gw.alicdn.com/mt/TB1FfnosGmWBuNjy1XaXXXCbXXa-3812-700.png" alt=""></p>
<p>得到一个3*1的矩阵，第一行是转换后的 x 值，第二行是转换后的 y 值，第三行是转换后的 z 值（2d不考虑z值）。</p>
<p>前面讲到，矩阵的第一行影响 x，第二行影响 y，也体现在这个地方。</p>
<p>假设我们的坐标是(50, 80)，这里还没有针对我们提出的问题上面的点进行计算。</p>
<p>我们把坐标写成矩阵的形式，设置 z 轴是1：</p>
<p><img src="//gw.alicdn.com/mt/TB1BdIVsN9YBuNjy0FfXXXIsVXa-519-700.png" alt=""></p>
<p>然后进行乘法计算：</p>
<p><img src="//gw.alicdn.com/mt/TB11DLDsMmTBuNjy1XbXXaMrVXa-4854-727.png" alt=""></p>
<p>通过我们计算出来的矩阵变换得到新的位置(46, 172)</p>
<h2 id="-">继续刚刚问题</h2>
<p>坐标是需要基于一个坐标系存在的，我们需要找到正确的坐标系才能算出准确的坐标。
在 CSS transform 中，有个属性是 transform-origin，来设置变换所基于的点，默认是<code>transform-origin: 50% 50%</code>，基于中间元素的中心点。我们需要以这个点建立坐标系。</p>
<p>在网页中，坐标系是 x 轴向右，y 轴向下。</p>
<p>转换前：</p>
<p><img src="//gw.alicdn.com/mt/TB18DKgsWmWBuNjy1XaXXXCbXXa-706-796.png" alt=""></p>
<p>转换后：
<img src="//gw.alicdn.com/mt/TB1Zcxds29TBuNjy1zbXXXpepXa-982-1023.png" alt=""></p>
<p>根据题目我们知道，这个点相对于绿色div左上角的坐标是(181, 50)
绿色div的宽高为200
基于绿色div中心点建立的坐标系，这个点的坐标是(81, -50)</p>
<p>将坐标代入公式进行计算：</p>
<p><img src="//gw.alicdn.com/mt/TB10o_gksuYBuNkSmRyXXcA3pXa-4854-727.png" alt=""></p>
<p>得到坐标约为(167, 13)</p>
<p>再将这个坐标转换成页面坐标系(267,113)</p>
<p>最终我们得到了这个点在经过转换后的坐标</p>
<h2 id="-">总结</h2>
<p>矩阵在计算机图形学中运用非常多，就像我们经常用的PhotoShop，虽然是设计软件，但它的图形也是依赖各种数学能力进行计算后展现的。我们玩的游戏、看的3D电影，其实都和数学息息相关，学好这些知识，才能真正的成为发明者，即便不成为发明者，在应用层理解这些，让我们能做的事情更多。</p>
<p>本文错误的地方，欢迎斧正。</p>
]]></description><link>https://fanmingfei.com/posts/CSS3_Transform_Matrix_Intro.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/CSS3_Transform_Matrix_Intro.html</guid></item><item><title><![CDATA[HomeAI 一大波职位]]></title><description><![CDATA[<p>HOME AI 长远目标构建具有现实意义的虚拟世界。2年目标通过软硬装的智能搭配设计，帮助用户省心省钱的拥有一个满意的家。</p>
<p>阿里巴巴智能场景事业部（Flab）是一支致力打造未来体验、探索未来科技的前沿团队，Flab技术团队综合利用人工智能、大数据、IoT等技术以及阿里巴巴独有的电商资源为线下新业务场景量身打造更加便利、快捷和科技感的用户产品和服务。这里我们广泛尝试、深入探索，不断追求技术与业务的最佳结合点，尝试从不同的维度定义未来生活。Flab技术团队也是一支快乐与Geek的团队，我们正在寻找同样快乐与Geek的小伙们加入我们！</p>
<p>如果你有意向，可以将简历发至：<a href="mailto:mingfei.fmf@alibaba-inc.com">mingfei.fmf@alibaba-inc.com</a>（坐标：杭州、北京均有）</p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51086">3d引擎技术专家</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51081">资深客户端开发工程师</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51522">3D视觉算法</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51082">前端开发专家-图形学方向</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51084">资深前端开发专员/专家</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51085">资深java开发工程师/专家</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51075">资深算法专员/专家-算法几何分析方向</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51078">资深算法专员/专家-图像处理与视觉方向</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51079">资深算法专员/专家-大数据处理方向</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51080">资深算法专员/专家-机器学习方向</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51040">产品经理</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51041">产品运营</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51047">交互设计专家</a></p>
<p><a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=51048">视觉设计专家</a></p>
<p><img src="http://p5.qhimg.com/t01927b6f9ea73698d8.jpg" alt=""></p>
]]></description><link>https://fanmingfei.com/posts/HomeAI.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/HomeAI.html</guid></item><item><title><![CDATA[CSS 字号和样式]]></title><description><![CDATA[<h3 id="font-size">font-size</h3>
<p>这个属性可以设置字体大小。</p>
<p>我们已经了解了 <strong><a href="/css-size-units.html">CSS 尺寸单位</a></strong>，可以用于设置字体的大小。</p>
<pre><code class="lang-css">p{ font-size: 16px;}
</code></pre>
<p>字体设置为 <code>16px</code> 并不代表每个字都是 <code>16px</code>，具体的大小取决于字体的字体。</p>
<h3 id="font-style">font-style</h3>
<p>这个属性可以把你的字体变成 _斜体_：</p>
<pre><code class="lang-css">h2{ font-style: italic;}
</code></pre>
<p>默认值是： <code>font-style: normal;</code>.</p>
<p>另外一个可选的值是 <code>oblique</code>，但是基本不用。</p>
<h3 id="font-weight">font-weight</h3>
<p>这个属性可以设置字体粗细：</p>
<pre><code class="lang-css">h2{ font-weight: bold;}
</code></pre>
<p>默认值是：<code>font-weight: normal;</code>.</p>
<p>根据字体所用的 <code>font-family</code>，这里的值可以设置成 <code>100</code> 到 <code>900</code>：</p>
<pre><code class="lang-css">font-weight: 100; /* Thin */
font-weight: 200; /* Extra Light */
font-weight: 300; /* Light */
font-weight: 400; /* 与 font-weight: normal; 相同 */
font-weight: 500; /* Medium */
font-weight: 600; /* Semi Bold */
font-weight: 700; /* 与 font-weight: bold; 相同 */
font-weight: 800; /* Extra Bold */
font-weight: 900; /* Ultra Bold */
</code></pre>
<p>很少有字体会支持所有的粗细程度。</p>
<h3 id="font-variant">font-variant</h3>
<p>这个可以将文本显示为小型大写字母</p>
<pre><code class="lang-css">h2{ font-variant: small-caps;}
</code></pre>
<p>默认值为：<code>font-variant: normal;</code>.</p>
<p>这个属性用的不多。</p>
]]></description><link>https://fanmingfei.com/posts/CSS_Font_Size_Style_Weight.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/CSS_Font_Size_Style_Weight.html</guid></item><item><title><![CDATA[CSS 行高]]></title><description><![CDATA[<p><code>line-height</code> 属性，可以用在块级元素上，顾名思义，就是 <strong>每一行的高度</strong>。</p>
<p><code>line-height</code> 属性可以使用下面的单位：</p>
<ul>
<li><code>px</code></li>
<li><code>em</code></li>
<li><code>%</code></li>
<li>无单位, 比如 <code>1.5</code></li>
</ul>
<p>在没有单位的时候，它的工作原理类似百分比。比如 <code>150%</code> 和 <code>1.5</code> 是一样的。后者更实用可读。</p>
<h3 id="-line-height-">为什么 line-height 非常重要</h3>
<p><code>line-height</code> 通过增加行间距，让你的文本更容易阅读。文本的大小影响可读性，建议使用相对与文本<strong>动态</strong>的值来设置行高，也就是无单位的值，不要使用<code>px</code>，因为使用px以后行高写死，如果字号变大，文字将会重叠。</p>
<p>当然 <code>px</code> 也是很有用的，比如说你想让一行文字对于整个块级元素上下居中，那么你可以设置行高为元素的高度。</p>
<p>因为使用 <code>%</code> 或 <code>em</code> 的可能产生意想不到的效果，推荐使用 <strong>无单位</strong> 的值</p>
<ul>
<li>在文章中，建议使用1.5倍行高</li>
<li>在文章标题中，建议使用1.2倍行高</li>
</ul>
<pre><code class="lang-css">body{ font-size: 16px; line-height: 1.5;}
</code></pre>
<p>通过计算，行高是 16 * 1.5 = <code>24px</code></p>
<h3 id="line-height-">Line-height 继承</h3>
<p>行高是会被子元素继承的：</p>
<pre><code class="lang-css">body{ font-size: 16px; line-height: 1.5;}
blockquote{ font-size: 18px;}
</code></pre>
<p><code>blockquote</code> 元素的行高会是 18*1.5 = <code>17px</code></p>
]]></description><link>https://fanmingfei.com/posts/CSS_Line_Height.html</link><guid isPermaLink="true">https://fanmingfei.com/posts/CSS_Line_Height.html</guid></item></channel></rss>