w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com zh-hans w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-data-and-props-part1.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>通过<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-registered.html">上一节中的学习</a>,咱们了解了在Vue中怎么创建伟德1946手机版和使用伟德1946手机版。在项目中使用伟德1946手机版,目的一般就是提高代码复用率,增强模块化,从而降低开发成本。在文章结尾处,我们提到了Vue中组合伟德1946手机版,就是<code>A</code>伟德1946手机版中包含了<code>B</code>伟德1946手机版。而伟德1946手机版与伟德1946手机版之间的相互使用避免不了数据之间的传递。那么Vue中伟德1946手机版的数据是如何传递的呢?这就是这一节将要了解和学习的内容。</p>" <p>首先要说明,伟德1946手机版数据传递不同于Vue全局的数据传递,<strong>伟德1946手机版实例的数据作用域名是孤立的</strong>,这里的孤立并不仅仅在伟德1946手机版内独立,而且是指上下层之间的数据隔离,<strong>即不能在子伟德1946手机版的模板内直接引用父伟德1946手机版的数据</strong>。如果要把数据从父伟德1946手机版传递到子伟德1946手机版,就需要使用<code>props</code>属性。这是父伟德1946手机版用来传递数据的一个自定义属性。也就是说,如果要彻底了解清楚Vue伟德1946手机版的数据传递,就很有必要了解清楚<code>props</code>属性。</p> <h2>伟德1946手机版数据流向</h2> <p>在Vue的官方文档中提到,在Vue中,父子伟德1946手机版的关系总结为:<strong><code>prop</code>向下传递,事件向上传递。</strong>父伟德1946手机版通过<code>prop</code>给子伟德1946手机版下发数据,子伟德1946手机版通过事件给父伟德1946手机版发送消息。如下图所示:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-8.png" alt="" /></p> <p>常把这种数据流称之为单向数据流。<code>prop</code>是单向绑定的:<strong>当父伟德1946手机版的属性变化时,将传给子伟德1946手机版,但是反过来不会</strong>。这是为了防止子伟德1946手机版无意间修改了父伟德1946手机版的状态,来避免应用的数据流变得难以理解。</p> <p>另外,每次父伟德1946手机版更新时,子伟德1946手机版的所有<code>prop</code>都会更新为最新值。这意味着你不应该在子伟德1946手机版内部改变<code>prop</code>。如果你这么做了,Vue会在控制台给出警告。用一张更细化的图来表示Vue伟德1946手机版系统中父子伟德1946手机版的数据流动:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-1.png" alt="" /></p> <p>使用<code>props</code>向子伟德1946手机版传递数据,首先要在子伟德1946手机版中定义子伟德1946手机版能接受的<code>props</code>,然后在父伟德1946手机版中子伟德1946手机版的自定义元素上将数据传递给它。</p> <h2>props的使用</h2> <p>前面提到过了,<strong>伟德1946手机版实例的作用域是孤立的</strong>。父伟德1946手机版需要通过<code>props</code>把数据传给子伟德1946手机版。要真正了解其中的原委,就很有必要了解清楚<code>props</code>的使用。那么我们从一些简单的示例开始吧。</p> <h3>props基础示例</h3> <p>首先来创建一个子伟德1946手机版<code>child</code>,并且在Vue的实例中定义了<code>data</code>选项。</p> <pre><code>let parent = new Vue({ el: '#app', data () { return { name: '伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】', age: 7 } }, components: { 'child': { template: '#child', props: ['myName', 'myAge'] } } }) </code></pre> <p>这里直接把Vue实例<code>parent</code>当作伟德1946手机版<code>child</code>的父伟德1946手机版。如果我们想要使用父伟德1946手机版的数据,则必须先在子伟德1946手机版中定义<code>props</code>,即:<code>props:['myName', 'myAge']</code>。</p> <p>接下来定义<code>child</code>伟德1946手机版的模板:</p> <pre><code>&lt;template id="child"&gt; &lt;div class="child"&gt; &lt;h3&gt;子伟德1946手机版child数据&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;label&gt;姓名&lt;/label&gt; &lt;span&gt;{{ myName }}&lt;/span&gt; &lt;/li&gt; &lt;li&gt; &lt;label&gt;年龄&lt;/label&gt; &lt;span&gt;{{ myAge }}&lt;/span&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>将父伟德1946手机版<code>parent</code>的<code>data</code>通过已定义好的<code>props</code>属性传递给子伟德1946手机版:</p> <pre><code>&lt;div id="app"&gt; &lt;child :my-name="name" :my-age="age"&gt;&lt;/child&gt; &lt;/div&gt; </code></pre> <p>给上面的示例,添加一点CSS,最终看到的效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="QQKKqg" src="//codepen.io/airen/embed/QQKKqg?height=400&amp;theme-id=0&amp;slug-hash=QQKKqg&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <blockquote> <p><strong>注意:</strong>由于HTML特性不区分大小写,在子伟德1946手机版定义<code>prop</code>时,使用了驼峰式大小写(camelCase)命名法。驼峰式大小写的<code>prop</code>用于特性时,需要转为短横线隔开(kebab-case)。例如,在<code>prop</code>中定义的<code>myName</code>,在用作特性时需要转换为<code>my-name</code>。</p> </blockquote> <p>用下图简单的剖析一下父伟德1946手机版<code>parent</code>是如何将数据传给子伟德1946手机版<code>child</code>,或许这样对于初学者更易于理解:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-2.png" alt="" /></p> <p>在父伟德1946手机版使用子伟德1946手机版时,通过以下语法将数据传递给子伟德1946手机版:</p> <pre><code>&lt;child :子伟德1946手机版的prop="父伟德1946手机版数据属性"&gt;&lt;/child&gt; </code></pre> <blockquote> <p><code>:</code>其实相当于<code>v-bind</code>,也就是Vue中的<code>v-bind</code>指令。这是属于动态绑定,让它的值被当作JavaScript表达式计算。稍后会做相关的介绍。</p> </blockquote> <h3>prop的绑定类型</h3> <p>在Vue中的<code>prop</code>绑定主要有单向绑定和双向绑定。先来了解一下单向绑定。</p> <h4>单向绑定</h4> <p>通过上面的示例,咱们简单的了解了怎么将父伟德1946手机版数据传递给子伟德1946手机版。而且在Vue 2.0中伟德1946手机版的<code>props</code>的数据流动改为了单向流动,即<strong>只能由伟德1946手机版外(调用伟德1946手机版方)通过伟德1946手机版的DOM属性<code>attribute</code>传递<code>props</code>给伟德1946手机版内,伟德1946手机版内只能被动接受伟德1946手机版外传递过来的数据,并且在伟德1946手机版内,不能修改由外层传来的<code>props</code>数据</strong>。</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-3.png" alt="" /></p> <p>但很多时候我们还是会修改子伟德1946手机版数据,那么问题来了,如果子伟德1946手机版修改了数据,对父伟德1946手机版是否有影响呢?我们基于上面的示例,做一下相应的调整:</p> <pre><code>&lt;div id="app"&gt; &lt;div class="parent"&gt; &lt;h3&gt;父伟德1946手机版Parent数据&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;label&gt;姓名:&lt;/label&gt; &lt;span&gt;{{ name }}&lt;/span&gt; &lt;input type="text" v-model="name" /&gt; &lt;/li&gt; &lt;li&gt; &lt;label&gt;年龄:&lt;/label&gt; &lt;span&gt;{{ age }}&lt;/span&gt; &lt;input type="text" v-model="age" /&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;child :my-name="name" :my-age="age"&gt;&lt;/child&gt; &lt;/div&gt; &lt;template id="child"&gt; &lt;div class="child"&gt; &lt;h3&gt;子伟德1946手机版child数据&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;label&gt;姓名&lt;/label&gt; &lt;span&gt;{{ myName }}&lt;/span&gt; &lt;input type="text" v-model="myName" /&gt; &lt;/li&gt; &lt;li&gt; &lt;label&gt;年龄&lt;/label&gt; &lt;span&gt;{{ myAge }}&lt;/span&gt; &lt;input type="text" v-model="myAge" /&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>看到的效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="JpbYeM" src="//codepen.io/airen/embed/JpbYeM?height=400&amp;theme-id=0&amp;slug-hash=JpbYeM&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>在这个Demo中我们来做两个小修改。首先修改父伟德1946手机版中的数据:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-4.gif" alt="" /></p> <p>从上面修改父伟德1946手机版数据得到的效果可以告诉我们:<strong>修改父伟德1946手机版的数据将会影响子伟德1946手机版,子伟德1946手机版的数据也会对应的修改</strong>。</p> <p>接下来再反过来,修改子伟德1946手机版的数据:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-4.gif" alt="" /></p> <p>从效果中可以看出:<strong>修改子伟德1946手机版数据并不会影响父伟德1946手机版的数据</strong>。</p> <blockquote> <p><code>prop</code>默认是单向绑定:<strong>当父伟德1946手机版的属性变化时,将传给子伟德1946手机版,但反过来不会。这是为了防止子伟德1946手机版无意修改了父伟德1946手机版的状态</strong>。</p> </blockquote> <p>用一个更真实的示例来进一步的阐述。假设我们要做一个iOS风格的开关按钮:</p> <ul> <li>点击按钮实现“开/关”状态切换</li> <li>不点击按钮,也可以通过外部修改数据切换“开/关”状态</li> </ul> <p>代码大致如下:</p> <pre><code>&lt;div id="app"&gt; &lt;switch-button :result="result"&gt;&lt;/switch-button&gt; &lt;div class="switch-button" @click="change" :class="result ? 'on' : 'off'"&gt; &lt;span&gt;{{ result ? "开" : "关" }}&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; Vue.component('switch-button', { template: ` &lt;div class="switch-button" @click="change" :class="result ? 'on' : 'off'"&gt; &lt;span&gt;{{ result ? "开" : "关" }}&lt;/span&gt; &lt;/div&gt; `, props: ['result'], methods: { change: function () { this.result = !this.result } } }) let app = new Vue({ el: '#app', data () { return { result: true } }, methods: { change: function () { this.result = !this.result; } } }) </code></pre> <p>上面的示例得到的效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="WMorBE" src="//codepen.io/airen/embed/WMorBE?height=400&amp;theme-id=0&amp;slug-hash=WMorBE&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>来操作一下两个切换按钮的效果:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-6.gif" alt="" /></p> <p>虽然效果上没有问题,但事实上,当我们点击“开关伟德1946手机版”时,将会发出一个警告信息:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-7.png" alt="" /></p> <p>再次验证:<strong>伟德1946手机版内不能修改<code>props</code>的值,同时修改的值也不会同步到伟德1946手机版外层,即调用伟德1946手机版方不知道伟德1946手机版内部当前的状态是什么。</strong></p> <blockquote> <p>至于这个Demo的操作会发出警告信息,以及怎么处理,这里先不说了。后续我们将会深入了解怎么处理这个Demo的警告信息。</p> </blockquote> <h4>双向绑定</h4> <p>咱们回到前面的示例中来。前面的示例演示告诉我们,父伟德1946手机版和子伟德1946手机版的操作:</p> <blockquote> <p><strong>修改父伟德1946手机版的数据将会影响子伟德1946手机版,子伟德1946手机版的数据也会对应的修改;修改子伟德1946手机版数据并不会影响父伟德1946手机版的数据</strong>。</p> </blockquote> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-8.gif" alt="" /></p> <p>要改变这一状态。可以通过<code>props</code>的双向绑定来完成。在Vue中,可以使用<code>.sync</code>显式地指定双向绑定,这样能让<strong>子伟德1946手机版的数据修改会回传给父伟德1946手机版</strong>。</p> <p>在上面的示例中调用子伟德1946手机版时,像下面这样操作:</p> <pre><code>&lt;child v-bind:my-name.sync="name" v-bind:my-age.sync="age"&gt;&lt;/child&gt; </code></pre> <div style="margin-bottom: 20px;"><iframe id="rJWMZK" src="//codepen.io/airen/embed/rJWMZK?height=400&amp;theme-id=0&amp;slug-hash=rJWMZK&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-prop-9.gif" alt="" /></p> <p>这正是 Vue 1.x 中的 <code>.sync</code> 修饰符所提供的功能。当一个子伟德1946手机版改变了一个带 <code>.sync</code> 的 <code>prop</code> 的值时,这个变化也会同步到父伟德1946手机版中所绑定的值。这很方便,但也会导致问题,因为它破坏了单向数据流。由于子伟德1946手机版改变 <code>prop</code> 的代码和普通的状态改动代码毫无区别,当光看子伟德1946手机版的代码时,你完全不知道它何时悄悄地改变了父伟德1946手机版的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。</p> <p>上面所说的正是我们在 2.0 中移除 <code>.sync</code> 的理由。但是在 2.0 发布之后的实际应用中,我们发现 <code>.sync</code> 还是有其适用之处,比如在开发可复用的伟德1946手机版库时。我们需要做的只是让子伟德1946手机版改变父伟德1946手机版状态的代码更容易被区分。</p> <p>从 2.3.0 起我们重新引入了 <code>.sync</code> 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父伟德1946手机版属性的 <code>v-on</code> 监听器。</p> <p>如下代码</p> <pre><code>&lt;comp :foo.sync="bar"&gt;&lt;/comp&gt; </code></pre> <p>会被扩展为:</p> <pre><code>&lt;comp :foo="bar" @update:foo="val =&gt; bar = val"&gt;&lt;/comp&gt; </code></pre> <p>当子伟德1946手机版需要更新 <code>foo</code> 的值时,它需要显式地触发一个更新事件:</p> <pre><code>this.$emit('update:foo', newValue) </code></pre> <p>感觉<code>props</code>双向绑定还是蛮复杂的。这也正是前面把切换按钮示例中解决警告信息留下来没有阐述的原因之一。下一节将学习解决父子伟德1946手机版数据和<code>props</code>的双向绑定。</p> <h2>总结</h2> <p>通过这篇文章的学习,了解了在Vue 2.0中,父子伟德1946手机版的数据流是单向的。即:<code>prop</code>默认是单向绑定,<strong>当父伟德1946手机版的属性变化时,将传给子伟德1946手机版,但反过来不会。这是为了防止子伟德1946手机版无意修改了父伟德1946手机版的状态</strong>。而很多时候我们又需要子伟德1946手机版修改数据时,在父伟德1946手机版中要有所反应。这个时候就需要涉及到双向数据绑定。在下一节中,将学习Vue 2.0中的数据双向绑定。</p> <p>由于本人是Vue的初学者,如果文章中有何不对之处,还请各路大神拍正。如果你有更好的想法和经验,欢迎在下面的评论中与我们一起分享。</p> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-data-and-props-part1.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-data-and-props-part1.html</a></p> rel="nofollow" </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/640.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/vue"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/vue/vue2"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue 2.0学习笔记</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/642.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue伟德1946手机版</a></div></div></div> Wed, 07 Feb 2018 15:52:47 +0000 Airen 2364 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/atomic-design.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><blockquote> <p>特别声明,本文转载<a href="http://xxysy.com/quot;//www.jianshu.com/u/37cb9a878440">@安琪Angela</a>的《<" href="http://xxysy.com/quot;//www.jianshu.com/p/13e87bf4f857">Atomi" Design原子设计 ┃ 构建科学规范的设计系统</a>》一文,如需转载,烦请注明原文地址:<a href="//www.jianshu.com/p/13e87bf4f857">https://www.jianshu.com/p/13e87bf4f857</a></p> rel="nofollow" </blockquote> <p>最近在Medium上看到一个设计理念正在兴起,这个设计方法逐渐被国外一些大公司运用于创建有层次和成熟规范的设计系统中。笔者很兴奋地读了相关的外文文章,准备把这个非常棒的独角兽设计流程分享给大家。</p> <h2>Introduction背景介绍</h2> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-1.jpg" alt="" /></p> <p>在2013年网页设计师@Brad Frost从化学中受到启发:原子(Atoms)结合在一起,形成分子(Molecures),进一步结合形成的生物体(Organisms)。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-2.png" alt="" /></p> <p>在已知宇宙中的所有事物都可以分解为一组有限的原子元素(下图是化学元素周期表)。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-3.jpg" alt="" /></p> <p>@Brad将这个概念应用在界面设计,我们的界面就是由一些基本的元素组成的。@Josh Duck的“HTML元素周期表”(下图)完美阐述了我们所有的网站、APP、企业内部网、hoobadyboops等等是如何由相同的HTML元素组成的。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-4.png" alt="" /></p> <p>通过在大层面(页)和小层面(原子)同时思考界面,Brad认为可以利用原子设计建立一个适应伟德1946手机版的动态系统。</p> <h2>Definition概念</h2> <p>原子设计是一种方法论,由原子、分子、组织、模板和页面共同协作以创造出更有效的用户界面系统的一种设计方法。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-5.png" alt="" /></p> <p>原子设计的五个阶段分别是:</p> <p><strong>Atoms原子</strong>:为网页构成的基本元素。例如标签、输入,或是一个按钮,也可以为抽象的概念,例如字体、色调等。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-6.png" alt="" /></p> <p><strong>Molecules分子</strong>:由原子构成的简单UI伟德1946手机版。例如,一个表单标签,搜索框和按钮共同打造了一个搜索表单分子。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-7.png" alt="" /></p> <p><strong>Organisms组织</strong>:由原子及分子组成的相对复杂的UI构成物 。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-8.png" alt="" /></p> <p><strong>Templates模版</strong>:将以上元素进行排版,显示设计的底层内容结构。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-9.png" alt="" /></p> <p><strong>Pages页面</strong>:将实际内容(图片、文章等)套件在特定模板,页面是模板的具体实例。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-10.png" alt="" /></p> <h2>Cases案例</h2> <p>在这里介绍一个华人设计师将Atomic Design应用在网页设计。</p> <h3>项目背景</h3> <p>FEVO主要业务为协助客户销售业务,因此我们有一个default的活动页面,客户提供活动资讯及促销机制,消费者即可上网购票。</p> <h3>客户需求</h3> <p>客户希望可以根据他们的品牌,设计定制化的网页,在有限的开发时间内要保持一致性。</p> <h3>问题定义</h3> <ul> <li>UI反馈方式不一致:易造成使用者经验混乱,增长认知时间。</li> <li>重点(购票)信息不明显:按钮在视觉上与促销优惠的黑色一致,没有重点的界面减低了销售的机会。</li> </ul> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-11.png" alt="" /></p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-12.png" alt="" /></p> <h3>设计过程</h3> <p>明确我们的设计目的是要用最少资源达到目的资源化,减少开发成本,让客户满意,这时我们可以利用原子设计。</p> <p>建立素材库,即Atomic Design的归纳元素。通过组织元素建立视觉层次,突出重点。并将此作为一致性的标准。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-13.jpeg" alt="" /></p> <p>根据使用情境,改变原子。</p> <p>由于素材库将网页拆解成元素,更方便重复使用、创造不同的视觉效果。最后设计师设计了四种模板,客户可以选择相对的颜色,页面主要色彩会应改变,符合品牌形象。</p> <p>图1:Modern现代简洁版,高浓度的Primary Color,产生有活力、精神的视觉效果。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-14.png" alt="" /></p> <p>图2:Royal深色底创造出高贵的形象,适合百老汇、奢侈品牌等。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-15.png" alt="" /></p> <p>图3:Spring Breeze利用女性柔和的颜色搭配,适合婚礼策划、春夏活动。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-16.png" alt="" /></p> <p>图4:Vibrant动感:渐变的背景,图片与促销优惠结合,在视觉上更为突出,适合演唱会、时尚秀等活动类型。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-17.png" alt="" /></p> <h2>Benefits优点</h2> <h3>Consistency一致性。</h3> <p>由于分解网站成单一元素,不论在哪一个页面,UI元素的互动性是相同的,例如颜色变化、字体的排序、以及回馈。不但让使用者经验相同,在视觉上更为和谐。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-18.jpeg" alt="" /></p> <h3>Efficiency效率。</h3> <p>由于建立了Pattern Library元件库,一旦要更改某一个元素,可以马上施行、应用。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-19.jpeg" alt="" /></p> <h3>Collaboration跨部门的共通语言。</h3> <p>不仅方便设计师思考页面的和谐性,也可以让工程师、品质检验清楚页面的逻辑架构及变化,减少不必要的来回沟通。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-20.jpeg" alt="" /></p> <h2>Criticism批评</h2> <h3>WHAT我们变成了用伟德1946手机版设计的机器人?</h3> <p>很多人当听到原子设计“工业化”和“再利用”的特点时,都将它们理解“标准化”和“创造性限制”。</p> <p>我不同意。当你真正找到了如何使用原子设计的方法时,你可以精确地决定何时何地给创造力腾出空间。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-21.png" alt="" /></p> <h3>WHEN“我们什么时候需要创造力,什么时候需要一致性?</h3> <p>我们可以在使用设计系统和原子设计方法的同时富有创造性。明确一个评判标准:在哪里保持强烈的一致性,在哪里创造惊喜或者展示品牌的独特性。</p> <p>如果我们想要一个强大的一致性和大量的重用系统,我们将从更具体和复杂的伟德1946手机版(如模板和页面)开始。</p> <p>如果我们想给设计师更多创造性的可能性,我们会给他们原子和分子,这样他们就可以创造新的成分,同时保持系统相似性。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-22.jpeg" alt="" /></p> <h3>HOW我们该怎么办呢?</h3> <p>工业化可以帮助我们节省重复设计和无用工作的时间,而设计者却没有额外的价值,例如:在15个不同的屏幕上执行相同的修改,创建20个相同伟德1946手机版,或者替换10个相同的实例。</p> <p>这种新获得的空闲时间应该允许我们为用户或客户设计更多有趣的元素:正确的用户流、品牌标识、用户反馈分析、开发创新的解决方案和进行相关的情感设计…</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-23.jpeg" alt="" /></p> <h2>Significance意义</h2> <h3>The part and the whole局部与整体。</h3> <p>使用Atomic Design构建伟德1946手机版系统,是创建一组相互依赖的元素。原子设计包含“局部影响整体,整体影响局部”的关系。我们不断地放大和缩小我们的界面。我们将花费时间在细节,微观交互,或者精炼一个伟德1946手机版,然后再后退一步来验证它在上下文中的样子,然后再后退一步,看看它作为一个整体做了什么。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-24.jpeg" alt="" /></p> <h3>Mutualize the work分担工作。</h3> <p>我们所有的伟德1946手机版都与我们的原子相连。由于一切都是相连的,我们可以很容易地验证修改系统的部分内容(比如色调原子)对系统其余部分的影响!</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-25.gif" alt="" /></p> <p>我们最终可以像开发者一样,拥有自己的风格指南,并围绕这些风格指南构建完整的系统。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-26.png" alt="" /></p> <h3>Share the system共享系统。</h3> <p>系统的共享对于保持不同产品之间的一致性是至关重要的。但当我们与其他设计师合作时,这一点更加困难,而且这种情况越来越频繁。</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-27.png" alt="" /></p> <p>在这里,我们需要一个所有人可以访问,并且始终是最新的共享库,共享库允许几个设计人员从相同的基础开始设计。其实这是简化了我们的工作,因为如果我们在共享库中更新伟德1946手机版,则修改将自动应用在使用此伟德1946手机版的所有设计内容中:</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-28.gif" alt="" /></p> <p>就现状而言,还没有一个完全适应原子设计的库……仍然缺少原子和伟德1946手机版之间的这种强相互依赖性,这使得我们需要创建一个生动的、不断发展的系统。</p> <p>另一个问题是,我们仍然有两个不同的库:设计器库和开发库……因此必须同时进行维护,容易发生错误并需要大量额外的工作。</p> <p>我对完美共享库的设想是:一个单一的库,它同时向设计者和开发人员提供资源:</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-29.jpeg" alt="" /></p> <p>Airbnb设计副总裁Alex Schleifer在IXDC2017国际体验设计大会上分享了这一创新<a href="http://xxysy.com/quot;//github.com/airbnb/react-sketchapp">Reac" Sketch app</a> 管理设计系统, 这是为Airbnb的设计系统而设计的,其实就是个实时更新的代码数据库,可以实时查询sketch数据、代码,也可以下载图标、设计模块,所有工程师、设计师都可以免费下载。看到这个,我想完美共享库也许并不是那么的遥远的未来…</p> <p><img src="/sites/default/files/blogs/2018/1802/Atomic-Design-30.png" alt="" /></p> <h2>References参考资料:</h2> <p>这次我只是创意的搬运工,有兴趣的旁友可以翻墙品味下原汁原味的Atomic Design。</p> <ul> <li><a href="http://xxysy.com/quot;//atomicdesign.bradfrost.com/table-of-contents/">Atomi" Design</a></li> <li><a href="http://xxysy.com/quot;//uxdesign.cc/atomic-design-how-to-design-systems-of-components-ab41f24f260e">Atomi" design: how to design systems of components</a></li> <li><a href="http://xxysy.com/quot;//uxdesign.cc/atomic-design-creativity-28ef74d71bc6">Atomi" Design &amp; creativity</a></li> <li><a href="http://xxysy.com/quot;//zhuanlan.zhihu.com/atomicdesign">原子设计</a></li>" </ul> </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/624.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">转载</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/384.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Atomic design</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/654.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">原子设计</a></div></div></div> Tue, 06 Feb 2018 13:08:47 +0000 Airen 2363 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-bootstrap-v4-button-component.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>在<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-registered.html">上一节中</a>,咱们学习了Vue中怎么创建伟德1946手机版。在这篇文章中我们以按钮伟德1946手机版为例,了解了怎么注册全局伟德1946手机版和局部伟德1946手机版。并且通过这些基础知识,可以轻易的创建类似于HTML中<code>button</code>元素效果的按钮伟德1946手机版。但这个伟德1946手机版非常的简陋,和我们想像的伟德1946手机版相差甚远。那么今天我们来看看,怎么在Vue中创建一个按钮伟德1946手机版。</p>" <h2>以Bootstrap的按钮为例</h2> <p>很多人说Bootstrap很拙逼,灵活性不够。但我不这么认为,我是Bootstrap的粉丝,我一直很推崇Bootstrap。并不是因为其效果有多牛逼,我膜拜的是他的设计思想。别的不多说了,今天我们的目的是使用Vue来创建一个按钮伟德1946手机版。那么我们就用Bootstrap的按钮来举例。在Bootstrap中的按钮分为<a href="http://xxysy.com/quot;//getbootstrap.com/docs/4.0/components/buttons/">Buttons</a>和<" href="http://xxysy.com/quot;//getbootstrap.com/docs/4.0/components/button-group/">Button组</a>两个伟德1946手机版,今天先来看<" href="http://xxysy.com/quot;//getbootstrap.com/docs/4.0/components/buttons/">Buttons伟德1946手机版</a>。</p>" <p>先来简单的回忆Bootstrap的Buttons伟德1946手机版,其效果和使用方式如下:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-button-component-1.png" alt="" /></p> <h2>简单分析</h2> <p>该伟德1946手机版是从Bootstrap4中获取的。正如上图所示,按钮中有很多不同的类和参数,所以我们将创建一个伟德1946手机版来完成。该伟德1946手机版是可以双向绑定的,也可以用于不同的地方。</p> <p>根据Bootstrap的按钮风格,我们将处理各种属性,比如:</p> <ul> <li><strong><code>btnText</code></strong>:按钮的文本</li> <li><strong><code>btnSize</code></strong>:按钮的大小,在Bootstrap中是通过<code>btn-lg</code>、<code>btn-sm</code>来控制</li> <li><strong><code>btnType</code></strong>:按钮的类型,在Bootstrap中是通过<code>btn-primary</code>、<code>btn-secondary</code>、<code>btn-success</code>、<code>btn-danger</code>、<code>btn-warning</code>、<code>btn-info</code>、<code>btn-light</code>和<code>btn-dark</code>等类名来控制</li> <li><strong><code>btnOutline</code></strong>:边框按钮,在Bootstrap中是通过<code>btn-outline-primary</code>、<code>btn-outline-secondary</code>、<code>btn-outline-success</code>、<code>btn-outline-danger</code>等类名来控制</li> <li><strong><code>btnActive</code></strong>:按钮的状态,它是一个布尔值,用来控制按钮是否是当前状态,在Bootstrap中,如果有<code>active</code>类名,表示按钮是当前状态(激活状态)</li> <li><strong><code>btnBlock</code></strong>:按钮是不是块级元素,它也是一个布尔值,在Bootstrap中通过<code>btn-block</code>类名来控制,如果有这个类名,按钮是一个块级元素</li> </ul> <p>这是对于Bootstrap中单个按钮会碰到的各种样式风格。在BootStrap中,还有一个按钮组的概念:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-button-component-2.png" alt="" /></p> <p>咱们先看看Vue怎么来写<code>button</code>伟德1946手机版。</p> <h2>创建按钮伟德1946手机版</h2> <p>通过前面的对Bootstrap伟德1946手机版的分析,可以得知,创建<code>button</code>伟德1946手机版时,其对应的<code>props</code>属性将会有<code>btnText</code>、<code>btnSize</code>、<code>btnType</code>、<code>btnOutline</code>、<code>btnActive</code>和<code>btnBlock</code>。而在上一节中,我们知道怎么注册伟德1946手机版。那么接下来,直接使用注册伟德1946手机版的语法糖来创建。</p> <pre><code>Vue.component('vue-button', { props: [ 'btnText', // 按钮文本内容 'btnType', // 按钮类型 'btnSize', // 按钮尺寸大小 'btnOutline', // 只有边框的按钮样式 'btnActive', // 按钮状态,是一个布尔值,true表示激活(当前状态) 'btnBlock' // 按钮是不是块元素,是一个布遥值,true表示块元素 ], template: ` &lt;button type="button" class="btn" :class='[computedType, computedSize, computedOutline, computedActive, computedBlock]' &gt;{{ btnText }}&lt;/button&gt; `, computed: { // 控制按钮的类型 computedType: function () { return `btn-${this.btnType}` }, // 控制按钮大小尺寸 computedSize: function () { return `btn-${this.btnSize}` }, // 控制按钮边框 computedOutline: function () { return `btn-outline-${this.btnOutline}` }, // 控制按钮是否激活 computedActive: function () { return this.btnActive === 'true' ? 'active' : '' }, // 控制按钮是否是块元素 computedBlock: function () { return this.btnBlock === 'true' ? 'btn-block' : '' } } }) </code></pre> <p>上面的代码其实表示我们已经完成了按钮伟德1946手机版的注册,在继续往下之前,先试着了解已经完成的工作:</p> <ul> <li>使用<code>Vue.component(tagName, options)</code>注册了一个<code>button</code>伟德1946手机版,这个伟德1946手机版的标签为<code>vue-button</code>;并且在这个伟德1946手机版中设置了<code>props</code>、<code>computed</code>两个选项</li> <li>给<code>props</code>属性设置了一个数组,数组中的值为<code>btnText</code>、<code>btnSize</code>、<code>btnType</code>、<code>btnOutline</code>、<code>btnActive</code>和<code>btnBlock</code>,这些都是我们控制按钮想要的属性,而这些属性都是来自Bootstrap的类(熟悉Bootstrap的同学一目了然)</li> <li>在<code>computed</code>中对属性值进行计算,以便正确的类可以用到模板中。你也看到了类的实现,使用的是数组的形式。有关于样式怎么绑定到Vue的伟德1946手机版中,<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/bind-inline-style-and-class.html">可以点击这里进行了解</a></li>" <li>计算值在将类追回到类数组中起主要作用,并根据对应的类获取所需要的样式,呈现对应的外观</li> </ul> <blockquote> <p>这个例子让我对<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-computed-intro.html">计算属性</a>(<code>computed</code>)和如何在计算值时考虑到伟德1946手机版的避性以及数据块的属性有了一个更好的理解。在Vue中,除了使用<code>computed</code>之外,还可以使用<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/working-with-methods-in-vue.html">方法</a>(<code>methods</code>)和<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-watch.html">观察者</a>(<code>watch</code>)。具本使用哪一种方式,可以根据自己的需求和应用场景来决定。如果你想了解这三者的不同之处,<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/when-to-use-methods-computed-properties-or-watchers.html">可以点击这里进行了解</a>。</p>" </blockquote> <h2>使用按钮伟德1946手机版</h2> <p>伟德1946手机版已经注册OK了,现在可以在Vue的实例中使用该伟德1946手机版。</p> <pre><code>&lt;div id="app"&gt; &lt;div class="container"&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Primary" btn-type="primary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Secondary" btn-type="secondary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Success" btn-type="success"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Danger" btn-type="danger"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Warning" btn-type="warning"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Info" btn-type="info"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Light" btn-type="light"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Dark" btn-type="dark"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Link" btn-type="link"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Primary" btn-outline="primary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Secondary" btn-outline="secondary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Success" btn-outline="success"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Danger" btn-outline="danger"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Warning" btn-outline="warning"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Info" btn-outline="info"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Light" btn-outline="light"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Dark" btn-outline="dark"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Link" btn-outline="link"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Large button" btn-type="primary" btn-size="lg"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Default button" btn-type="primary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Small button" btn-type="primary" btn-size="sm"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Large block button" btn-type="primary" btn-size="lg" btn-block=true&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Small block button" btn-type="primary" btn-size="sm" btn-block=true&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Primary" btn-type="primary" btn-size="lg" btn-active=true&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <p>没有样式的时候,效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-button-component-3.png" alt="" /></p> <p>看上去没有任何差别。这个时候把Bootstrap的样式代码添加进来,你可以只引入<code>button</code>部分的样式。这个时候你看到的效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="VQaZMX" src="//codepen.io/airen/embed/VQaZMX?height=400&amp;theme-id=0&amp;slug-hash=VQaZMX&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>除了上述的方式之外,也可以在使用的时候,通过<code>data</code>来传相应的参数,比如:</p> <pre><code>data: { buttonLabel: “Login”, buttonType:“primary”, bigButton:{ label:“Big Button Label”, type:“warning”, size:“lg”, active:“true”, block:“true” } } </code></pre> <p>在<code>app</code>的Vue实例中:</p> <pre><code>&lt;vue-button :btn-text=“buttonLabel” :btn-type=“buttonType”&gt;&lt;/vue-button&gt; &lt;vue-button :btn-text=“bigButton.label” :btn-type=“bigButton.type” :btn-size=“bigButton.size” :btn-active=“bigButton.active” :btn-block=“bigButton.block”&gt;&lt;/vue-button&gt; </code></pre> <p>效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-button-component-4.png" alt="" /></p> <p>不过上面的按钮并不完美,查看前面的Demo的源码,我们不难发现,有些类名会是这样:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-button-component-5.png" alt="" /></p> <p>有很多的<code>undefined</code>出现在<code>class</code>中。对于追求完美的同学而言,似乎无法接受。接下来尝试一下对其进行修正。</p> <pre><code>Vue.component('vue-button', { props: [ 'btnText', // 按钮文本内容 'btnType', // 按钮类型 'btnSize', // 按钮尺寸大小 'btnOutline', // 只有边框的按钮样式 'btnActive', // 按钮状态,是一个布尔值,true表示激活(当前状态) 'btnBlock' // 按钮是不是块元素,是一个布遥值,true表示块元素 ], template: ` &lt;button type="button" class="btn" :class='[computedType, computedSize, computedOutline, computedActive, computedBlock]' &gt;{{ btnText }}&lt;/button&gt; `, computed: { // 控制按钮的类型 computedType: function () { return this.btnType ? `btn-${this.btnType}` : '' }, // 控制按钮大小尺寸 computedSize: function () { return this.btnSize ? `btn-${this.btnSize}` : '' }, // 控制按钮边框 computedOutline: function () { return this.btnOutline ? `btn-outline-${this.btnOutline}` : '' }, // 控制按钮是否激活 computedActive: function () { return this.btnActive === 'true' ? 'active' : '' }, // 控制按钮是否是块元素 computedBlock: function () { return this.btnBlock === 'true' ? 'btn-block' : '' } } }) </code></pre> <p>在<code>btnType</code>、<code>btnSize</code>和<code>btnOutline</code>加了一个判断,如果没有传值,将会返回<code>''</code>。这样一来就不会在<code>class</code>中出现类似<code>btn-underfinde</code>这样的类名了。</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-button-component-6.png" alt="" /></p> <p>最终看到的<a href="http://xxysy.com/quot;//codepen.io/airen/full/GQZvVy/">效果是一致的</a>。</p>" <h2>单文件创建按钮伟德1946手机版</h2> <p>除此之外,咱位还可以单文件来创建伟德1946手机版。这里我们使用Vue-cli的方式来创建。有关于Vue-cli的操作,这里不详细阐述了,感兴趣的可以<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/running-environment.html">点击这里阅读</a>。</p>" <p>在<code>src/components/</code>目录下创建<code>Button.vue</code>文件,其实就是<code>button</code>伟德1946手机版所需要的内容。在单个文件中创建一个伟德1946手机版,主要包括:</p> <pre><code>&lt;template&gt; &lt;/template&gt; &lt;script&gt; &lt;/script&gt; &lt;style&gt; &lt;/style&gt; </code></pre> <p>其中<code>&lt;template&gt;</code>对应的是<code>button</code>中所需要的模板内容,<code>&lt;script&gt;</code>是伟德1946手机版中的JavaScript代码,而<code>&lt;style&gt;</code>则是伟德1946手机版需要的样式。比如我们所要的Bootstrap 4的按钮伟德1946手机版,可以这样写:</p> <pre><code>&lt;template&gt; &lt;button type="button" class="btn" :class='[computedType, computedSize, computedOutline, computedActive, computedBlock]' &gt;{{ btnText }}&lt;/button&gt; &lt;/template&gt; &lt;script&gt; export default { name: 'Button', props: { btnText: { type: String, default: '' }, btnType: { type: String, default: '' }, btnSize: { type: String, default: '' }, btnOutline: { type: String, default: '' }, btnActive: { }, btnBlock: { } }, computed: { // 控制按钮的类型 computedType: function () { return this.btnType ? `btn-${this.btnType}` : '' }, // 控制按钮大小尺寸 computedSize: function () { return this.btnSize ? `btn-${this.btnSize}` : '' }, // 控制按钮边框 computedOutline: function () { return this.btnOutline ? `btn-outline-${this.btnOutline}` : '' }, // 控制按钮是否激活 computedActive: function () { return this.btnActive === 'true' ? 'active' : '' }, // 控制按钮是否是块元素 computedBlock: function () { return this.btnBlock === 'true' ? 'btn-block' : '' } } } &lt;/script&gt; &lt;style scoped&gt; /*引入bootstrap.css中的button.css*/ .btn { display: inline-block; font-weight: 400; ... &lt;/style&gt; </code></pre> <p>接下来就可以这样调用<code>Button.vue</code>伟德1946手机版。比如在<code>App.vue</code>中:</p> <pre><code>&lt;template&gt; &lt;div id="app"&gt; &lt;div class="container"&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Primary" btn-type="primary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Secondary" btn-type="secondary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Success" btn-type="success"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Danger" btn-type="danger"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Warning" btn-type="warning"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Info" btn-type="info"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Light" btn-type="light"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Dark" btn-type="dark"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Link" btn-type="link"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Primary" btn-outline="primary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Secondary" btn-outline="secondary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Success" btn-outline="success"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Danger" btn-outline="danger"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Warning" btn-outline="warning"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Info" btn-outline="info"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Light" btn-outline="light"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Dark" btn-outline="dark"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Link" btn-outline="link"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Large button" btn-type="primary" btn-size="lg"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Default button" btn-type="primary"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Small button" btn-type="primary" btn-size="sm"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Large block button" btn-type="primary" btn-size="lg" btn-block="true"&gt;&lt;/vue-button&gt; &lt;vue-button btn-text="Small block button" btn-type="primary" btn-size="sm" btn-block="true"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;div class="block"&gt; &lt;vue-button btn-text="Primary" btn-type="primary" btn-size="lg" btn-active="true"&gt;&lt;/vue-button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; import vueButton from "./components/Button"; export default { name: "App", components: { vueButton } }; &lt;/script&gt; </code></pre> <p>最终你能看到的效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-button-component-7.png" alt="" /></p> <p>有关于源码<a href="http://xxysy.com/quot;//github.com/airen/VueStudy/tree/master/vue-button">可以点击这里获取</a>。另外有关于Vue学习的示例代码,都将放在Github的<" href="http://xxysy.com/quot;//github.com/airen/VueStudy">VueStudy</a>仓库,感兴趣的可以点击这里获取自己感兴趣的东西。如果你有兴趣,也可以将你的示例代码提交到这个库,与大家一起分享。</p>" <h2>总结</h2> <p>在<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-registered.html">伟德1946手机版使用</a>一文中,我们了解了在Vue中怎么注册全局和局部伟德1946手机版,以及怎么使用注册好的伟德1946手机版。为了巩固前面学习内容,这篇文章我个通过创建Bootstrap4中的<code>button</code>伟德1946手机版为例,看看在Vue中怎么创建伟德1946手机版,以及使用伟德1946手机版。</p>" <p>由于本人是Vue的初学者,如果文中有不对之处,还请各路大神拍正。如果你有更好的建议或经验欢迎在下面的评论中与我们一起分享。</p> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-bootstrap-v4-button-component.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-bootstrap-v4-button-component.html</a></p> rel="nofollow" </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/640.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/vue"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/vue/vue2"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue 2.0学习笔记</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/642.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue伟德1946手机版</a></div></div></div> Sun, 04 Feb 2018 16:02:05 +0000 Airen 2362 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-registered.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>从这一节开始正式进入对Vue 2.0伟德1946手机版的系统学习。在Vue中,伟德1946手机版是最强大的功能之一。而且Vue伟德1946手机版涉及到的知识点也非常的多,比如伟德1946手机版的使用,<code>prop</code>、事件、<code>slots</code>以及动态动组等等。在一节的内容中无法全部涵盖这些知识点。所以将会分几节内容来整理Vue伟德1946手机版中的学习笔记。</p> <h2>什么是伟德1946手机版</h2> <p>什么是伟德1946手机版?围绕这个问题,我查阅了这方面的相关资料,特别是几位大神<a href="http://xxysy.com/quot;//github.com/hax">@hax</a>、<" href="http://xxysy.com/quot;//github.com/xufei">@飞叔</a>和<" href="http://xxysy.com/quot;//github.com/fouber">@云龙</a>有关于伟德1946手机版相关的方面的阐述,让我受益非浅。建议大家多花点时间先阅读几位大神整理的伟德1946手机版相关的内容:</p>" <ul> <li>《<a href="http://xxysy.com/quot;//github.com/hax/hax.github.com/issues/21">关于伟德19463331开发中“模块”和“伟德1946手机版”概念的思考</a>》b" <a href="http://xxysy.com/quot;//github.com/hax">@hax</a></li>" <li>《<a href="http://xxysy.com/quot;//github.com/xufei/blog/issues/19">2015伟德19463331伟德1946手机版化框架之路</a>》b" <a href="http://xxysy.com/quot;//github.com/xufei">@飞叔</a></li>" <li>《<a href="http://xxysy.com/quot;//github.com/xufei/blog/issues/6">Web应用的伟德1946手机版化:基本思路</a>》b" <a href="http://xxysy.com/quot;//github.com/xufei">@飞叔</a></li>" <li>《<a href="http://xxysy.com/quot;//github.com/xufei/blog/issues/7">Web应用的伟德1946手机版化:管控平台</a>》b" <a href="http://xxysy.com/quot;//github.com/xufei">@飞叔</a></li>" <li>《<a href="http://xxysy.com/quot;//github.com/fouber/blog/issues/4">伟德19463331工程与模块化框" </a>》by <a href="http://xxysy.com/quot;//github.com/fouber">@云龙</a></li>" </ul> <p>我是一位CSSer,在很多时候也会聊模块化和伟德1946手机版相关的概念,接受这方面最早的概念来自于Bootstrap这个CSS Framework。后来我更喜欢Brad Frost提出的原子设计(Atomic Design):</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-1.png" alt="" /></p> <p>如果从这个角度出发,Web中的任何一个元素(对应原子设计中的Atoms原子),都可以把其当作一个伟德1946手机版,比如最常见的按钮。另外也可以把由多个原子构成的构建(Molecules分子),也可以当作是一个伟德1946手机版,比如一个搜索表单。</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-2.png" alt="" /></p> <p>在CSSer的世界中,经常会把Web中可复用的部分划分为伟德1946手机版。伟德1946手机版即是使用一个到多个元素(Atoms原子)组成的任何界面部分。比如下面的三个卡片,虽然在外观上长得不全一致,但他用到的元素近乎是一样的。</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-3.png" alt="" /></p> <p>但需要注意的是,<strong>伟德1946手机版并不一定需要模块化</strong>。</p> <blockquote> <p>伟德1946手机版和模块化两者有什么区别,强烈建议阅读贺老(@hax)的《<a href="http://xxysy.com/quot;//github.com/hax/hax.github.com/issues/21">关于伟德19463331开发中“模块”和“伟德1946手机版”概念的思考</a>》一文。</p>" </blockquote> <p>或许这样理解伟德1946手机版有点粗陋,那么我们来看看Vue官网对伟德1946手机版是怎么定义的:</p> <blockquote> <p>伟德1946手机版(Component)是Vue最强大的功能之一。伟德1946手机版可以扩展HTML元素,封装可重用的代码。在较高层面上,伟德1946手机版是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,伟德1946手机版也可以表现为用<code>is</code>特性进行了扩展的原生HTML元素。所有的Vue伟德1946手机版同时也都是<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-instances-and-life-cycles.html">Vue的实例</a>,所以可以接受相同的选项对象(除了一些根级特有的选项)并提供相同的<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-instances-and-life-cycles.html">生命周期钩子</a>。</p>" </blockquote> <p>Vue提供一个伟德1946手机版系统,提供了一种抽象,让我们可以使用独立可复用的小伟德1946手机版来构建大型应用,任意类型的应用界面都可以抽象为一个伟德1946手机版树:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-4.png" alt="" /></p> <p>越来越感觉伟德1946手机版类似原子设计中的,原子、分子和组织。而整个应用界面类似于原子设计中的模板和页面。</p> <h2>伟德1946手机版的创建</h2> <p>既然Vue的伟德1946手机版是一个非常强大的特性,那么我们首要要了解的是在Vue中怎么创建伟德1946手机版。@ANTHONYGORE在他的一篇博文中介绍了<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/seven-ways-to-define-a-component-template-by-vuejs.html">七种创建Vue伟德1946手机版的方式</a>。不管使用哪种方式,创建Vue的伟德1946手机版都有三个基本步骤:<strong>创建伟德1946手机版构造器、注册伟德1946手机版和使用伟德1946手机版</strong>。</p>" <p><img src="/sites/default/files/blogs/2018/1802/vue-component-5.png" alt="" /></p> <p>比如,我们创建一个<code>Button</code>伟德1946手机版:</p> <pre><code>// 1. 创建一个伟德1946手机版构造器 let myButton = Vue.extend({ template: `&lt;button&gt;点击我&lt;/button&gt;` }) // 2. 注册伟德1946手机版,并指定伟德1946手机版的标签,伟德1946手机版的HTML标签为&lt;my-button&gt; Vue.component('my-button', myButton) // 创建Vue实例 let app = nue Vue({ el: '#app' }) &lt;!-- 3. #app是Vue实例挂载的元素 --&gt; &lt;div id="app"&gt; &lt;my-button /&gt; &lt;/div&gt; </code></pre> <p>添加点CSS样式,看到的效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-6.png" alt="" /></p> <p>通过浏览器开发者工具查看,使用Vue的伟德1946手机版<code>&lt;my-button /&gt;</code>和使用HTML的元素<code>&lt;button&gt;</code>最终得到的结果没啥区别。</p> <p>创建这样一个简单的伟德1946手机版并不是很困难的事情,对于初学者而言,是要理解怎么创建伟德1946手机版。咱们一起来看看伟德1946手机版的创建和注册:</p> <ul> <li><code>Vue.extend()</code>是Vue构造器的扩展,调用<code>Vue.extend()</code>创建的是一个伟德1946手机版构造器</li> <li><code>Vue.extend()</code>构造器有一个选项对象,选项对象的<code>template</code>属性用于定义伟德1946手机版要渲染的HTML,简单的理解这个属性用来定义伟德1946手机版的模板(也就是伟德1946手机版的HTML结构)</li> <li>使用<code>Vue.component()</code>注册伟德1946手机版,在注册伟德1946手机版时需要提供两个参数,第一个参数是伟德1946手机版的标签,比如上例中的<code>my-button</code>,第二个参数是伟德1946手机版构造器,比如上例中的<code>myButton</code></li> <li>伟德1946手机版应该挂载到某个Vue实例下,否则它不会生效。这一点需要特别的注意。另外同一个伟德1946手机版可以同时挂载到多个Vue实例下</li> </ul> <h3>全局注册</h3> <p>我们已经知道,可以通过以下方式创建一个Vue实例:</p> <pre><code>let app = new Vue({ el: '#app' }) </code></pre> <p>并且使用<strong>Vue.component(tagName, options)可以注册一个伟德1946手机版,而且使用这种方式注册的伟德1946手机版是一个全局的</strong>,这意味着该伟德1946手机版可以在任意Vue实例下使用。比如:</p> <pre><code>Vue.component('my-button', myButton) </code></pre> <p>其中<code>myButton</code>是通过<code>Vue.extend()</code>方法构建的,除此之外,咱们还可以这样写:</p> <pre><code>Vue.component('my-button', { template: `&lt;button&gt;点击我&lt;/button&gt;` }) </code></pre> <blockquote> <p>请注意,对于自定义标签的命名,Vue不强制遵循<a href="http://xxysy.com/quot;//www.w3.org/TR/custom-elements/#concepts">W3C规则</a>(小写,并且包含一个短杠),尽管这被认为是最佳实践。</p>" </blockquote> <p>伟德1946手机版注册之后,便可以作为自定义元素<code>&lt;my-button /&gt;</code>在一个实例的模板中使用。注意确保在初始化根实例之前注册伟德1946手机版:</p> <pre><code>&lt;div id="app"&gt; &lt;my-button /&gt; &lt;/div&gt; </code></pre> <h3>局部注册</h3> <p>在Vue中,不必把每个伟德1946手机版都注册到全局。你也可以通过某个Vue实例/伟德1946手机版的实例选项<code>components</code>注册,使用该选项注册的伟德1946手机版被称为<strong>局部注册</strong>,言外之意,该伟德1946手机版只能在对应的Vue实例中使用,如果别的Vue实例调用该伟德1946手机版,将会报一个提示错误。比如,把上面的全局注册的伟德1946手机版,换成局局部注册:</p> <pre><code>let myButton = Vue.extend({ template: `&lt;button&gt;点击我&lt;/button&gt;` }) let app = new Vue({ el: 'app', components: { 'my-button': myButton } }) &lt;div id="app"&gt; &lt;my-button /&gt; &lt;/div&gt; </code></pre> <p>得到的效果和注册全局伟德1946手机版是一样的。不同的是,如果你在另一个Vue实例中调用注册的局部伟德1946手机版,改伟德1946手机版不会生效。比如在<code>app2</code>这个实例中调用<code>app</code>中注册的伟德1946手机版<code>my-button</code>,就不会生效。</p> <pre><code>&lt;div id="app2"&gt; &lt;my-button /&gt; &lt;/div&gt; </code></pre> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-7.png" alt="" /></p> <p>通过这个示例说明了注册全局伟德1946手机版和注册局部伟德1946手机版的不同方法,以用其运用范围:</p> <ul> <li>通过<code>Vue.component(tagName, options)</code>注册全局伟德1946手机版,可以在任何Vue实例范围中使用</li> <li>通过Vue实例的<code>components</code>属性注册局部伟德1946手机版,只能在该实例范围中使用</li> </ul> <h3>伟德1946手机版注册语法糖</h3> <p>以上伟德1946手机版注册的方式有些繁锁,Vue为了简化伟德1946手机版注册的过程,提供了注册语法糖。如果你仔细的话,前面也简单的提到过一下。那么这里特意提出来,伟德1946手机版注册语法糖。先来看使用<code>Vue.component()</code>直接创建和注册伟德1946手机版:</p> <pre><code>// 注册全局伟德1946手机版 my-button Vue.component('my-button', { template: `&lt;button&gt;点击我&lt;/button&gt;` }) let app = new Vue({ el: '#app' }) </code></pre> <p><code>Vue.component()</code>的第一个参数是伟德1946手机版标签名称,第二个参数是一个选项对象,使用选对象的<code>template</code>属性定义伟德1946手机版模板。使用这种方式,Vue在背后会自动调用<code>Vue.extend()</code>来创建伟德1946手机版构造器。</p> <p>接下来看在选项对象<code>components</code>属性中注册局部伟德1946手机版的语法糖:</p> <pre><code>let app = new Vue({ el: '#app', components: { 'my-button': { template: `&lt;button&gt;点击我&lt;/button&gt;` } } }) </code></pre> <p>尽管注册伟德1946手机版的语法糖简化了伟德1946手机版注册,但在<code>template</code>选项中拼接HTML元素还是相当的麻烦,尽管ES6的语法让事情变得简单了不少,但也将导致HTML和JavaScript的高耦合性。</p> <p>庆幸的是,Vue除了上面这些语法糖之外,还提供了其他的方式。比如<code>x-template</code>:</p> <pre><code>Vue.component('my-button', { template: '#my-button' }) &lt;script type="text/x-template" id="my-button"&gt; &lt;button&gt;点击我&lt;/button&gt; &lt;/script&gt; </code></pre> <p>内联模板<code>inline-template</code>方式:</p> <pre><code>Vue.component('my-button',{ // ... }) &lt;my-button inline-template&gt; &lt;button&gt;点击我&lt;/button&gt; &lt;/my-button&gt; </code></pre> <p>除了上述方式,还有<code>&lt;template&gt;</code>、<code>render()</code>函数、JSX以及单文件伟德1946手机版等方式。有关于这方面的详细介绍,<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/seven-ways-to-define-a-component-template-by-vuejs.html">可以点击这里</a>。</p>" <p>不过使用DOM模板解析时(例如,使用<code>el</code>选项来把Vue实例挂载到一个已有内容的元素上),你会受到HTML本身的一些限制,因为Vue只有在浏览器解析、规范化模反之后才能获取内容。尤其要注意,像<code>&lt;ul&gt;</code>、<code>&lt;ol&gt;</code>、<code>&lt;table&gt;</code>、<code>&lt;select&gt;</code>这样的元素里允许包含的元素有限制,而另一些像<code>&lt;option&gt;</code>这样的元素只能出现在某些特定元素的内部。</p> <ul> <li><code>a</code> 不能包含其它的交互元素(如按钮,链接)</li> <li><code>ul</code> 和 <code>ol</code> 只能直接包含 <code>li</code></li> <li><code>select</code> 只能包含 <code>option</code> 和 <code>optgroup</code></li> <li><code>table</code> 只能直接包含 <code>thead</code>, <code>tbody</code>, <code>tfoot</code>, <code>tr</code>, <code>caption</code>, <code>col</code>, <code>colgroup</code></li> <li><code>tr</code> 只能直接包含 <code>th</code> 和 <code>td</code></li> </ul> <p>在自定义伟德1946手机版中使用这些受限制的元素时会导致一些问题,例如:</p> <pre><code>&lt;table&gt; &lt;my-row&gt;...&lt;/my-row&gt; &lt;/table&gt; </code></pre> <p>自定义伟德1946手机版<code>&lt;my-row&gt;</code>会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的<code>is</code>特性:</p> <pre><code>&lt;table&gt; &lt;tr is="my-row"&gt;&lt;/tr&gt; &lt;/table&gt; </code></pre> <p><strong>应当注意,如果使用来自以下来源之一的字符串模板,则没有这些限制:</strong></p> <ul> <li><code>&lt;script type="text/x-template"&gt;</code></li> <li>JavaScript 内联模板字符串</li> <li><code>.vue</code> 伟德1946手机版</li> </ul> <p>因此,请尽可能使用字符串模板。</p> <h3>伟德1946手机版的<code>el</code>和<code>data</code>选项</h3> <p>传入Vue构造器的多数选项也可以用在<code>Vue.extend()</code>或<code>Vue.component()</code>中,不过有两个特列:<code>data</code>和<code>el</code>。Vue规定:</p> <blockquote> <p>在定义伟德1946手机版的选项时,<code>data</code>和<code>el</code>选项必须使用函数。</p> </blockquote> <p>实际上,如果你这么做:</p> <pre><code>Vue.component('my-component', { template: '&lt;span&gt;{{ message }}&lt;/span&gt;', data: { message: 'hello' } }) </code></pre> <p>那么 Vue 会停止运行,并在控制台发出警告,告诉你在伟德1946手机版实例中 <code>data</code> 必须是一个函数。但理解这种规则为何存在也是很有益处的,所以让我们先作个弊:</p> <pre><code>&lt;div id="example-2"&gt; &lt;simple-counter&gt;&lt;/simple-counter&gt; &lt;simple-counter&gt;&lt;/simple-counter&gt; &lt;simple-counter&gt;&lt;/simple-counter&gt; &lt;/div&gt; var data = { counter: 0 } Vue.component('simple-counter', { template: '&lt;button v-on:click="counter += 1"&gt;{{ counter }}&lt;/button&gt;', // 技术上 data 的确是一个函数了,因此 Vue 不会警告, // 但是我们却给每个伟德1946手机版实例返回了同一个对象的引用 data: function () { return data } }) new Vue({ el: '#example-2' }) </code></pre> <p>由于这三个伟德1946手机版实例共享了同一个 <code>data</code> 对象,因此递增一个 <code>counter</code> 会影响所有伟德1946手机版!这就错了。我们可以通过为每个伟德1946手机版返回全新的数据对象来修复这个问题:</p> <pre><code>data: function () { return { counter: 0 } } </code></pre> <p>现在每个 <code>counter</code> 都有它自己内部的状态了。</p> <h2>伟德1946手机版组合</h2> <p>伟德1946手机版设计初衷就是要配合使用的,最常见的就是形成父子伟德1946手机版的关系:伟德1946手机版<code>A</code>在它的模板中使用了伟德1946手机版<code>B</code>。它们之间必然需要相互通信:<strong>父伟德1946手机版可能要给子伟德1946手机版下发数据,子伟德1946手机版则可能要将它内部发生的事情告诉父伟德1946手机版</strong>。然而,通过一个良好定义的接口来尽可能将父子伟德1946手机版解耦也是很重要的。这保证了每个伟德1946手机版的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。</p> <p>在Vue中,父子伟德1946手机版的关系总结为<strong><code>prop</code>向下传递,事件向上传递</strong>。父伟德1946手机版通过<strong><code>prop</code></strong>给子伟德1946手机版下发数据,子伟德1946手机版通过<strong>事件</strong>给父伟德1946手机版发送消息。看看它们是怎么工作的。</p> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-8.png" alt="" /></p> <p>咱们先不深入了解父子伟德1946手机版关系,以及他们的通讯方式。我们先来看看父子伟德1946手机版的创建和使用姿势:</p> <pre><code>// 构造子伟德1946手机版 let myButton = Vue.extend({ template: `&lt;button&gt;Search&lt;/button&gt;` }) // 构造父伟德1946手机版 let mySearch = Vue.extend({ // 在父伟德1946手机版mySearch中使用&lt;my-button&gt;标签 template: ` &lt;!-- 注意,有多个标签时,需要用一个容器将他们包裹起来 --&gt; &lt;div&gt; &lt;label for="search"&gt;Search the site&lt;/label&gt; &lt;input type="search" name="search" id="search" placeholder="Enter keyword" /&gt; &lt;my-button /&gt; &lt;/div&gt; `, components: { // 局部注册myButton伟德1946手机版,该伟德1946手机版只能在mySearch伟德1946手机版内使用 'my-button': myButton } }) // 注册全局伟德1946手机版mySearch Vue.component('my-search', mySearch) let app = new Vue({ el: '#app' }) </code></pre> <p>添加一点样式。我们看到的效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="VQeWzo" src="//codepen.io/airen/embed/VQeWzo?height=400&amp;theme-id=0&amp;slug-hash=VQeWzo&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>分几个步骤来理解这段代码:</p> <ul> <li>先使用<code>Vue.extend()</code>定义了一个<code>myButton</code>伟德1946手机版构造器和<code>mySearch</code>伟德1946手机版构造器</li> <li><code>components: {'my-button', myButton}</code>将<code>myButton</code>伟德1946手机版注册到<code>mySearch</code>伟德1946手机版,并将<code>myButton</code>伟德1946手机版的标签设置为<code>my-button</code></li> <li>在<code>mySearch</code>伟德1946手机版内通过<code>template</code>标签,定义了搜索表单<code>mySearch</code>伟德1946手机版所需要的模板,并且引用了<code>myButton</code>伟德1946手机版</li> <li><code>Vue.component('my-search', mySearch)</code>全局注册<code>mySearch</code>伟德1946手机版</li> <li>在页面中使用<code>my-search</code>标签,挂截到<code>app</code>实例中,渲染整个<code>mySearch</code>伟德1946手机版,其子伟德1946手机版<code>myButton</code>也将被渲染出来</li> </ul> <p><img src="/sites/default/files/blogs/2018/1802/vue-component-9.png" alt="" /></p> <h2>总结</h2> <p>在这一节中,我们学习了Vue中伟德1946手机版的一些简单概念,并且掌握怎么通过<code>Vue.extend()</code>来构造伟德1946手机版,<code>Vue.component()</code>来注册全局伟德1946手机版以及<code>components</code>属性来注册局部伟德1946手机版。并且简单的学习了一些注册伟德1946手机版语法糖和创建伟德1946手机版的一些方式。文章中的示你的伟德1946手机版都是极其简单的,比如我们的按钮伟德1946手机版,事实上和HTML中写按钮一样的效果,那是我们对Vue的伟德1946手机版了解的还不够透彻,我相信随着后面的学习,我们可以把这个伟德1946手机版做得更完美。比如给他赋予事件,修改按钮内容,根据按钮状态修改颜色等等。</p> <p>由于本人是Vue的初学者,如果文章中有不对之处,还请各种大婶拍正。如果你对Vue的伟德1946手机版有很深入的了解以及丰富的经验,欢迎在下面的评论中与我们一起分享。最后希望这篇文章对初学者有所帮助。</p> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-registered.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-registered.html</a></p> rel="nofollow" </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/640.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/vue"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/vue/vue2"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue 2.0学习笔记</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/642.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue伟德1946手机版</a></div></div></div> Fri, 02 Feb 2018 17:18:59 +0000 Airen 2361 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/css-paint-api.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>CSS Paint API是W3C规范中之一,<a href="//www.w3.org/TR/css-paint-api-1/">目前的版本是Level1</a>。它也被称为<strong>CSS Custom Paint</strong>或者<strong>Houdini's Paint Worklet</strong>。对于开发者而言,有一个值得高兴的是,Chrome65将会支持该API。也就是说,可以使用CSS Paint API提供的<code>registerPaint(name, paintCtor)</code>做一些事情。</p> <p>那么CSS Paint API是什么?你能用它做什么?它又是如何工作的呢?带着一系列的为什么,我们开启对CSS Paint API的初探。</p> <h2>什么是CSS Paint</h2> <p>先来了解CSS Paint是什么?在理解这个概念之前,我们先来回忆一下,我们在平时写CSS时是如何给一个元素设置背景图片。了解CSS的同学,都应该知道,采用<code>background-image</code>属性,比如:</p> <pre><code>background-image: url(xxx.jpg) </code></pre> <p>或者:</p> <pre><code>background-image: linear-gradient(to bottom, red, green) </code></pre> <p>除了给<code>background-image</code>指定一个图片之外,还可以是渐变(CSS中的渐变相当于一张背景图片)。而我们要了解的CSS Paint则是通过JavaScript的方式,让你在CSS中能够引入用JavaScript编写的图形。感觉有点类似于HTML5中的<code>canvas</code>,对吗?如果你继续往后看,你会越加有这样的感觉。</p> <h2>写一个Paint Worklet</h2> <p>先定义一个叫作<code>myPainter</code>的Paint Worklet,接下来使用<code>CSS.paintWorklet.addModule('my-paint-worklet.js')</code>来加载已定义好的CSS Paint Worklet。在<code>my-paint-worklet.js</code>文件中,使用<code>registerPaint</code>函数来注册一个Paint Woklet的类:</p> <pre><code>class MyPainter { paint(ctx, geometry, properties) { // ... } } registerPaint('myPainter', MyPainter) </code></pre> <p>在<code>paint()</code>回调中,我们可以使用<code>&lt;canvas&gt;</code>中<code>CanvasRenderingContext2D</code>的<code>ctx</code>方法。如果你熟悉<code>&lt;canvas&gt;</code>,那么你就可以知道怎么绘制Paint Worklet。<code>geometry</code>用来指定画布的<code>width</code>和<code>height</code>,<code>properties</code>可以获取自定义元素属性,这么说有点抽象,后面会介绍到该属性。</p> <blockquote> <p><strong>特别声明:</strong>CSS Paint中的Paint Worklet的<code>ctx</code>和<code>&lt;canvas&gt;</code>中的<code>ctx</code>并不是百分之百的相同。到目前为止,文本渲染的方法是无法使用的,这主要是出于安全原因,你无法从画布上读取像素。</p> </blockquote> <p>来看一个简单的示例。先创建一个<code>index.html</code>文件:</p> <pre><code>&lt;!doctype html&gt; &lt;html&gt; &lt;head&gt; &lt;style&gt; body { width: 100vw; height: 100vh; background-image: paint(checkerboard); } &lt;/style&gt; &lt;script&gt; CSS.paintWorklet.addModule('checkerboard.js') &lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p>然后创建<code>checkerboard.js</code>,并在这个文件中添加下面的代码:</p> <pre><code>class CheckerboardPainter { paint(ctx, geom, properties) { const colors = ['red', 'green', 'blue']; const size = 32; for(let y = 0; y &lt; geom.height/size; y++) { for(let x = 0; x &lt; geom.width/size; x++) { const color = colors[(x + y) % colors.length]; ctx.beginPath(); ctx.fillStyle = color; ctx.rect(x * size, y * size, size, size); ctx.fill(); } } } } registerPaint('checkerboard', CheckerboardPainter); </code></pre> <p>到时将会看到的效果如下所示:</p> <p><img src="/sites/default/files/blogs/2018/1801/checkerboard1.png" alt="" /></p> <p>上面的示例,先定义了一个叫<code>CheckboardPainter</code>的Paint Worklet,并且将之注册为<code>checkboard</code>,然后通过<code>CSS.paintWorklet.addModule()</code>的方法加载这个Paint Worklet。最后在CSS中,使用<code>paint(checkboard)</code>给指定的元素添加背景图。其最终效果正如上图所示。</p> <p>可能你会纳闷,这样的效果使用<code>background-image</code>就能实现(不管是调用图片,还是使用<code>linear-gradient</code>都可以实现类似效果)。那他们两者之间有何区别吗?其实两者是有所区别的:</p> <blockquote> <p>CSS Paint与<code>background-image</code>的差别就是<code>background-image</code>是根据代码计算出来的,不会随着元素的大小变化而伸缩。而CSS Paint绘制的图像总是会和元素容器所需保持一样的大。也就是说,让你修改元素大小可视区域时,CSS Paint绘制的图像会重新绘制。言外之意,背景图像总是和它所需要的一样大,包括对高密度(Hight-density)显示器的补偿。</p> </blockquote> <p>是不是很酷。</p> <h2>个性化Paint Worklet</h2> <p>前面提到过,定义一个Paint Worklet时<code>paint()</code>方法接受三个参数,其中第三个参数<code>properties</code>可以让Paint Worklet可以访问其他CSS属性,这就是<code>properties</code>参数的强大之处。通过一个静态的类,比如<code>inputProperties</code>属性,你可以对任何CSS属性,包括自定义属性进行更改。这些值将通过<code>properties</code>参数提供给你。</p> <p>比如下面这个例子。同样先创建一个<code>index.html</code>文件,在文件中加入下面的代码:</p> <pre><code>&lt;!doctype html&gt; &lt;html&gt; &lt;head&gt; &lt;style&gt; body { width: 100vw; height: 100vh; --checkerboard-spacing: 10; --checkerboard-size: 32; background-image: paint(checkerboard); } &lt;/style&gt; &lt;script&gt; CSS.paintWorklet.addModule('checkerboard.js') &lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p>然后在创建的<code>checkerboard.js</code>文件中加入下面的代码:</p> <pre><code>class CheckerboardPainter { static get inputProperties() { return [ '--checkerboard-spacing', '--checkerboard-size' ] } paint(ctx, geom, properties) { const size = parseInt(properties.get('--checkerboard-size').toString()); const spacing = parseInt(properties.get('--checkerboard-spacing').toString()); const colors = ['red', 'green', 'blue']; for(let y = 0; y &lt; geom.height/size; y++) { for(let x = 0; x &lt; geom.width/size; x++) { ctx.fillStyle = colors[(x + y) % colors.length]; ctx.beginPath(); ctx.rect(x*(size + spacing), y*(size + spacing), size, size); ctx.fill(); } } } } registerPaint('checkerboard', CheckerboardPainter); </code></pre> <p>现在我们可以使用相同的代码来处理所有不同类型的格子效果。但更爽的是,现在可以通过开发者调试工具,<a href="http://xxysy.com/quot;//googlechromelabs.github.io/houdini-samples/paint-worklet/parameter-checkerboard/">在找到正确的外观之前</a>,对这些值进行修改。</p>" <p><img src="/sites/default/files/blogs/2018/1801/checkerboard2.gif" alt="" /></p> <p>在 <code>CheckerboardPainter</code> 类中,给静态属性 <code>inputProperties</code> 定义了两个属性:<code>--checkerboard-spacing</code> 和 <code>--checkerboard-size</code>,这两个属性可以像一般的 CSS 的属性一样使用于 HTML 元素,而这两个属性的值将被 <code>paint()</code> 的第三个参数 <code>properties</code> 获得,被用于生产图像。所以,如果修改 <code>body</code> 的 <code>--checkerboard-spacing</code> 或者 <code>--checkerboard-size</code> 属性,背景图将会发生改变,正如上面的录屏展示的效果一样。</p> <blockquote> <p><strong>注意:</strong>如果把颜色也参数化是不是很有意?<code>spec</code>允许<code>paint()</code>函数获取参数列表。这个特性还没有在Chrome中实现,因为它目前还严重依赖于Houdini的属性和值的API,让其能正常工作,还需要一些时间。</p> </blockquote> <h2>不支持Paint Worklet的浏览器</h2> <p>到目前为止,只有Chrome浏览器支持Paint Worklet(Chrome 65可以看到效果)。尽管其他浏览器都发出响应将会支持CSS Paint API的特性,但到目前依旧没有啥进展。为了跟上时代的发展,<a href="http://xxysy.com/quot;//ishoudinireadyyet.com/">Houdini支持吗</a>?与此同时,就算浏览器还不支持CS" Paint的Paint Worklet,我们也要确保使用渐进增加来保证你的代码能正常运行。为了确保这一点,你必须在CSS和JavaScript中调整你的代码。</p> <blockquote> <p>如果你从未接触过Houdini,甚至说都不知道他是什么?建议你点击这里对<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/553.html">Houdini</a>进行一些简单的了解。另外这里有一个<" href="http://xxysy.com/quot;//lab.iamvdo.me/houdini/smooth-corners">CS" Houdini的示例仓库</a>,这些示例将向你展示Houdini的神奇之处。</p> </blockquote> <p>回到正题上来吧,对于不支持的浏览器,可以通过检查CSS对象,实现对JavaScript中Paint Worklet支持情况做一个检测:</p> <pre><code>if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } </code></pre> <p>而在CSS通过<code>@supports</code>来做相应的检测:</p> <pre><code>@supports (background: paint(id)) { body { background-image: paint(checkerboard); } } </code></pre> <p>另外,众所周知,如果浏览器遇到一个未知的属性,则会忽略此属性声明的规则。如果你对同一个属性进行两次声明,前者是CSS的属性,其后紧跟Paint Worklet,你就可以对不支持CSS Paint的浏览器做降级处理。比如下面这样:</p> <pre><code>body { background-image: linear-gradient(0, red, blue); background-image: paint(myGradient, red, blue); } </code></pre> <p>这样一来,支持CSS Paint的浏览器,第二个<code>background-image</code>将会覆盖第一个。在不支持CSS Paint的浏览器,第二个属性规则将会示为无效,而第一个<code>background-image</code>将会起作用。</p> <h2>示例</h2> <p>在众多CSS Paint的示例中,其中一些比其他的示列更易理解。其中一个比较易于理解的示例就是使用CSS Paint来减少对DOM元素。通常,元素的添加纯粹是为了使用CSS创建修饰。例如,在<a href="http://xxysy.com/quot;//getmdl.io/">Materia" Design Lite</a>中的<code>button</code>的Ripple效果。为了实现这个效果,添加了两个额外的<code>&lt;span&gt;</code>元素。如果你的Web页面有很多这样的按钮效果,那么你就要增加很多个DOM元素,因此可能影响你的页面性能。如果使用<a href="http://xxysy.com/quot;//googlechromelabs.github.io/houdini-samples/paint-worklet/ripple/">CS" Paint来实现Ripple效果</a>,你不需要添加任何额外的DOM元素。此外,你还拥有更易于自定义的属性。</p> <p>使用CSS Paint的另一个好处是,在大多数的情况下,能解决很多CSS无法解决的事情,而且代码量也少。当然,其中也有一个取舍问题。当画布的大小或任何参数发生变化时,绘图的代码将会运行。因此,如果你的代码很复杂,那需要很长的时间,它可能会引入jank。Chrome正在处理主要线程上的Paint Worklet,因此即使运行时间长,Paint Worklet也不会影响主线程的响应能力。</p> <p>对于我来说,最令人兴奋的前景是,CSS Paint可以很快的为CSS新特性创建Polyfill。比如<a href="http://xxysy.com/quot;//lab.iamvdo.me/houdini/conic-gradient/"><code>conic-gradient</code>的Polyfill</a>。另外的一个例子,在CSS会议中,它决定你现在可以有多个边框颜色。在这个会议还在进行之时,@Kilpatrick就通过CS" Paint为其<a href="http://xxysy.com/quot;//twitter.com/malyw/status/934737334494429184">写了一个对应的Polyfill</a>。</p>" <h2>不规则盒子的思考</h2> <p>大多数人在学习CSS Paint时都从<code>background-image</code>和<code>border-image</code>着手。其实另一个值得我们思考的是,如果使用 CSS Paint的<code>mask-image</code>可以让DOM元素具有任意形状。比如<a href="http://xxysy.com/quot;//googlechromelabs.github.io/houdini-samples/paint-worklet/diamond-shape/">钻石形状</a>:</p>" <p><img src="/sites/default/files/blogs/2018/1801/houdinidiamond.png" alt="" /></p> <h2>写在最后</h2> <p>CSS Paint在Chrome Canary中已有一段时间。值得庆幸的是,在Chrome 65中将会默认启用CSS Paint。对于开发者而言,我们应该不断去尝试新的可能性,CSS Paint将会让我们打开更多的思路,为你的灵感提供更多于创作的空间。可以看看<a href="http://xxysy.com/quot;//lab.iamvdo.me/houdini/">@Vincen" De Oliveira收藏的一些案例</a>。或许在这些案例中,你也能产生一些新的灵感。</p> <blockquote> <p><strong>特别声明:</strong>此篇文章内容来源于<a href="http://xxysy.com/quot;//developers.google.com/web/resources/contributors/surma">@Surma</a>的《<" href="http://xxysy.com/quot;//developers.google.com/web/updates/2018/01/paintapi">CS" Paint API</a>》一文。</p> </blockquote> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/css-paint-api.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/css-paint-api.html</a></p> </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/translations"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">译文</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/653.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">CSS Paint API</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/553.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Houdini</a></div></div></div> Tue, 30 Jan 2018 15:56:29 +0000 Airen 2360 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/using-css-clip-path-create-interactive-effects.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>你是否还记得小时候剪过杂志上的照片,把它们粘在纸上,用来制作自己的拼贴画?这篇文章是关于使用CSS的<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/431.html"><code>clip-path</code></a>属性,用来实现Web上裁剪图片的效果。将讨论如何进行切割,以及如何使用这些镂空部件来制作一些有趣的效果。</p>" <p>我将以下面的照片为例。照片中的这朵花从照片中的其他部分脱颖而出。也变成一个自然的焦点。</p> <p><img src="/sites/default/files/blogs/2018/1801/clip-path.webp" alt="" /></p> <h2>创建SVG</h2> <p>首先,将示例图片导入其中创建一个新的SVG文件。你需要有处理矢量图的编辑软件,对图像进行切割。我使用的是一个免费的软件<a href="http://xxysy.com/quot;//gitlab.com/inkscape/inkscape">Inkscape</a>,你也可以使用其他的应用程序,比如Adob" Illustrator((译:译者用的是Sketch)),甚至还可以使用在线编辑器,比如<a href="http://xxysy.com/quot;//vectr.com/">Vectr</a>。</p>" <p>先在图像编辑器中创建一个新的SVG文档,其大小是<code>100px</code>。使用<code>100px</code>的正方形很重要,因为剪切路径的长度是百分比长度(length-percent)。选择<code>0 ~ 100</code>的比例将允许从像素到百分比的无缝转换。</p> <p>在继续下去之前,我发现一个非常有价值的事情是编辑器可以把SVG代码导出来。怎么导出SVG代码取决于应用程序。例如,在<a href="//css-tricks.com/snippets/svg/abobe-illustrator-export-options/">Illustrator中有两种方法</a>。查看标记可以让我们了解编辑器在幕后做什么,因为并非所有应用程序都以<a href="//css-tricks.com/one-illustration-three-svg-outputs/">相同的方式导出SVG</a>。看到代码会增加你对标记的理解。这是一个双赢的选择。</p> <p>导出的SVG代码类似下面这样:</p> <pre><code>&lt;svg … width="100px" height="100px" viewBox="0 0 100 100" …&gt; ... &lt;/svg&gt; </code></pre> <p>其中一个更重要的部分是SVG中的<code>viewBox</code>属性,因为它代表了SVG的内部坐标系统。下面是对它如何工作的详细解释。上面的代码中可以看到,SVG的<code>width</code>、<code>height</code>和<code>viewBox</code>的比例都是在<code>0</code>到<code>100</code>之间。</p> <p>接下来是导入图像。在这里,我们要将图像大小调整为<code>100px</code>,并将其置于原点<code>(0,0)</code>。这样做可能会破坏图像的长宽比,除非你的图像是正方形。可我们的示例图并不是正方形,在后面运用<code>clip-path</code>,这将不再会是问题。</p> <p><img src="/sites/default/files/blogs/2018/1801/clip-path-2.webp" alt="" /></p> <p>再次查看导出来的代码,在SVG文件中可以看到<code>&lt;image&gt;</code>元素。注意,<code>preserveAspectRatio</code>设置为<code>none</code>。这告诉我们图像的原始尺寸被忽略了。</p> <pre><code>&lt;svg width="100px" height="100px" viewBox="0 0 100 100" &gt; &lt;image id="clip-path" x="0" y="0" width="100" height="100" xlink:href="http://xxysy.com/quot;data:image/png;base64,..."&gt;&lt;/image&gt" &lt;/svg&gt; </code></pre> <blockquote> <p><strong>特别声明:</strong>译者使用的是Sketch制图软件,导出来的SVG代码会有一大坨,像屎一样的难看,并且<code>&lt;image&gt;</code>的<code>xlink:href</code>引入的是Base64图片。你也可以直接将图片的链接地址替换Base64代码。</p> </blockquote> <h2>屏蔽图像文件</h2> <p>现在来看看实际的图像切割。</p> <p>切割图像的概念叫做掩蔽(<strong>masking</strong>)。如果你不熟悉masking,并不要紧,它本质上是用钢笔工具在图像勾画一个封闭的区域。就像是你不懂矢量编辑也可以做到这一点。不需要任何特殊的技术,你只需要几个基本步骤就可以搞定。</p> <p>屏蔽位图就像从真正的杂志上剪下图像一样。这和在矢量编辑器中创建路径是类似的。选择钢笔工具,勾画出你想剪掉的部分轮廓。在这个示例中,就是图像中的花朵部分。在构造蒙层图像的过程中,你可以创造出许多你喜欢的东西。不过需要特别注意,一定要关闭路径。</p> <p>在进行切割时,只使用cusp节点是很重要的,因为<code>clip-path</code>不支持复杂的形状,比如贝塞尔曲线。它只支持简单的形状,比如<code>polygon</code>、<code>circle</code>和<code>ellipse</code>。</p> <p>如果我们现在查看导出来的SVG代码,那么输出的代码中会包含一个<code>path</code>元素,其中包含你绘制的形状的坐标。下面的代码是我输出的路径的示例:</p> <pre><code>... &lt;path d="m 52.843605,79.860084 -0.69448,1.767767 -0.883884,0 -1.26269,-1.578364 -0.757615,0.06314 -1.388959,-2.714785 -0.12627,-2.967323 -1.704632,2.525381 -1.136422,-0.126269 -0.505076,-2.841054 -1.515229,1.325825 -1.325825,-0.126269 -0.252538,-1.578363 -0.947018,-0.126269 -0.252538,-0.315673 -0.947018,0.126269 -0.69448,-0.757614 0.126269,-1.641498 -0.441942,-0.252538 -0.189403,-2.588516 -0.505077,-0.06314 -1.010152,0.568211 -0.568211,-1.452094 0.441942,-2.399112 -1.325825,-0.126269 -0.378808,-1.262691 0.378808,-2.08344 0.883883,-1.641498 -1.010152,-1.26269 0.505076,-1.957171 -1.452094,-1.010152 -0.378808,-1.010153 1.136422,-2.209709 -2.209709,-0.378807 -0.441941,-1.704632 0.631345,-2.020305 1.704632,-1.38896 -1.578363,-1.452094 0.568211,-2.462247 0.820749,-0.441942 0.126269,-1.515229 0.757614,-1.073287 0.441942,-1.515228 -0.505076,-1.38896 0,-2.272843 0.505076,-1.010153 1.136422,-0.505076 1.325825,0 0.06313,-0.568211 -0.947018,-2.08344 0.378807,-0.631345 0,-0.441942 1.073288,-0.69448 1.073287,0 0.56821,0.315673 -0.189403,-2.525381 0.189403,-0.883884 0.378808,0.757615 0.06313,-0.883884 0.378807,-0.378807 0.189404,-0.378807 0.126269,-2.08344 0.315673,0.06314 0,-0.568211 0.378807,-0.06313 1.199556,0.568211 0.505076,0.69448 0.252538,-2.08344 0.631346,-0.505076 0.631345,-0.568211 0.441942,-0.505076 0.252538,0.505076 0,-0.883883 1.262691,0.315673 0.820749,-1.894036 1.325825,1.136421 1.073287,-1.452094 0.820749,0.189403 1.010152,1.515229 0.505077,0.757615 0.631345,-1.452095 0.820749,-0.56821 0.820749,0.505076 0.378807,0.631345 0.820749,-0.189403 0.820749,0.947018 0,0.252538 0.69448,-0.126269 0.378807,0.631345 0.820749,0 0.568211,1.515229 0.378807,1.325825 0.505076,-0.189404 0.252538,0.441942 0.378808,0.126269 0.441941,2.08344 0,0.568211 0.505077,-0.126269 0,0.883883 0.694479,-0.252538 0.505077,0.505076 0.252538,0.947018 0,0.883884 0.315673,0 0.378807,0.631345 0.441941,0.631345 0.06314,1.515229 -0.378807,1.957171 -0.441942,1.767767 2.904189,-1.136422 0.252538,0.631345 0.126269,2.209709 -0.883884,1.830902 1.38896,0.378807 1.010153,1.199556 -0.378808,1.641498 -0.947018,1.767767 -0.505076,0.378807 0.69448,1.767767 1.010153,1.26269 0.378807,1.38896 -0.378807,1.515229 -0.568211,0.315673 -0.505077,1.010152 -1.452094,0.883884 0.189404,1.325825 0.315672,0.883883 -0.378807,1.38896 -1.388959,1.073287 -0.505077,0.126269 0,0.505077 -0.189403,1.830901 -1.010153,0.631345 0.820749,2.209709 -0.631345,1.452094 -1.641498,-0.189403 0.126269,1.578363 -0.315673,1.641498 -1.073287,0.505076 -0.378807,0.315673 -0.378807,0.883883 -0.252538,1.010153 0.06313,2.714785 -0.631345,0.631345 -1.578364,-0.883883 -0.757614,-1.262691 -0.189404,2.462247 0.189404,2.083439 -0.252538,2.588516 -0.441942,1.894036 -0.631345,0.631346 -0.631345,-0.189404 -0.820749,-0.883883 z" /&gt; ... </code></pre> <p>下面是我勾画花朵的录屏。</p> <p><img src="/sites/default/files/blogs/2018/1801/clip-path-4.gif" alt="" /></p> <p>这是我的图像,在蒙层(mask)上有一点点不透明,以显示最后的形状被剪掉:</p> <p><img src="/sites/default/files/blogs/2018/1801/clip-path-3.webp" alt="" /></p> <h2>将SVG转换为CSS Clip Path</h2> <p>现在我们有了这个蒙版,接下来让我们看看如何从SVG变成<code>clip-path</code>。这意味着在SVG代码中转换路径描述符或<code>d</code>属性。</p> <p>在我们研究如何进行转换之前,让我们讨论一下使用剪切路径的原因。你可能会问我们为什么要创建一个剪切路径?为会不在矢量图编辑器中屏蔽图像并导出预切图像呢?使用图像比使用大量的CSS代码要方便得多。但在我看来,使用剪切路径有两个主要好处:<strong>交互性和压缩</strong>。SVG本质上是DOM中的代码,它可以被操作,而且它的文件大小要比相同形状的位图图像小得多。</p> <p>CSS剪切路径的语法与SVG中的语法有些相反。成对用逗号分隔,空格是单独的坐标。这与SVG描述符语法完全相反。为了使转换更加复杂,有些形状只使用绝对坐标。SVG路径更为灵活,因为它们可以同时使用两个坐标系统。</p> <p>我创建了一个<a href="http://xxysy.com/quot;//www.npmjs.com/package/clip-svg">基本的节点脚本</a>,它可以转换SVG路径。它在相对坐标中使用路径,并使用CSS的<code>clip-path</code>输出相应的多边形。它使用正则解析SVG文件。可以明显的补充增加比例正常化。添加规范化将消除在创建掩码时只使用需要的正方形图形。</p>" <p>这是应用剪切路径到花朵上的照片效果:</p> <div style="margin-bottom: 20px;"><iframe id="MQWvOp" src="//codepen.io/airen/embed/MQWvOp?height=400&amp;theme-id=0&amp;slug-hash=MQWvOp&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h2>使用CSS Clip Path的技巧</h2> <p>现在我们有裁剪的部分,接下来看看我们能做些什么。</p> <h3>叠加效果</h3> <p>一个有意思的效果就是将裁剪下出来的部分叠加在原图上。下面的示例,说明了在原始图像上叠加裁剪部分的思路。它会给你一个定位和两个不同部分的概念。有了这两个不同的元素,我们就有可能分别对前景和背景应用不同的效果。</p> <div style="margin-bottom: 20px;"><iframe id="wyBzQv" src="//codepen.io/airen/embed/wyBzQv?height=400&amp;theme-id=0&amp;slug-hash=wyBzQv&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h3>高亮效果</h3> <p>图像上的某部分高亮不仅仅是视觉上的吸引,它还可以对你的网站的用户体验产生真正的影响。在Web页面中,你可能想要突出显示图像的某些部分,这并不难。在图像中某些部分高亮是一个用例。另一种方法是在产品展示中突出产品的某些特性。第三个案例可以是一张地图,你可以在其中高亮的地方讲一些有趣的事情。在适当地应用此功能增加用户的体验。使用剪切路径是在UI中实现高亮显示的一种方法。</p> <p>回到刚才的图像中,现在可以很容易地使用花朵高亮。高亮实现的方法是通过降低其不透明度来降低花的背景:</p> <div style="margin-bottom: 20px;"><iframe id="XZJjwz" src="//codepen.io/airen/embed/XZJjwz?height=400&amp;theme-id=0&amp;slug-hash=XZJjwz&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>我们可以在用户悬浮到花朵上时,花朵高亮显示。方法其一是添加JavaScript事件(<code>addEventListener</code>)并将它们附加到蒙版元素上。通过像<code>mouseenter</code>和<code>mouseout</code>事件来捕获用户悬浮在花朵上。我们甚至可以切换背景的类名来触发效果。其中需要对CSS的<code>opaticy</code>添加一个过渡效果。</p> <div style="margin-bottom: 20px;"><iframe id="QQwGwG" src="//codepen.io/airen/embed/QQwGwG?height=400&amp;theme-id=0&amp;slug-hash=QQwGwG&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>我们甚至可以在同一张图像中多次重复上述这样的技术,让一个图像在多个地方高亮。</p> <div style="margin-bottom: 20px;"><iframe id="qxEqOQ" src="//codepen.io/airen/embed/qxEqOQ?height=400&amp;theme-id=0&amp;slug-hash=qxEqOQ&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h3>褪色和模糊效果</h3> <p>去年我看到背景图像模糊的效果,这是一种增强前景元素的反向方法。而不是增强前景元素本身,实现这样的效果可以将背景图像模糊,来达到同等的效果。这种增强前景元素的方法有另一个效果:当前焦点的元素保持不变,但同时,它也变得更加突出。</p> <p>实现模糊效果的最简单的方法就是使用CSS中<code>filter</code>的<code>blur</code>。下面的示例与前面的示例类似,使用JavaScript的回调方法来触发鼠标悬浮的效果。它不是调整背景颜色,而是用CSS的<code>filter</code>的<code>blur</code>,并对其添加一定的过渡效果。</p> <div style="margin-bottom: 20px;"><iframe id="ZrYBer" src="//codepen.io/airen/embed/ZrYBer?height=400&amp;theme-id=0&amp;slug-hash=ZrYBer&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>然而,使用CSS的<code>filter</code>来实现模糊效果,对于性能而言是非常昂贵的。这与用于创建模糊效果的GPU上的着色器有关。使用CSS来实现模糊的动效不是一个很好的主意。一个更有效的选择是重用图像的预过滤版本,并使用交叉渐变(cross-fade)。换句话说,我们将复制的背景图像的不透明度变为动画,而不是对模糊做动效。其效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="GQgNMp" src="//codepen.io/airen/embed/GQgNMp?height=400&amp;theme-id=0&amp;slug-hash=GQgNMp&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h3>描边效果</h3> <p>另一种增强裁剪元素的效果是采用描边(outline)效果。重新使用蒙版是一个简单的方法。如果将SVG插入到两个主要元素之间,并添加一个<code>scale</code>(本例使用的是<code>1.04</code>),将其显示为一个较粗的描边效果。</p> <div style="margin-bottom: 20px;"><iframe id="QQwGQM" src="//codepen.io/airen/embed/QQwGQM?height=400&amp;theme-id=0&amp;slug-hash=QQwGQM&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>当然,我们也可以在鼠标悬浮的时候触发元素描边效果,并为其添加一些动效:</p> <div style="margin-bottom: 20px;"><iframe id="bLNBMW" src="//codepen.io/airen/embed/bLNBMW?height=400&amp;theme-id=0&amp;slug-hash=bLNBMW&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>蒙版的边缘有点粗。可以使用SVG过滤器给蒙版边缘进行一些软化效果的处理。如下所示:</p> <div style="margin-bottom: 20px;"><iframe id="WMboKE" src="//codepen.io/airen/embed/WMboKE?height=400&amp;theme-id=0&amp;slug-hash=WMboKE&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h3>镂空效果</h3> <p>如果想对剪掉的那么具有镂空效果呢?如果它显示了你想要扣掉部分背景呢?</p> <p>例如,你想剪掉一个油炸的圈饼。然后你想通过蒙版把中间的洞排除在外。那你怎么去掉蒙版呢?除非我们使用SVG,否则<code>clip-path</code>不允许我们这么做。这意味着一次创建多个形状是不可能的。</p> <p>创建这些孔的一种方法是使用非常薄的连接器,把它画成一个形状。换句话说,可以从边缘切开一个很薄的切口,然后把洞挖出来。下面示例演示整个效果的过程:</p> <div style="margin-bottom: 20px;"><iframe id="LQEbvY" src="//codepen.io/airen/embed/LQEbvY?height=400&amp;theme-id=0&amp;slug-hash=LQEbvY&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h3>形变效果</h3> <p>为了使高亮的效果更有效,我们实际可以修改<code>clip-path</code>本身来实现。下面是一个示例,在示例中创建了一个蝴蝶动态高亮效果。在鼠标悬浮时,三种不同的切割部分之间高亮显示。</p> <div style="margin-bottom: 20px;"><iframe id="yvygLx" src="//codepen.io/airen/embed/yvygLx?height=400&amp;theme-id=0&amp;slug-hash=yvygLx&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h3>双重曝光效果</h3> <p>另外我们可以使用<code>clip-path</code>创建双重曝光效果。在同一个蒙版中对两个图像做混合处理。</p> <div style="margin-bottom: 20px;"><iframe id="qxEROE" src="//codepen.io/airen/embed/qxEROE?height=400&amp;theme-id=0&amp;slug-hash=qxEROE&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h2>浏览器兼容性</h2> <p>那么现在可以在所有浏览器中使用<code>clip-path</code>吗?不幸的是,目前还不能。在Caniuse上查看的话,你可以看到,他一片通红。</p> <div style="margin-bottom: 20px;"><iframe src="//caniuse.com/css-clip-path/embed" scrolling="no" frameborder="0" height="300" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h2>总结</h2> <p>我希望你能从这篇文章中掌握一些关键:</p> <ul> <li>使用剪切路径是使图像高亮的一种方法</li> <li>在原始图像上叠加裁剪部分,可以在图像中创建不同类型的高亮效果</li> <li>可以通过蒙版对剪切部分创建交一些交互效果</li> <li><code>clip-path</code>属性为用户体验提供了一种新的方式,可以对图像的各个部分进行高亮显示</li> </ul> <blockquote> <p>本文根据<a href="//css-tricks.com/author/mikaelainalem/">@MIKAEL AINALEM</a>的《<a href="https://css-tricks.com/using-css-clip-path-create-interactive-effects/">Using CSS Clip Path to Create Interactive Effects</a>》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:<a href="//css-tricks.com/using-css-clip-path-create-interactive-effects/">https://css-tricks.com/using-css-clip-path-create-interactive-effects/</a>。</p> </blockquote> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/using-css-clip-path-create-interactive-effects.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/using-css-clip-path-create-interactive-effects.html</a></p> </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/translations"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">译文</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/68.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">CSS</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/431.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">clip-path</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/svg-tutorial"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">SVG</a></div></div></div> Tue, 30 Jan 2018 13:21:04 +0000 Airen 2359 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-modal-component.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>Modal弹框在Web应用或者Web页面上非常常见,很多时候在不同的项目都会重写这样的一个Modal弹框。为了能偷懒,思考了一下,能不能写一个伟德1946手机版,比如说使用Vue创建一个伟德1946手机版,一个Modal伟德1946手机版,让其能在各个Web页面或者应用上使用。在这篇文章中,学习一下如何使用<code>transition</code>和<code>slots</code>来创建可重用的Modal伟德1946手机版。</p> <h2>Modal构成</h2> <p>Modal对于大家来说是很常见的一个东东,在<a href="http://xxysy.com/quot;//dribbble.com/search?q=modal">Dribbble上搜索<code>modal</code>可以看到很多非常优秀的Modal设计</a>。比如下面这个:</p>" <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-2.png" alt="" /></p> <p>一般对于Modal弹框会有五个部分构成:</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-3.png" alt="" /></p> <ul> <li>Modal弹框的蒙层,一般是黑色半透明,全屏显示</li> <li>Modal弹框头部</li> <li>Modal弹框主体</li> <li>Modal弹框脚部</li> <li>Modal弹框关闭按钮</li> </ul> <p>这是一个Modal弹框最常见的五个部分,当然不是所有的Modal弹框都会有这些东西。对于以前我们写一个Modal弹框会这样来写:</p> <pre><code>&lt;div class="modal-backdrop"&gt; &lt;div class="modal"&gt; &lt;div class="modal-header"&gt; &lt;div class="modal-close"&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class="modal-body"&gt;&lt;/div&gt; &lt;div class="modal-footer"&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <blockquote> <p>很多时候在<code>modal-footer</code>中也会带有关闭弹框的动作按钮,需要具体场景具体分析。</p> </blockquote> <h2>使用Vue创建Modal伟德1946手机版</h2> <h3>使用Vue-cli构建项目</h3> <p>在这里我使用Vue-cli来构建Vue的项目,对于Vue-cli在这里不做过多的阐述。通过:</p> <pre><code>vue init webpack vue-modal </code></pre> <p>然后一路按照命令提示执行下去。运行<code>npm run dev</code>,你会先看到一个这样的界面:</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-4.png" alt="" /></p> <h2>创建modal伟德1946手机版</h2> <p>找到项目中的<code>src/components/</code>目录,创建一个<code>Modal.vue</code>文件。有了这个伟德1946手机版文件之后,咱们先从伟德1946手机版的模板开始。需要定义出Modal伟德1946手机版常见的几个部位:</p> <pre><code>&lt;template&gt; &lt;div class="modal-backdrop"&gt; &lt;div class="modal"&gt; &lt;slot name="header"&gt;&lt;/slot&gt; &lt;slot name="body"&gt;&lt;/slot&gt; &lt;slot name="footer"&gt;&lt;/slot&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>注意,这里使用了Vue的<code>slots</code>。这样我们就可以通过<code>props</code>来提供Modal弹框的<code>header</code>、<code>body</code>和<code>footer</code>。因为使用<code>slots</code>可以有更多的灵活性。</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-1.png" alt="" /></p> <p>使用<code>slots</code>使用我们可以很轻易地重用不同类型的弹框内容。可以在Modal弹框显示一个简单的文本,但是我们可能希望重用相同的Modal弹框来提交一个请求。虽然<code>props</code>一般情况下足够我们构建一个伟德1946手机版,但是通过<code>props</code>会要求我们使用<code>v-html</code>来渲染它。这样做一个不好的地方,很可能会受到XSS的攻击。</p> <p>在这里,使用不同命名的<code>slots</code>,可以让我们在一个伟德1946手机版中使用多个<code>slots</code>。当定个一个指定的<code>slots</code>时,我们所识别的任何名称都将被呈现,而不是原来的<code>slots</code>。简单的理解,就把它看作是一个占位符。有点类似于<code>input</code>中的<code>placeholder</code>,如果不显式的给<code>slots</code>指定内容,它也可以有默认的内容。</p> <p>由于所提供的内容将会替换<code>&lt;slot&gt;</code>标记,为了保证<code>&lt;slot&gt;</code>对应的区域有我们想要的类名,最好用一个容器将每个<code>&lt;slot&gt;</code>包裹起来。这个时候我们的模板变成这样:</p> <pre><code>&lt;template&gt; &lt;div class="modal-backdrop"&gt; &lt;div class="modal"&gt; &lt;div class="modal-header"&gt; &lt;slot name="header"&gt;&lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-body"&gt; &lt;slot name="body"&gt;&lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-footer"&gt; &lt;slot name="footer"&gt;&lt;/slot&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>给<code>slots</code>添加一点默认的值以及给容器设置一些初始的CSS样式,让其看起来像一个基本的Modal弹框:</p> <pre><code>&lt;template&gt; &lt;div class="modal-backdrop"&gt; &lt;div class="modal"&gt; &lt;div class="modal-header"&gt; &lt;slot name="header"&gt; &lt;h2&gt;这是Modal弹框的标题&lt;/h2&gt; &lt;button type="button" class="btn-close" @click="close"&gt;x&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-body"&gt; &lt;slot name="body"&gt; 这是Modal弹框的主体 &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-footer"&gt; &lt;slot name="footer"&gt; 这是Modal弹框的脚部 &lt;button type="button" class="btn-green" @click="close"&gt;关闭&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; export default { name: 'Modal', data () { return { } }, methods: { close: function () { this.$emit('close'); } } } &lt;/script&gt; &lt;style scoped&gt; .modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0,0,0,.3); display: flex; justify-content: center; align-items: center; } .modal { background-color: #fff; box-shadow: 2px 2px 20px 1px; overflow-x:auto; display: flex; flex-direction: column; } .modal-header, .modal-footer { padding: 15px; display: flex; } .modal-header { border-bottom: 1px solid #eee; color: #4aae9b; justify-content: space-between; } .modal-footer { border-top: 1px solid #eee; justify-content: flex-end; } .modal-body { position: relative; padding: 20px 10px; } .btn-close { border: none 0; font-size: 20px; padding: 20px; cursor: pointer; font-weight: bold; color: #4aae9b; background-color: transparent; } .btn-green { color: #fff; background-color: #4aae9b; border: 1px solid #4aae9b; border-radius: 2px; } &lt;/style&gt; </code></pre> <p>这时你看到的弹框基本效果像这样:</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-5.png" alt="" /></p> <p>目前一进入页面,Modal弹框就打开了。这并不是我们想要的交互行为。一般情况下,Modal弹框是不显示的,只有用户进行了某个操作行为,才会显示Modal框。咱们在上面的基础上做一下调整。</p> <p>在<code>App.vue</code>中的<code>template</code>添加一个按钮:</p> <pre><code>&lt;template&gt; &lt;div id="app"&gt; &lt;button type="button" class="btn" @click="showModal"&gt;打开Modal&lt;/button&gt; &lt;modal v-show="isModalVisible" @close="closeModal" /&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>在<code>&lt;modal&gt;</code>中使用<code>v-show</code>指令绑定了<code>isModalVisible</code>,而这个<code>isModalVisible</code>默认是一个<code>flase</code>值,只有当<code>button</code>的<code>@click</code>触发了<code>showModal</code>方法时,<code>isModalVisible</code>的值才会变成<code>true</code>。同时<code>&lt;modal&gt;</code>绑定了一个<code>@close</code>事件,这个事件中有<code>closeModal</code>的方法,当这个事件触发时,<code>isModalVisible</code>将又会变回<code>false</code>。这些事情都将在<code>App.vue</code>的<code>&lt;script&gt;</code>中完成:</p> <pre><code>&lt;script&gt; import Modal from './components/Modal.vue' export default { name: 'App', components: { Modal }, data () { return { isModalVisible: false } }, methods: { showModal: function () { this.isModalVisible = true }, closeModal: function () { this.isModalVisible = false } } } &lt;/script&gt; </code></pre> <p>这个时候在你的浏览器中将会看到一个这样的效果:</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-6.gif" alt="" /></p> <p>现在基本上能满足我们要的Modal弹框的需求。但现在点击Modal弹框之外的地方,无法关闭Modal弹框。也就是点击蒙层无法关闭弹框。要实现这样的一个交互行为,咱们得回到<code>Modal.vue</code>伟德1946手机版中,在<code>props</code>添加一个<code>show</code>。并且需要在<code>modal-backdrop</code>中添加一个<code>@click</code>事件,并给这个事件传入<code>close</code>方法,同时需要在<code>.modal</code>中添加<code>@click.stop</code>,以名造成点击弹框任何地方都会关闭Modal弹框。</p> <pre><code>&lt;template&gt; &lt;div class="modal-backdrop" @click="close" v-show="show"&gt; &lt;div class="modal" @click.stop&gt; &lt;div class="modal-header"&gt; &lt;slot name="header"&gt; &lt;h2&gt;这是Modal弹框的标题&lt;/h2&gt; &lt;button type="button" class="btn-close" @click="close"&gt;x&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-body"&gt; &lt;slot name="body"&gt; 这是Modal弹框的主体 &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-footer"&gt; &lt;slot name="footer"&gt; 这是Modal弹框的脚部 &lt;button type="button" class="btn-green" @click="close"&gt;关闭&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; export default { name: 'Modal', props: ['show'], data () { return { } }, methods: { close: function () { this.$emit('close'); } } } &lt;/script&gt; </code></pre> <p>这个时候看到效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-7.gif" alt="" /></p> <p>此时你往<code>Modal.vue</code>中添加不同内容时,看到的弹框内容就不一样,比如文章中最早展示的一个Modal弹框效果。上面只是一个最基本的Modal弹框。</p> <h2>添加transitions</h2> <p>现在我们打开Modal弹框的效果是很生硬的,并没有任何动效,也不生动。事实上,在Vue中可以通过<code>&lt;transition&gt;</code>来帮助我们实现这样的效果。</p> <p>Vue提供了一个<code>&lt;transition&gt;</code>容器伟德1946手机版,它允许我们添加进入和离开的过渡效果。这个容器伟德1946手机版可以用于任何元素或伟德1946手机版,提供了CSS和JavaScript的钩子。</p> <p>每当一个伟德1946手机版或一个元素由<code>transition</code>插入或删除时,Vue将会检查给定的元素是否有CSS的<code>transition</code>,并在正确的时间添加或删除它们。对于JavaScript的钩子来说也是如此。但是在Modal弹框中,我们使用<code>&lt;transition&gt;</code>的CSS钩子足够了。</p> <p>Vue的<code>transition</code>提供了<a href="http://xxysy.com/quot;//vuejs.org/v2/guide/transitions.html#Transition-Classes">六种类</a>来控制元素进入和离开的过渡效果。它们中的每个都将以<code>transition</code>的<code>name</code>名做为前缀来命名。有关于这方面的详细说明,可以查看<" href="http://xxysy.com/quot;//alligator.io/vuejs/understanding-transitions/">官方文档</a>。</p>" <p>首先改造一下<code>Modal.vue</code>的模板,在模板中<code>.modal-backdrop</code>外添加一个<code>&lt;transition&gt;</code>容器伟德1946手机版,并在这个容器伟德1946手机版中添加<code>name</code>用来控制进入和离开的过渡效果:</p> <pre><code>&lt;template&gt; &lt;transition name="modal-fade"&gt; &lt;div class="modal-backdrop" @click="close" v-show="show"&gt; &lt;div class="modal" @click.stop&gt; &lt;div class="modal-header"&gt; &lt;slot name="header"&gt; &lt;h2&gt;这是Modal弹框的标题&lt;/h2&gt; &lt;button type="button" class="btn-close" @click="close"&gt;x&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-body"&gt; &lt;slot name="body"&gt; 这是Modal弹框的主体 &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-footer"&gt; &lt;slot name="footer"&gt; 这是Modal弹框的脚部 &lt;button type="button" class="btn-green" @click="close"&gt;关闭&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/transition&gt; &lt;/template&gt; </code></pre> <p>现在,添加了一个过渡,让不透明慢慢淡出,使用应用的类:</p> <pre><code>.modal-fade-enter, .modal-fade-leave-active { opacity: 0; } .modal-fade-enter-active, .modal-fade-leave-active { transition: opacity .5s ease } </code></pre> <p>你将看到的效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-8.gif" alt="" /></p> <p>有关于Vue中的<code>transition</code>更多的介绍可以阅读下面相关的文章:</p> <ul> <li><a href="http://xxysy.com/quot;//vuejs.org/v2/guide/transitions.html#Transition-Classes">Enter/Leav" &amp; List Transitions</a></li> <li><a href="http://xxysy.com/quot;//vuejs.org/v2/guide/transitioning-state.html">Stat" Transitions</a></li> <li><a href="//css-tricks.com/intro-to-vue-5-animations/">Intro to Vue.js: Animations</a></li> <li><a href="http://xxysy.com/quot;//github.com/asika32764/vue2-animate">vue2-animate</a></li>" <li><a href="http://xxysy.com/quot;//snipcart.com/blog/vuejs-transitions-animations">Creatin" Vue.js Transitions &amp; Animation: Live Examples</a></li> <li><a href="http://xxysy.com/quot;//alligator.io/vuejs/understanding-transitions/">Understandin" Vue.js Transitions</a></li> <li><a href="http://xxysy.com/quot;//github.com/itemsets/vue2/issues/2">关于Vue2.0的过渡效果与过渡状态</a></li>" <li><a href="http://xxysy.com/quot;//chenjiahan.github.io/vodal/">Vodal" A vue modal with animations.</a></li> </ul> <h2>添加可访问性</h2> <p>为了让Modal弹框更具可访问性,可以通过<code>aria</code>属性来实现这一点。</p> <p>添加<code>role="dialog"</code>能让读屏软件识别我们的伟德1946手机版是一个应用程序的对话框。它与UI的其他部分分离。虽然添加<code>role="dialog"</code>对我们的Modal弹框是有帮助的,但是它还不足以使Modal弹框变得具有可访问性,我们需要在适当的地方添加一些标记。通过<code>aria-labelledby</code>和<code>aria-bedby</code>属性可以实现这一个目标。</p> <pre><code>&lt;template&gt; &lt;transition name="modal-fade"&gt; &lt;div class="modal-backdrop" @click="close" v-show="show"&gt; &lt;div class="modal" @click.stop role="dialog" aria-labelledby="modalTile" arial-describedby="modalDescription"&gt; &lt;div class="modal-header" id="modalTitle"&gt; &lt;slot name="header"&gt; &lt;h2&gt;这是Modal弹框的标题&lt;/h2&gt; &lt;button type="button" class="btn-close" @click="close" aria-label="Close modal"&gt;x&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-body" id="modalDescription"&gt; &lt;slot name="body"&gt; 这是Modal弹框的主体 &lt;/slot&gt; &lt;/div&gt; &lt;div class="modal-footer"&gt; &lt;slot name="footer"&gt; 这是Modal弹框的脚部 &lt;button type="button" class="btn-green" @click="close" aria-label="Close modal"&gt;关闭&lt;/button&gt; &lt;/slot&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/transition&gt; &lt;/template&gt; </code></pre> <h2>个性化Modal弹框</h2> <p>万事具备,现在我们来实现文章开头想要的一个Modal弹框:</p> <pre><code>&lt;template&gt; &lt;div id="app"&gt; &lt;div class="btn-area"&gt; &lt;button type="button" class="btn" @click="showModal"&gt;打开Modal&lt;/button&gt; &lt;/div&gt; &lt;modal v-show="isModalVisible" @close="closeModal"&gt; &lt;div slot="header"&gt; &lt;h3&gt;Publish your site&lt;/h3&gt; &lt;button type="button" class="btn-close" @click="closeModal" aria-label="Close modal"&gt;x&lt;/button&gt; &lt;/div&gt; &lt;div class="content" slot="body"&gt; &lt;p&gt;Ready to publish your site to the web? Awesome! Use Twitter, Facebook, or your email to save your site. You can always make edits to your site later.&lt;/p&gt; &lt;div class="action"&gt; &lt;button type="button"&gt;Use Twitter&lt;/button&gt; &lt;button type="button"&gt;Use Facebook&lt;/button&gt; &lt;/div&gt; &lt;div class="form"&gt; &lt;div class="form-control"&gt; &lt;label for="email"&gt;Your email&lt;/label&gt; &lt;input type="email" placeholder="Your email" id="email" name="email"&gt; &lt;/div&gt; &lt;div class="form-control"&gt; &lt;label for="password"&gt;Choose a password&lt;/label&gt; &lt;input type="password" placeholder="Choose a password" id="password" name="password"&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;button type="button" slot="footer" @click="closeModal" aria-label="Close modal" class="btn-green"&gt;publish site&lt;/button&gt; &lt;/modal&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>这个时候你看到的Modal弹框像这样:</p> <p><img src="/sites/default/files/blogs/2018/1801/modal-vue-9.png" alt="" /></p> <p>现在可以添加样式了。让你的弹框更美观一点。这里就不聊样式的添加了。</p> <blockquote> <p>有关于本例的Modal弹框伟德1946手机版的代码可以在<a href="http://xxysy.com/quot;//github.com/airen/VueStudy">VueStudy</a>的<" href="http://xxysy.com/quot;//github.com/airen/VueStudy/tree/master/vue-modal">vue-modal</a>中获取。如果你对<strong>VueStudy</strong>感兴趣,可以观注这个项目,如果你愿意分享你的Demo或者伟德1946手机版,也可以向这个仓库提交<" href="http://xxysy.com/quot;//github.com/airen/VueStudy/compare">Requests</a>。</p>" </blockquote> <h2>总结</h2> <p>这篇文章整理了怎么使用Vue来创建一个Modal弹框。主要用到了Vue的两个知识点,一个是<code>slots</code>,另一个是<code>transition</code>容器伟德1946手机版。其中<code>slots</code>可以帮助我们构建高可复用性的Modal弹框;<code>transition</code>可以帮助我们Modal弹框出现和离开的时候有一定的动效,不至于效果生硬。</p> <p>这个Modal弹框还不够强大,个性化的配置不强。随着后续知识点更足之时,将再次完善这个伟德1946手机版。由于本人是Vue的初学者,如果文章中有不对之处,还请各咱大婶斧正。如果你有更好的建议或者作品,欢迎一起分享。</p> <h2>扩展阅读</h2> <ul> <li><a href="//junerockwell.com/how-to-make-simple-basic-modal-using-bootstrap-css-vuejs-2/">Make a Modal using Bootstrap CSS and VueJS</a></li> <li><a href="http://xxysy.com/quot;//vuejs.creative-tim.com/vue2-transitions/">Vu" 2 Transitions:Elegant, reusable Vue 2 transitions</a></li> <li><a href="http://xxysy.com/quot;//adamwathan.me/2016/01/04/composing-reusable-modal-dialogs-with-vuejs/">Composin" Reusable Modal Dialogs with Vue.js</a></li> <li><a href="http://xxysy.com/quot;//alligator.io/vuejs/vue-modal-component/">Buildin" a Modal Component with Vue.js</a></li> </ul> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-modal-component.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-modal-component.html</a></p> rel="nofollow" </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/640.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/vue"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/vue/vue2"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue 2.0学习笔记</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/642.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue伟德1946手机版</a></div></div></div> Sat, 27 Jan 2018 15:05:04 +0000 Airen 2358 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/svg/create-progress-bar-with-svg-and-css.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/svg/create-progress-bar-with-svg.html">在上一节中</a>,学习了怎么利用SVG的<code>stroke-dasharray</code>和<code>stroke-dashoffset</code>来制作进度条。记得在文章末尾留了一个悬念,说这一节中,要聊聊怎么用Vue来把这个SVG的进度封装成伟德1946手机版。</p>" <p>咱们先不聊Vue怎么把这个封装成伟德1946手机版(我搜索了一下,有现在所这方面伟德1946手机版,而且做得蛮好的,接下来先学习一下)。今天接着聊上一节中的进度条怎么来实现。不过略有不同。不同点来自于网上一位朋友向我提的一个问题。问题是这样的:</p> <blockquote> <p>用SVG做一个环形的进度条一样的东西,这个很好做(用的 <code>stroke-dasharray</code>),但是,我需要再做一个小圆,随着环形慢慢变成一个圆时一直和头部在一起移动(这个不懂)。这个能稍微指点一下我吗?</p> <p><img src="/sites/default/files/blogs/2018/1801/unnamed.png" alt="" /></p> </blockquote> <p>说实话,我也很好奇!但我想在以前的基础上添加一个<code>&lt;circle&gt;</code>,让这个新添加的<code>circle</code>跟着内圈做位移(旋转),应该是可以的。也正因为出于好奇,给他找了两个Demo(<a href="http://xxysy.com/quot;//www.adeveloperdiary.com/d3-js/create-custom-progress-chart-using-d3-js-part1/">Demo1</a>、<" href="http://xxysy.com/quot;//www.adeveloperdiary.com/d3-js/create-custom-progress-chart-using-d3-js-part2/">Demo2</a>" 。原本应该能满足其需求。但并不是这样,他希望不借用第三方的库。后来回来自己写了一个 Demo。简单的记录一下这个过程。</p> <h2>简单的分析一下</h2> <p>不管是水平的还是圆形的,其都具有三层,比如下图所示:</p> <p><img src="/sites/default/files/blogs/2018/1801/process-bar-2-1.png" alt="" /></p> <ul> <li>灰色表示进度条的底层(总共的长度)</li> <li>红色表示进度条的进度 (已完成长度)</li> <li>绿色表示起点 (跟随圆点)</li> </ul> <p>在SVG中的话,对应的就是:</p> <ul> <li>水平进度条,灰色和红色是由<code>&lt;line&gt;</code>元素构建,绿色的由<code>&lt;circle&gt;</code>构建</li> <li>圆形进度条由三个<code>&lt;circle&gt;</code>构建</li> </ul> <p>知道其中的原委,那就好办的得多了。咱们可以先绘制一个静态的图出来:</p> <pre><code>&lt;svg width="200" height="200" viewBox="0 0 200 200"&gt; &lt;!-- 水平进度条 --&gt; &lt;line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#666" stroke-linecap="round" /&gt; &lt;line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#FC4D04" stroke-dasharray="170" stroke-dashoffset="90" stroke-linecap="round" id="lineInner" /&gt; &lt;circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" /&gt; &lt;!-- 圆形进度条 --&gt; &lt;circle cx="100" cy="120" r="74" fill="none" stroke="#666" stroke-width="12" /&gt; &lt;circle cx="100" cy="120" r="74" fill="none" stroke="#FC4D04" stroke-width="12" stroke-dasharray="465" stroke-dashoffset="400" stroke-linecap="round" /&gt; &lt;circle cx="100" cy="120" r="74" fill="none" stroke="green" stroke-width="12" stroke-dashoffset="464.5" stroke-dasharray="465" stroke-linecap="round" /&gt; &lt;/svg&gt; </code></pre> <p>这个时候看到的效果如下:</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;display: flex; justify-content: center; align-items: center; width: 100%;"> <svg width="200" height="200" viewBox="0 0 200 200"> <line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#666" stroke-linecap="round" /> <line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#FC4D04" stroke-dasharray="170" stroke-dashoffset="90" stroke-linecap="round" id="lineInner" /> <circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" id="circle"/> <circle cx="100" cy="120" r="74" fill="none" stroke="#666" stroke-width="12" /> <circle cx="100" cy="120" r="74" fill="none" stroke="#FC4D04" stroke-width="12" stroke-dasharray="465" stroke-dashoffset="400" stroke-linecap="round" /> <circle cx="100" cy="120" r="74" fill="none" stroke="green" stroke-width="12" stroke-dashoffset="464.5" stroke-dasharray="465" stroke-linecap="round" id="circleInner"/> </svg> </div> <p>从效果中,可以看出来,现在绿色的圆点都在默认的起点。并没有跟随进度条红色的部分。在SVG中并没有直接的方法或者API来让绿色点保持在红色点的终点。这个时候咱们需要借助CSS的特性来完成。这也离不开一些数学的计算。具体怎么来计算呢?先来看水平进度条。</p> <p>通过前面的学习,知道使用<code>.getTotalLength()</code>可以知道其长度。在这个示例中,通过:</p> <pre><code>document.querySelectorAll('line')[0].getTotalLength() // =&gt; 170 </code></pre> <p>可以知道整个水平进度条的总长度是<code>170</code>,而红色的进度是通过<code>stroke-dashoffset</code>值来控制的,在这个示例中是<code>90</code>。有了这两个值,就很好的控制绿色的值了。使用CSS的<code>translateX()</code>来做对应的位移,其位移的值是<code>170 - 90</code>,即<code>80</code>。</p> <pre><code>&lt;circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" id="circle" style="transform: translateX(80px);"/&gt; </code></pre> <p>在行内添加了<code>transform:translateX(80px)</code>,当然你也可以在CSS样式中写。这个时候你看到的效果如下:</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;display: flex; justify-content: center; align-items: center; width: 100%;"> <svg width="200" height="200" viewBox="0 0 200 200"> <line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#666" stroke-linecap="round" /> <line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#FC4D04" stroke-dasharray="170" stroke-dashoffset="90" stroke-linecap="round" id="lineInner" /> <circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" id="circle" style="transform: translateX(80px);"/> <circle cx="100" cy="120" r="74" fill="none" stroke="#666" stroke-width="12" /> <circle cx="100" cy="120" r="74" fill="none" stroke="#FC4D04" stroke-width="12" stroke-dasharray="465" stroke-dashoffset="400" stroke-linecap="round" /> <circle cx="100" cy="120" r="74" fill="none" stroke="green" stroke-width="12" stroke-dashoffset="464.5" stroke-dasharray="465" stroke-linecap="round" id="circleInner"/> </svg> </div> <p>上面完成了水平进度条的效果,接下来看圆形进度条。对于圆形的进度条,我们同要要知道其长度,同样可以能过<code>.getTotalLength()</code>来获取:</p> <pre><code>document.querySelectorAll('circle')[2].getTotalLength(); // =&gt;464.2044677734375 </code></pre> <p>建议上向上取整,此时我们的周长是<code>465</code>。除此之外,还可以通过<code>2π * r</code>来计算。</p> <pre><code>Math.PI * 2 * 74; // =&gt; 464.9557127312894 </code></pre> <p>因为圆形进度条是一个圆,那么通过<code>transltate()</code>是无法实现的,这个时候,需要使用的是<code>transform</code>中的<code>rotate()</code>来旋转。既然需要旋转就需要一个角度值。那又得数学公式了。简单的回忆一下:</p> <pre><code>弧度 = 角度 * Math.PI / 180 =&gt; rad = (π / 180) * deg 角度 = 弧度 * 180 / Math.PI =&gt; deg = (rad * 180) / π </code></pre> <p>其实就是角度(<code>deg</code>)和弧度(<code>rad</code>)之间的转换。一个完整的圆的弧度是<code>2π</code>,所以<code>2π rad = 360°</code>,<code>1 π rad = 180°</code>,<code>1°=π/180 rad</code>,<code>1 rad = 180°/π</code>(约<code>57.29577951°</code>)。以度数表示的角度,把数字乘以<code>π/180</code>便转换成弧度;以弧度表示的角度,乘以<code>180/π</code>便转换成度数。</p> <blockquote> <p>角所对的弧长是半径的几倍,那么角的大小就是几弧度</p> </blockquote> <p>平时我们常看到的各种弧度如下:</p> <p><img src="/sites/default/files/blogs/2018/1801/process-bar-2-2.png" alt="" /></p> <p>通过JavaScript可以来这样进行角度和弧度之间的换算:</p> <pre><code>rad = (Math.PI * deg) / 180 deg = (rad * 180) / Math.PI </code></pre> <p>下图展示了常见的角度和弧度之间的换算:</p> <p><img src="/sites/default/files/blogs/2018/1801/process-bar-2-3.png" alt="" /></p> <p>回到我们的示例当中来。通过<code>.getTotalLength()</code>可以获取圆角的长度约为<code>465</code>,对应红色圆的<code>stroke-dashoffset</code>值为<code>400</code>。那么就能获取到红色进度的弧长为<code>65</code>。前面说过,<strong>角所对的弧长是半径的几倍,那么角的大小就是几弧度</strong>:</p> <pre><code>65 / 74 = .87rad </code></pre> <p>另外也可以将<code>.87rad</code>换成<code>deg</code>。根据前面的计算公式,可以计算出来:</p> <pre><code>65 / 74 * 180 / Math.PI = 50.32737389662636 </code></pre> <p>大约<code>50deg</code>。那么在<code>circle</code>元素中添加:</p> <pre><code>transform:rotate(.8783783783783784rad); transform-origin: 100px 120px; </code></pre> <p>这个时候看到的效果如下:</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;display: flex; justify-content: center; align-items: center; width: 100%;"> <svg width="200" height="200" viewBox="0 0 200 200"> <line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#666" stroke-linecap="round" /> <line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#FC4D04" stroke-dasharray="170" stroke-dashoffset="90" stroke-linecap="round" id="lineInner" /> <circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" id="circle" style="transform: translateX(80px);"/> <circle cx="100" cy="120" r="74" fill="none" stroke="#666" stroke-width="12" /> <circle cx="100" cy="120" r="74" fill="none" stroke="#FC4D04" stroke-width="12" stroke-dasharray="465" stroke-dashoffset="400" stroke-linecap="round" /> <circle cx="100" cy="120" r="74" fill="none" stroke="green" stroke-width="12" stroke-dashoffset="464.5" stroke-dasharray="465" stroke-linecap="round" id="circleInner" style="transform:rotate(.8783783783783784rad); transform-origin: 100px 120px;"/> </svg> </div> <blockquote> <p><strong>特别声明</strong>:SVG的坐标系统和Web中的坐标系统是不一样的,所以需要对元素做坐标变换,也就有了<code>transform-origin: 100px 120px</code>的设置。</p> </blockquote> <h2>添加动画效果</h2> <p>SVG中改变<code>stroke-dashoffset</code>的值,可以实现线条自画的效果。这并不是什么新东西,也不是复杂的东西。在我们今天的示例当中的关键点是怎么当绿色的点跟着移动。</p> <p>通过前面的了解,在水平进度条中,需要给<code>translateX( )</code>一个值,而这个值是:</p> <pre><code>.getTotalLength() - stroke-dashoffset </code></pre> <p>对于圆形的稍为复杂一点,其要给<code>rotate()</code>传一个值:</p> <pre><code>(.getTotalLength() - stroke-dashoffset) / r; // =&gt; 弧度值 </code></pre> <p>或者:</p> <pre><code>(.getTotalLength() - stroke-dashoffset) / r * 180 / Math.PI; =&gt;角度值 </code></pre> <p>既然知道其中原理,我们就来换成Vue的环境。把红色的<code>stroke-dashoffset</code>绑定一个数据,然后使用<code>input[type="range"]</code>来动态修改<code>stroke-dashoffset</code>的值。并且动态修改绿色圆点的位置或者旋转值。比如:</p> <pre><code>&lt;div id="app"&gt; &lt;svg width="200" height="200" viewBox="0 0 200 200"&gt; &lt;line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#666" stroke-linecap="round" /&gt; &lt;line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#FC4D04" stroke-dasharray="170" :stroke-dashoffset="dashOffsetLine" stroke-linecap="round" id="lineInner" /&gt; &lt;circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" id="circle" style="transform: translateX(80px);" id="circle"/&gt; &lt;circle cx="100" cy="120" r="74" fill="none" stroke="#666" stroke-width="12" /&gt; &lt;circle cx="100" cy="120" r="74" fill="none" stroke="#FC4D04" stroke-width="12" stroke-dasharray="465" :stroke-dashoffset="dashOffsetCircle" stroke-linecap="round" /&gt; &lt;circle cx="100" cy="120" r="74" fill="none" stroke="green" stroke-width="12" stroke-dashoffset="464.5" stroke-dasharray="465" stroke-linecap="round" id="circleInner" style="transform:rotate(.8783783783783784rad); transform-origin: 100px 120px;"/&gt; &lt;/svg&gt; &lt;div class="action"&gt; &lt;div class="line"&gt; &lt;label for=""&gt;line: stroke-dashoffset = "{{dashOffsetLine}}"&lt;/label&gt; &lt;input type="range" name="points" min="0" value="{{dashOffsetLine}}" v-model="dashOffsetLine" max="170" step="1" @input="moveLine"&gt; &lt;/div&gt; &lt;div class="circle"&gt; &lt;label&gt;circle:stroke-dashoffset = "{{dashOffsetCircle}}"&lt;/label&gt; &lt;input type="range" name="points" min="0" value="{{dashOffsetCircle}}" v-model="dashOffsetCircle" max="465" step="1" @input="moveCircle"&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <p>对应的Vue代码:</p> <pre><code>let app = new Vue({ el: '#app', data () { return { dashOffsetCircle: 400, dashOffsetLine: 90, } }, methods: { moveLine: function (e){ let circleLine = document.getElementById('circle') let lineInnerLen = document.getElementById('lineInner').getTotalLength() circleLine.style.transform="translateX(" + (lineInnerLen - e.target.value) + "px)" }, moveCircle: function(e) { let circleInner = document.getElementById('circleInner') let circleInnerLen = Math.ceil(document.getElementById('circleInner').getTotalLength()) let arcLen = circleInnerLen - this.dashOffsetCircle let rad = arcLen / 74 circleInner.style.transform="rotate(" + rad + "rad)" } } }) </code></pre> <p>Vue代码写得比较拙逼,路过的大婶请多多指点。这个时候,你拖动<code>input</code>,改变其值时,就可以看到对应的效果。</p> <div style="margin-bottom: 20px;"><iframe id="jYgBYL" src="//codepen.io/airen/embed/jYgBYL?height=400&amp;theme-id=0&amp;slug-hash=jYgBYL&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>事实上除了使用<code>line</code>元素和<code>circle</code>元素之外,还可以使用<code>path</code>元素来实现类似的效果。感兴趣的同学,不仿使用<code>path</code>来做一个。如果做了,记得在下面的评论中与一起分享您的成果。</p> <h2>总结</h2> <p>这篇文章主要在上一节的基础上添加了一个网友想要的效果。就是在进度条上有一个圆点,能跟随进度条一起移动。其实现原理并不复杂。对于水平进度条,通过<code>translateX()</code>来改变其位移的位置,对于圆形进度条,则通过旋转来改变其位置。不管是哪一种,都需要动态的计算出他们的值,然后修改对应元素的样式。很多时候CSS和SVG的结合能让我们做出一些意想不到的效果。如果你感兴趣的话,不仿一试。</p> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/svg/create-progress-bar-with-svg-and-css.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/svg/create-progress-bar-with-svg-and-css.html</a></p> </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blogs/svg.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">SVG</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/svg-tutorial"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">SVG</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/vue"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div></div></div> Fri, 26 Jan 2018 16:34:15 +0000 Airen 2357 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/mobile/vw-layout-in-vue.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>有关于移动端的适配布局一直以来都是众说纷纭,对应的解决方案也是有很多种。在《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/mobile/lib-flexible-for-html5-layout.html">使用Flexible实现手淘H5页面的终端适配</a>》提出了Flexible的布局方案,随着<code>viewport</code>单位越来越受到众多浏览器的支持,因此在《<" href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/vw-for-layout.html">再聊移动端页面的适配</a>》一文中提出了<code>vw</code>来做移动端的适配问题。到目前为止不管是哪一种方案,都还存在一定的缺陷。言外之意,还没有哪一个方案是完美的。</p> <p>事实上真的不完美?其实不然。最近为了新项目中能更完美的使用<code>vw</code>来做移动端的适配。探讨出一种能解决不兼容<code>viewport</code>单位的方案。今天整理一下,与大家一起分享。如果方案中存在一定的缺陷,欢迎大家一起拍正。</p> <h2>准备工作</h2> <p>对于Flexible或者说<code>vw</code>的布局,其原理不在这篇文章进行阐述。如果你想追踪其中的原委,强烈建议你阅读早前整理的文章《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/mobile/lib-flexible-for-html5-layout.html">使用Flexible实现手淘H5页面的终端适配</a>》和《<" href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/vw-for-layout.html">再聊移动端页面的适配</a>》。</p> <blockquote> <p>说句题外话,由于Flexible的出现,也造成很多同学对<code>rem</code>的误解。正如当年大家对<code>div</code>的误解一样。也因此,大家都觉得<code>rem</code>是万能的,他能直接解决移动端的适配问题。事实并不是如此,至于为什么,我想大家应该去阅读<a href="http://xxysy.com/quot;//github.com/amfe/lib-flexible"><code>flexible.js</code></a>源码,我相信你会明白其中的原委。</p>" </blockquote> <p>回到我们今天要聊的主题,怎么实现<code>vw</code>的兼容问题。为了解决这个兼容问题,我将借助Vue官网提供的构建工程以及一些PostCSS插件来完成。在继续后面的内容之前,需要准备一些东西:</p> <ul> <li><a href="http://xxysy.com/quot;//nodejs.org/en/">NodeJs</a></li>" <li><a href="http://xxysy.com/quot;//www.npmjs.com/">NPM</a></li>" <li><a href="http://xxysy.com/quot;//webpack.js.org/">Webpack</a></li>" <li><a href="http://xxysy.com/quot;//github.com/vuejs/vue-cli">Vue-cli</a></li>" <li><a href="//github.com/postcss/postcss-import">postcss-import</a></li> <li><a href="//github.com/postcss/postcss-url">postcss-url</a></li> <li><a href="//github.com/yisibl/postcss-aspect-ratio-mini">postcss-aspect-ratio-mini</a></li> <li><a href="//github.com/MoOx/postcss-cssnext">postcss-cssnext</a></li> <li><a href="//github.com/postcss/autoprefixer">autoprefixer</a></li> <li><a href="//github.com/evrone/postcss-px-to-viewport">postcss-px-to-viewport</a></li> <li><a href="//github.com/jonathantneal/postcss-write-svg">postcss-write-svg</a></li> <li><a href="//github.com/ben-eb/cssnano">cssnano</a></li> <li><a href="//github.com/springuper/postcss-viewport-units">postcss-viewport-units</a></li> <li><a href="http://xxysy.com/quot;//github.com/rodneyrehm/viewport-units-buggyfill">Viewpor" Units Buggyfill</a></li> </ul> <p>对于这些起什么作用,先不阐述,后续我们会聊到上述的一些东西。</p> <h2>使用Vue-cli来构建项目</h2> <p>对于<a href="http://xxysy.com/quot;//nodejs.org/en/">NodeJs</a>、<" href="http://xxysy.com/quot;//www.npmjs.com/">NPM</a>和<" href="http://xxysy.com/quot;//webpack.js.org/">Webpack</a>相关介绍,大家可以查阅其对应的官网。这里默认你的系统环境已经安装好Nodejs、NPM和Webpack。我的系统目前使用的Node版本是<code>v9.4.0</code>;NPM的版本是<code>v5.6.0</code>。事实上,这些都并不重要。</p>" <h3>使用Vue-cli构建项目</h3> <p>为了不花太多的时间去深入的了解Webpack(Webpack对我而言,太蛋疼了),所以我直接使用Vue-cli来构建自己的项目,因为我一般使用Vue来做项目。如果你想深入的了解Webpack,建议你阅读下面的文章:</p> <ul> <li><a href="http://xxysy.com/quot;//doc.webpack-china.org/">Webpack文档</a></li>" <li><a href="http://xxysy.com/quot;//github.com/webpack-contrib/awesome-webpack">Awesom" Webpack</a></li> <li><a href="http://xxysy.com/quot;//segmentfault.com/a/1190000005995267">Webpac" 伟德1946网页版资源收集</a></li> <li><a href="http://xxysy.com/quot;//zhuanlan.zhihu.com/p/21702056">Vue+Webpack开发可复用的单页面富应用伟德1946网页版</a></li>" </ul> <p>接下来的内容,直接使用Vue官方提供的Vue-cli的构建工具来构建Vue项目。首先需要安装Vue-cli:</p> <pre><code>$ npm install -g vue-cli </code></pre> <p>全局先安装<a href="http://xxysy.com/quot;//github.com/vuejs/vue-cli">Vue-cli</a>,假设你安装好了Vue-cli。这样就可以使用它来构建项目:</p>" <pre><code>vue init webpack vw-layout </code></pre> <p>根据命令提示做相应的操作:</p> <p><img src="/sites/default/files/blogs/2018/1801/vw-layout-2.png" alt="" /></p> <p>进入到刚创建的<code>vw-layout</code>:</p> <pre><code>cd vw-layout </code></pre> <p>然后执行:</p> <pre><code>npm run dev </code></pre> <p>在浏览器执行<code>http://localhost:8080</code>,就可以看以默认的页面效果:</p> <p><img src="/sites/default/files/blogs/2018/1801/vw-layout-3.png" alt="" /></p> <blockquote> <p>以前的版本需要先执行<code>npm i</code>安装项目需要的依赖关系。现在新版本的可以免了。</p> </blockquote> <p>这时,可以看到的项目结构如下:</p> <p><img src="/sites/default/files/blogs/2018/1801/vw-layout-1.png" alt="使用Vue-cli构建项目" /></p> <h2>安装PostCSS插件</h2> <p>通过Vue-cli构建的项目,在项目的根目录下有一个<code>.postcssrc.js</code>,默认情况下已经有了:</p> <pre><code>module.exports = { "plugins": { "postcss-import": {}, "postcss-url": {}, "autoprefixer": {} } } </code></pre> <p>对应我们开头列的的PostCSS插件清单,现在已经具备了:</p> <ul> <li><a href="//github.com/postcss/postcss-import">postcss-import</a></li> <li><a href="//github.com/postcss/postcss-url">postcss-url</a></li> <li><a href="//github.com/postcss/autoprefixer">autoprefixer</a></li> </ul> <p>简单的说一下这几个插件。</p> <h3>postcss-import</h3> <p><a href="//github.com/postcss/postcss-import"><code>postcss-import</code></a>相关配置可以<a href="//github.com/postcss/postcss-import">点击这里</a>。目前使用的是默认配置。只在<code>.postcssrc.js</code>文件中引入了该插件。</p> <p><code>postcss-import</code>主要功有是解决<code>@import</code>引入路径问题。使用这个插件,可以让你很轻易的使用本地文件、<code>node_modules</code>或者<code>web_modules</code>的文件。这个插件配合<a href="//github.com/postcss/postcss-url"><code>postcss-url</code></a>让你引入文件变得更轻松。</p> <h3>postcss-url</h3> <p><a href="https://github.com/postcss/postcss-url"><code>postcss-url</code></a>相关配置可以点击<a href="https://github.com/postcss/postcss-url">这里</a>。该插件主要用来处理文件,比如图片文件、字体文件等引用路径的处理。</p> <p>在Vue项目中,<a href="https://github.com/vuejs/vue-loader"><code>vue-loader</code></a>已具有类似的功能,只需要配置中将<code>vue-loader</code>配置进去。</p> rel="nofollow" <h3>autoprefixer</h3> <p><a href="https://github.com/postcss/autoprefixer"><code>autoprefixer</code></a>插件是用来自动处理浏览器前缀的一个插件。如果你配置了<a href="https://github.com/MoOx/postcss-cssnext"><code>postcss-cssnext</code></a>,其中就已具备了<code>autoprefixer</code>的功能。在配置的时候,未显示的配置相关参数的话,表示使用的是<a href="https://github.com/ai/browserslist">Browserslist</a>指定的列表参数,你也可以像这样来指定<code>last rel="nofollow" 2 versions</code> 或者 <code>&gt; 5%</code>。</p> <p>如此一来,你在编码时不再需要考虑任何浏览器前缀的问题,可以专心撸码。这也是PostCSS最常用的一个插件之一。</p> <h3>其他插件</h3> <p>Vue-cli默认配置了上述三个PostCSS插件,但我们要完成<code>vw</code>的布局兼容方案,或者说让我们能更专心的撸码,还需要配置下面的几个PostCSS插件:</p> <ul> <li><a href="//github.com/yisibl/postcss-aspect-ratio-mini">postcss-aspect-ratio-mini</a></li> <li><a href="//github.com/evrone/postcss-px-to-viewport">postcss-px-to-viewport</a></li> <li><a href="//github.com/jonathantneal/postcss-write-svg">postcss-write-svg</a></li> <li><a href="//github.com/MoOx/postcss-cssnext">postcss-cssnext</a></li> <li><a href="//github.com/ben-eb/cssnano">cssnano</a></li> <li><a href="//github.com/springuper/postcss-viewport-units">postcss-viewport-units</a></li> </ul> <p>要使用这几个插件,先要进行安装:</p> <pre><code>npm i postcss-aspect-ratio-mini postcss-px-to-viewport postcss-write-svg postcss-cssnext postcss-viewport-units cssnano --S </code></pre> <p>安装成功之后,在项目根目录下的<code>package.json</code>文件中,可以看到新安装的依赖包:</p> <pre><code>"dependencies": { "cssnano": "^3.10.0", "postcss-aspect-ratio-mini": "0.0.2", "postcss-cssnext": "^3.1.0", "postcss-px-to-viewport": "0.0.3", "postcss-viewport-units": "^0.1.3", "postcss-write-svg": "^3.0.1", "vue": "^2.5.2", "vue-router": "^3.0.1" }, </code></pre> <p>接下来在<code>.postcssrc.js</code>文件对新安装的PostCSS插件进行配置:</p> <pre><code>module.exports = { "plugins": { "postcss-import": {}, "postcss-url": {}, "postcss-aspect-ratio-mini": {}, "postcss-write-svg": { utf8: false }, "postcss-cssnext": {}, "postcss-px-to-viewport": { viewportWidth: 750, // (Number) The width of the viewport. viewportHeight: 1334, // (Number) The height of the viewport. unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to. viewportUnit: 'vw', // (String) Expected units. selectorBlackList: ['.ignore', '.hairlines'], // (Array) The selectors to ignore and leave as px. minPixelValue: 1, // (Number) Set the minimum pixel value to replace. mediaQuery: false // (Boolean) Allow px to be converted in media queries. }, "postcss-viewport-units":{}, "cssnano": { preset: "advanced", autoprefixer: false, "postcss-zindex": false } } } </code></pre> <blockquote> <p><strong>特别声明:</strong>由于<code>cssnext</code>和<code>cssnano</code>都具有<code>autoprefixer</code>,事实上只需要一个,所以把默认的<code>autoprefixer</code>删除掉,然后把<code>cssnano</code>中的<code>autoprefixer</code>设置为<code>false</code>。对于其他的插件使用,稍后会简单的介绍。</p> </blockquote> <p>由于配置文件修改了,所以重新跑一下<code>npm run dev</code>。项目就可以正常看到了。接下来简单的介绍一下后面安装的几个插件的作用。</p> <h3>postcss-cssnext</h3> <p><a href="//github.com/MoOx/postcss-cssnext"><code>postcss-cssnext</code></a>其实就是<a href="//cssnext.io/">cssnext</a>。该插件可以让我们使用CSS未来的特性,其会对这些特性做相关的兼容性处理。其包含的特性主要有:</p> <p><img src="/sites/default/files/blogs/2018/1801/vw-layout-4.png" alt="postcss-cssnext" /></p> <p>有关于<code>cssnext</code>的每个特性的操作文档,可以<a href="//cssnext.io/features/#automatic-vendor-prefixes">点击这里浏览</a>。</p> <h3>cssnano</h3> <p><a href="//github.com/ben-eb/cssnano"><code>cssnano</code></a>主要用来压缩和清理CSS代码。在Webpack中,<code>cssnano</code>和<a href="//github.com/webpack-contrib/css-loader"><code>css-loader</code></a>捆绑在一起,所以不需要自己加载它。不过你也可以使用<a href="//github.com/postcss/postcss-loader"><code>postcss-loader</code></a>显式的使用<code>cssnano</code>。有关于<code>cssnano</code>的详细文档,可以<a href="//cssnano.co/guides/getting-started/">点击这里</a>获取。</p> <p>在<code>cssnano</code>的配置中,使用了<code>preset: "advanced"</code>,所以我们需要另外安装:</p> <pre><code>npm i cssnano-preset-advanced --save-dev </code></pre> <p><code>cssnano</code>集成了一些<a href="//cssnano.co/guides/optimisations/">其他的PostCSS插件</a>,如果你想禁用<code>cssnano</code>中的某个插件的时候,可以像下面这样操作:</p> <pre><code>"cssnano": { autoprefixer: false, "postcss-zindex": false } </code></pre> <p>上面的代码把<code>autoprefixer</code>和<code>postcss-zindex</code>禁掉了。前者是有重复调用,后者是一个讨厌的东东。只要启用了这个插件,<code>z-index</code>的值就会重置为<code>1</code>。这是一个天坑,<strong>千万记得将<code>postcss-zindex</code>设置为<code>false</code></strong>。</p> <h3>postcss-px-to-viewport</h3> <p><a href="https://github.com/evrone/postcss-px-to-viewport"><code>postcss-px-to-viewport</code></a>插件主要用来把<code>px</code>单位转换为<code>vw</code>、<code>vh</code>、<code>vmin</code>或者<code>vmax</code>这样的视窗单位,也是<a href="https://www.atatech.org/articles/87388"><code>vw</code>适配方案</a>的核心插件之一。</p> rel="nofollow" <p>在配置中需要配置相关的几个关键参数:</p> <pre><code>"postcss-px-to-viewport": { viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750 viewportHeight: 1334, // 视窗的高度,根据750设备的宽度来指定,一般指定1334,也可以不配置 unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除) viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw selectorBlackList: ['.ignore', '.hairlines'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名 minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值 mediaQuery: false // 允许在媒体查询中转换`px` } </code></pre> <p>目前出视觉设计稿,我们都是使用<code>750px</code>宽度的,那么<code>100vw = 750px</code>,即<code>1vw = 7.5px</code>。那么我们可以根据设计图上的<code>px</code>值直接转换成对应的<code>vw</code>值。在实际撸码过程,不需要进行任何的计算,直接在代码中写<code>px</code>,比如:</p> <pre><code>.test { border: .5px solid black; border-bottom-width: 4px; font-size: 14px; line-height: 20px; position: relative; } [w-188-246] { width: 188px; } </code></pre> <p>编译出来的CSS:</p> <pre><code>.test { border: .5px solid #000; border-bottom-width: .533vw; font-size: 1.867vw; line-height: 2.667vw; position: relative; } [w-188-246] { width: 25.067vw; } </code></pre> <p>在不想要把<code>px</code>转换为<code>vw</code>的时候,首先在对应的元素(<code>html</code>)中添加配置中指定的类名<code>.ignore</code>或<code>.hairlines</code>(<code>.hairlines</code>一般用于设置<code>border-width:0.5px</code>的元素中):</p> <pre><code>&lt;div class="box ignore"&gt;&lt;/div&gt; </code></pre> <p>写CSS的时候:</p> <pre><code>.ignore { margin: 10px; background-color: red; } .box { width: 180px; height: 300px; } .hairlines { border-bottom: 0.5px solid red; } </code></pre> <p>编译出来的CSS:</p> <pre><code>.box { width: 24vw; height: 40vw; } .ignore { margin: 10px; /*.box元素中带有.ignore类名,在这个类名写的`px`不会被转换*/ background-color: red; } .hairlines { border-bottom: 0.5px solid red; } </code></pre> <p>上面解决了<code>px</code>到<code>vw</code>的转换计算。那么在哪些地方可以使用<code>vw</code>来适配我们的页面。根据相关的测试:</p> <ul> <li>容器适配,可以使用<code>vw</code></li> <li>文本的适配,可以使用<code>vw</code></li> <li>大于<code>1px</code>的边框、圆角、阴影都可以使用<code>vw</code></li> <li>内距和外距,可以使用<code>vw</code></li> </ul> <h3>postcss-aspect-ratio-mini</h3> <p><a href="//github.com/yisibl/postcss-aspect-ratio-mini"><code>postcss-aspect-ratio-mini</code></a>主要用来处理元素容器宽高比。在实际使用的时候,具有一个默认的结构</p> <pre><code>&lt;div aspectratio&gt; &lt;div aspectratio-content&gt;&lt;/div&gt; &lt;/div&gt; </code></pre> <p>在实际使用的时候,你可以把自定义属性<code>aspectratio</code>和<code>aspectratio-content</code>换成相应的类名,比如:</p> <pre><code>&lt;div class="aspectratio"&gt; &lt;div class="aspectratio-content"&gt;&lt;/div&gt; &lt;/div&gt; </code></pre> <p>我个人比较喜欢用自定义属性,它和类名所起的作用是同等的。结构定义之后,需要在你的样式文件中添加一个统一的宽度比默认属性:</p> <pre><code>[aspectratio] { position: relative; } [aspectratio]::before { content: ''; display: block; width: 1px; margin-left: -1px; height: 0; } [aspectratio-content] { position: absolute; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; } </code></pre> <p>如果我们想要做一个<code>188:246</code>(<code>188</code>是容器宽度,<code>246</code>是容器高度)这样的比例容器,只需要这样使用:</p> <pre><code>[w-188-246] { aspect-ratio: '188:246'; } </code></pre> <p><strong>有一点需要特别注意:<code>aspect-ratio</code>属性不能和其他属性写在一起,否则编译出来的属性只会留下<code>aspect-ratio</code>的值,比如:</strong></p> <pre><code>&lt;div aspectratio w-188-246 class="color"&gt;&lt;/div&gt; </code></pre> <p>编译前的CSS如下:</p> <pre><code>[w-188-246] { width: 188px; background-color: red; aspect-ratio: '188:246'; } </code></pre> <p>编译之后:</p> <pre><code>[w-188-246]:before { padding-top: 130.85106382978725%; } </code></pre> <p>主要是因为在插件中做了相应的处理,不在每次调用<code>aspect-ratio</code>时,生成前面指定的默认样式代码,这样代码没那么冗余。所以在使用的时候,需要把<code>width</code>和<code>background-color</code>分开来写:</p> <pre><code>[w-188-246] { width: 188px; background-color: red; } [w-188-246] { aspect-ratio: '188:246'; } </code></pre> <p>这个时候,编译出来的CSS就正常了:</p> <pre><code>[w-188-246] { width: 25.067vw; background-color: red; } [w-188-246]:before { padding-top: 130.85106382978725%; } </code></pre> <p>有关于宽高比相关的详细介绍,如果大家感兴趣的话,可以阅读下面相关的文章:</p> <ul> <li><a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/aspect-ratio.html">CSS实现长宽比的几种方案</a></li> <li><a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/aspect-ratio-boxes.html">容器长宽比</a></li> <li><a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/experiments-in-fixed-aspect-ratios.html">Web中如何实现纵横比</a></li> <li><a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/css-polyfluidsizing-using-calc-vw-breakpoints-and-linear-equations.html">实现精准的流体排版原理</a></li> </ul> <blockquote> <p>目前采用PostCSS插件只是一个过渡阶段,在将来我们可以直接在CSS中使用<code>aspect-ratio</code>属性来实现长宽比。</p> </blockquote> <h3>postcss-write-svg</h3> <p><a href="//github.com/jonathantneal/postcss-write-svg"><code>postcss-write-svg</code></a>插件主要用来处理移动端<code>1px</code>的解决方案。该插件主要使用的是<code>border-image</code>和<code>background</code>来做<code>1px</code>的相关处理。比如:</p> <pre><code>@svg 1px-border { height: 2px; @rect { fill: var(--color, black); width: 100%; height: 50%; } } .example { border: 1px solid transparent; border-image: svg(1px-border param(--color #00b1ff)) 2 2 stretch; } </code></pre> <p>编译出来的CSS:</p> <pre><code>.example { border: 1px solid transparent; border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch; } </code></pre> <p>上面演示的是使用<code>border-image</code>方式,除此之外还可以使用<code>background-image</code>来实现。比如:</p> <pre><code>@svg square { @rect { fill: var(--color, black); width: 100%; height: 100%; } } #example { background: white svg(square param(--color #00b1ff)); } </code></pre> <p>编译出来就是:</p> <pre><code>#example { background: white url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%2300b1ff' width='100%25' height='100%25'/%3E%3C/svg%3E"); } </code></pre> <p>解决<code>1px</code>的方案除了这个插件之外,还有其他的方法。可以阅读前期整理的《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/fix-1px-for-retina.html">再谈Retina下<code>1px</code>的解决方案</a>》一文。</p> <blockquote> <p><strong>特别声明:</strong>由于有一些低端机对<code>border-image</code>支持度不够友好,个人建议你使用<code>background-image</code>的这个方案。</p> </blockquote> <h3>CSS Modules</h3> <p>Vue中的<code>vue-loader</code>已经集成了<a href="//github.com/css-modules/css-modules">CSS Modules</a>的功能,个人建议在项目中开始使用CSS Modules。特别是在Vue和React的项目中,CSS Modules具有很强的优势和灵活性。建议看看CSS In JS相关的资料。在Vue中,使用CSS Modules的相关文档可以阅读Vue官方提供的文档《<a href="//vue-loader.vuejs.org/en/features/css-modules.html">CSS Modules</a>》。</p> <h3>postcss-viewport-units</h3> <p><a href="//github.com/springuper/postcss-viewport-units"><code>postcss-viewport-units</code></a>插件主要是给CSS的属性添加<code>content</code>的属性,配合<a href="http://xxysy.com/quot;//github.com/rodneyrehm/viewport-units-buggyfill"><code>viewport-units-buggyfill</code></a>库给<code>vw</code>、<code>vh</code>、<code>vmin</code>和<code>vmax</code>做适配的操作。</p>" <p>这是实现<code>vw</code>布局必不可少的一个插件,因为少了这个插件,这将是一件痛苦的事情。后面你就清楚。</p> <p>到此为止,有关于所需要的PostCSS已配置完。并且简单的介绍了各个插件的作用,至于详细的文档和使用,可以参阅对应插件的官方文档。</p> <h2>vw兼容方案</h2> <p>在《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/vw-for-layout.html">再聊移动端页面的适配</a>》一文中,详细介绍了,怎么使用<code>vw</code>来实现移动端的适配布局。这里不做详细的介绍。建议你花点时间阅读这篇文章。</p> <p>先把未做兼容处理的示例二维码贴一个:</p> <p><img src="/sites/default/files/blogs/2017/1707/vw-layout-1.png" alt="" /></p> <p>你可以使用手淘App、优酷APP、各终端自带的浏览器、UC浏览器、QQ浏览器、Safari浏览器和Chrome浏览器扫描上面的二维码,您看到相应的效果:</p> <p><img src="/sites/default/files/blogs/2017/1707/vw-layout-2.png" alt="" /></p> <p>但还有不支持的,比如下表中的<code>No</code>,表示的就是不支持</p> <table> <thead> <tr> <th>品牌</th> <th>型号</th> <th>系统版本</th> <th>分辨率</th> <th>屏幕尺寸</th> <th>手淘APP</th> <th>优酷APP</th> <th>原生浏览器</th> <th>QQ浏览器</th> <th>UC浏览器</th> <th>Chrome浏览器</th> </tr> </thead> <tbody> <tr> <td>华为</td> <td>Mate9</td> <td>Android7.0</td> <td>1080 x 1920</td> <td>5英寸</td> <td>Yes</td> <td>Yes</td> <td>No</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> </tr> <tr> <td>华为</td> <td>Mate7</td> <td>Android4.2</td> <td>1080 x 1920</td> <td>5.2英寸</td> <td>Yes</td> <td>Yes</td> <td>No</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> </tr> <tr> <td>魅族</td> <td>Mx4 (M460 移动4G)</td> <td>Android4.4.2</td> <td>1152 x 1920</td> <td>5.36英寸</td> <td>Yes</td> <td>No</td> <td>No</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> </tr> <tr> <td>Oppo</td> <td>R7007</td> <td>Android4.3</td> <td>1280 x 720</td> <td>5英寸</td> <td>Yes</td> <td>No</td> <td>No</td> <td>Yes</td> <td>Yes</td> <td>No</td> </tr> <tr> <td>三星</td> <td>N9008 (Galaxy Note3)</td> <td>Android4.4.2</td> <td>1080 x 1920</td> <td>5.7英寸</td> <td>Yes</td> <td>No</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> </tr> <tr> <td>华硕</td> <td>ZenFone5(x86)</td> <td>Android4.3</td> <td>720 x 280</td> <td>5英寸</td> <td>No</td> <td>No</td> <td>No</td> <td>Yes</td> <td>No</td> <td>No</td> </tr> </tbody> </table> <p>正因如此,很多同学都不敢尝这个螃蟹。害怕去处理兼容性的处理。不过不要紧,今天我把最终的解决方案告诉你。</p> <p>最终的解决方案,就是使用<code>viewport</code>的polyfill:<a href="http://xxysy.com/quot;//github.com/rodneyrehm/viewport-units-buggyfill">Viewpor" Units Buggyfill</a>。使用<code>viewport-units-buggyfill</code>主要分以下几步走:</p> <h3>引入JavaScript文件</h3> <p><code>viewport-units-buggyfill</code>主要有两个JavaScript文件:<code>viewport-units-buggyfill.js</code>和<code>viewport-units-buggyfill.hacks.js</code>。你只需要在你的HTML文件中引入这两个文件。比如在Vue项目中的<code>index.html</code>引入它们:</p> <pre><code>&lt;script src="//g.alicdn.com/fdilab/lib3rd/viewport-units-buggyfill/0.6.2/??viewport-units-buggyfill.hacks.min.js,viewport-units-buggyfill.min.js"&gt;&lt;/script&gt; </code></pre> <p>你也可以使用其他的在线CDN地址,也可将这两个文件合并压缩成一个<code>.js</code>文件。这主要看你自己的兴趣了。</p> <p>第二步,在HTML文件中调用<code>viewport-units-buggyfill</code>,比如:</p> <pre><code>&lt;script&gt; window.onload = function () { window.viewportUnitsBuggyfill.init({ hacks: window.viewportUnitsBuggyfillHacks }); } &lt;/script&gt; </code></pre> <p>为了你Demo的时候能获取对应机型相关的参数,我在示例中添加了一段额外的代码,估计会让你有点烦:</p> <pre><code>&lt;script&gt; window.onload = function () { window.viewportUnitsBuggyfill.init({ hacks: window.viewportUnitsBuggyfillHacks }); var winDPI = window.devicePixelRatio; var uAgent = window.navigator.userAgent; var screenHeight = window.screen.height; var screenWidth = window.screen.width; var winWidth = window.innerWidth; var winHeight = window.innerHeight; alert( "Windows DPI:" + winDPI + ";\ruAgent:" + uAgent + ";\rScreen Width:" + screenWidth + ";\rScreen Height:" + screenHeight + ";\rWindow Width:" + winWidth + ";\rWindow Height:" + winHeight ) } &lt;/script&gt; </code></pre> <p>具体的使用。在你的CSS中,只要使用到了<code>viewport</code>的单位(<code>vw</code>、<code>vh</code>、<code>vmin</code>或<code>vmax</code> )地方,需要在样式中添加<code>content</code>:</p> <pre><code>.my-viewport-units-using-thingie { width: 50vmin; height: 50vmax; top: calc(50vh - 100px); left: calc(50vw - 100px); /* hack to engage viewport-units-buggyfill */ content: 'viewport-units-buggyfill; width: 50vmin; height: 50vmax; top: calc(50vh - 100px); left: calc(50vw - 100px);'; } </code></pre> <p>这可能会令你感到恶心,而且我们不可能每次写<code>vw</code>都去人肉的计算。特别是在我们的这个场景中,咱们使用了<a href="https://github.com/evrone/postcss-px-to-viewport"><code>postcss-px-to-viewport</code></a>这个插件来转换<code>vw</code>,更无法让我们人肉的去添加<code>content</code>内容。</p> <p><strong>这个时候就需要前面提到的<a href="//github.com/springuper/postcss-viewport-units"><code>postcss-viewport-units</code></a>插件</strong>。这个插件将让你无需关注<code>content</code>的内容,插件会自动帮你处理。比如插件处理后的代码:</p> <p><img src="/sites/default/files/blogs/2018/1801/vw-layout-5.png" alt="" /></p> <p><a href="http://xxysy.com/quot;//github.com/rodneyrehm/viewport-units-buggyfill">Viewpor" Units Buggyfill</a>还提供了其他的功能。详细的这里不阐述了。但是<code>content</code>也会引起一定的副作用。比如<code>img</code>和伪元素<code>::before</code>(<code>:before</code>)或<code>::after</code>(<code>:after</code>)。在<code>img</code>中<code>content</code>会引起部分浏览器下,图片不会显示。这个时候需要全局添加:</p> <pre><code>img { content: normal !important; } </code></pre> <p>而对于<code>::after</code>之类的,就算是里面使用了<code>vw</code>单位,<a href="http://xxysy.com/quot;//github.com/rodneyrehm/viewport-units-buggyfill">Viewpor" Units Buggyfill</a>对其并不会起作用。比如:</p> <pre><code>// 编译前 .after { content: 'after content'; display: block; width: 100px; height: 20px; background: green; } // 编译后 .after[data-v-469af010] { content: "after content"; display: block; width: 13.333vw; height: 2.667vw; background: green; } </code></pre> <p>这个时候我们需要通过添加额外的标签来替代伪元素(这个情景我没有测试到,后面自己亲测一下)。</p> <p>到了这个时候,你就不需要再担心兼容问题了。比如下面这个示例:</p> <p><img src="/sites/default/files/blogs/2018/1801/vw-layout-6.png" alt="" /></p> <p>请用你的手机,不管什么APP扫一扫,你就可以看到效果。(小心弹框哟),如果你发现了还是有问题,请把弹出来的信息截图发给我。</p> <p>如查你想看看别的机型效果,可以点击<a href="http://xxysy.com/quot;/sites/default/files/blogs/2018/1801/IMG_4563.JPG">这里</a>、<" href="http://xxysy.com/quot;/sites/default/files/blogs/2018/1801/IMG_4566.JPG">这里</a>、<" href="http://xxysy.com/quot;/sites/default/files/blogs/2018/1801/IMG_4576.JPG">这里</a>、还有<" href="http://xxysy.com/quot;/sites/default/files/blogs/2018/1801/IMG_4578.JPG">这里</a>。整个示例的源码,可以<" href="http://xxysy.com/quot;/sites/default/files/blogs/2018/1801/vw-layout.zip">点击这里下载</a>。</p>" <blockquote> <p>如果你下载了示你源码,先要确认你的系统环境能跑Vue的项目,然后<a href="http://xxysy.com/quot;/sites/default/files/blogs/2018/1801/vw-layout.zip">下载下来之后</a>,解压缩,接着运行<code>np" i</code>,再运行<code>npm run dev</code>,你就可以看到效果了。</p> </blockquote> <h2>总结</h2> <p>如果你看到这里了,希望这篇文章对你有所帮助。能帮助你解决项目中的实际问题,让你不再担心移动端的适配问题。当然更希望的是你在实际的项目中用起这个方案,把碰到的问题及时反馈给偶。如果你有更好的方案,欢迎在下面的评论中与我们一起分享。</p> <div class="blog-author media"><a class="media-object" href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank"><img src="/sites/default/files/blogs/author/airen.jpg"></a><div class="media-body"><h3 class="media-heading"><a href="http://xxysy.com/quot;//weibo.com/伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】"" target="_blank">大漠</a></h3><div class="media-des">常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/book-comment.html"" target="_blank">图解CSS3:核心技术与案例实战</a>》。</div></div></div> <p>如需转载,烦请注明出处:<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/mobile/vw-layout-in-vue.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/mobile/vw-layout-in-vue.html</a></p> rel="nofollow" </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/mobile"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">mobile</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/484.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">vw</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/180.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Layout</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/130.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">布局</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/vue"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/mobile"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">mobile</a></div></div></div> Wed, 24 Jan 2018 17:43:24 +0000 Airen 2356 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/svg/creating-a-star-to-heart-animation-with-svg-and-vanilla-javascript.html <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>在<a href="//css-tricks.com/emulating-css-timing-functions-javascript/">我写的这篇文章中</a>, 讲述了如何用Vanilla JavaScript使动画顺滑的从一种状态过渡到另一种。最好先看下那篇文章,因为在这篇文章中我们要用到一些那篇文章中讲过的内容。例如例子的演示、各种时间函数的公式、当从结束状态过渡到初始状态时不使时间函数倒转过来。都在那篇文章中做了详细讲解。</p> <p>在最后的例子中,通过改变绘制嘴形的<code>path</code>的属性<code>d</code>,我们得到了从悲伤的嘴变高兴的嘴的效果。</p> <p>更高水平的控制路径数据能够带给我们更有趣的效果,例如星形变心形。</p> <p><img src="/sites/default/files/blogs/2018/1801/star_to_heart.gif" alt="" /></p> <p>这是我们要实现的星形变心形的动画效果。</p> <h2>思路</h2> <p>它们都是由五个<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/EKLNvZ">三次贝塞尔曲线</a>构成。下边的互动演示展示了每条曲线以及这些曲线相连接的点。点击任意曲线或连接点可以看到两个图形的曲线是如何相对应的。</p>" <div style="margin-bottom: 20px;"><iframe id="PEgKKO" src="//codepen.io/airen/embed/PEgKKO?height=400&amp;theme-id=0&amp;slug-hash=PEgKKO&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>可以看出所有曲线都是由三次贝塞尔曲线创建的。即使其中一些曲线的两个控制点重叠了。</p> <p>构成星形和心形的形状都是极简且不符合实际的。但它们可以做到。</p> <h2>初始代码</h2> <p>从表情动画的例子中可以看出, 我通常选择用 Pug(译:即Jade,一种模版引擎) 生成这类形状。但在这里,由于生成的路径数据还将由JavaScript处理过渡效果。包括计算坐标以及将这些坐标放入属性<code>d</code> 。所以使用JavaScript来做所有的这些是最好的选择。</p> <p>这意味着我们不必写很多标签:</p> <pre><code>&lt;svg&gt; &lt;path id='shape'/&gt; &lt;/svg&gt; </code></pre> <p>JavaScript中,我们首先获得元素 <code>svg</code> 和元素 <code>path</code> 。<code>path</code> 是那个星形变心形再变回星形的形状。然后,我们给元素 <code>svg</code> 设置<code>viewBox</code>属性,使得 SVG 沿两个轴的尺寸相等,并且坐标轴的原点<code>(0,0)</code>在 SVG 正中间。这意味着,当<code>viewBox</code>的尺寸值为<code>D</code> 时,它的左上角坐标为<code>(-.5*D,-.5*D)</code>。最后,这个也很重要,就是创建一个对象来存储过渡的初始和最终状态,以及一个将我们想要的值设置给 SVG 图形属性的方法。</p> <pre><code>const _SVG = document.querySelector('svg'), _SHAPE = document.getElementById('shape'), D = 1000, O = { ini: {}, fin: {}, afn: {} }; (function init() { _SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' ')); })(); </code></pre> <p>现在我们把这事解决了,可以开始更有趣的部分了!</p> <h2>图形的几何绘制</h2> <p>我们用终点和控制点的初始坐标来绘制星形,用它们的最终坐标来绘制心形。 每个坐标的过渡范围是它的初始值与最终值之间的差值。在这个例子中,当星形向心形转换时,我们会转动(<code>rotate</code>)它,因为我们想让星形的角朝上。我们还会改变填充(<code>fill</code>),从金色的星形变成深红色的心形。</p> <p>那么,我们怎么能获得这两个图形的终点和控制点的坐标呢?</p> <h3>星形</h3> <p>在星形的例子中,我们先从一个正五角星形开始。我们的曲线(译:构成星形每个角的曲线)终点落在正五角星形边的交叉点上,我们把正五角星形的顶点作为控制点。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 2500" xmlns="http://www.w3.org/2000/svg" class="live1"> <style> .live1 * { fill: none; stroke: #333; stroke-width: 10 } .live1 style + * { stroke: #f90; stroke-width: 20 } .live1 [r] { fill: #f90 } </style> <path d="M222,305C0,988,0,988 -222,305C-940,305,-940,305 -359,-117C-581,-799,-581,-799 -0,-377C581,-799,581,-799 359,-117C940,305,940,305 222,305"></path> <path d="M0,988,-581,-799,940,305,-940,305,581,-799Z"></path> <circle cy="988" r="18"></circle> <circle cx="-222" cy="305" r="25"></circle> <circle cx="-940" cy="305" r="18"></circle> <circle cx="-359" cy="-117" r="25"></circle> <circle cx="-581" cy="-799" r="18"></circle> <circle cy="-377" r="25"></circle> <circle cx="581" cy="-799" r="18"></circle> <circle cx="359" cy="-117" r="25"></circle> <circle cx="940" cy="305" r="18"></circle> <circle cx="222" cy="305" r="25"></circle> </svg> </div> <p><em>五个三次贝塞尔曲线的终点和控制点用黄点标识在了正五角星形的顶点和边的交叉点上(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/YrebKb?editors=1000">Live</a>)</em>。</p>" <p><a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/ybVEzP/?editors=1100">直接给定正五角星形外接圆的半径</a>(或者直径)就可以获得五角星形的顶点。也就是我们" SVG 的<code>viewBox</code> 设定的尺寸(简单起见,在这种情况下我们不考虑高填密)。但是如何获得他们的交叉点呢?</p> <p>首先,我们先看下边的说明图。注意图中正五角星形中间高亮标注的小五边形。小五边形的顶点与正五角星形边的交叉点是重合的。这个小五边形显然是个正五边形(译:五个边的长度相等)。这个小正五边形的内切圆和内径跟正五角星形的是同一个。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 2500" xmlns="http://www.w3.org/2000/svg" class="live2"> <style> .live2 * { fill: none; stroke: #333; stroke-width: 10 } .live2 [r='305'] { opacity: .5 } .live2 [r='305'], .live2 [d] + [d] { stroke-width: 20 } .live2 [d] + [d] { stroke: #b53 } .live2 [r] + [d] { stroke: #95a; stroke-width: 15 } .live2 [r] ~ [r] { fill: #f90 } </style> <path d="M0,988 -581,-799 940,305 -940,305 581,-799z"></path> <path d="M0,-377 -359,-117 -222,305 222,305 359,-117z"></path> <circle r="305"></circle> <path d="M0,305 0 0 -290,94M-179,-247 0 0 179,-247M290,94 0 0"></path> <circle cy="988" r="18"></circle> <circle cy="305" r="18"></circle> <circle cy="-377" r="25"></circle> <circle cx="-581" cy="-799" r="18"></circle> <circle cx="-290" cy="94" r="18"></circle> <circle cx="-359" cy="-117" r="25"></circle> <circle cx="940" cy="305" r="18"></circle> <circle cx="-179" cy="-247" r="18"></circle> <circle cx="-222" cy="305" r="25"></circle> <circle cx="-940" cy="305" r="18"></circle> <circle cx="179" cy="-247" r="18"></circle> <circle cx="222" cy="305" r="25"></circle> <circle cx="581" cy="-799" r="18"></circle> <circle cx="290" cy="94" r="18"></circle> <circle cx="359" cy="-117" r="25"></circle> <circle r="25"></circle> </svg> </div> <p><em>正五角星形和内部的正五边形的内切圆是同一个 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/wrmKxY?editors=1000">Live</a>)。</em></p>" <p>因此,如果我们计算出正五角星形的内径,那么也就获得了正五边形的内径。这个内径和<a href="http://xxysy.com/quot;//mathworld.wolfram.com/CentralAngle.html">圆心角</a>" 一起对应正五边形的边。根据这个我们就可以获得正五边形的<a href="http://xxysy.com/quot;//mathworld.wolfram.com/Circumcircle.html">外接圆半径</a>" 。这样就可以倒推出正五边形顶点的坐标。这些点正是正五角星形边的交叉点,也就是星形五个三次贝塞尔曲线的终点。</p> <p>我们的正五角星形可以用<a href="http://xxysy.com/quot;//en.wikipedia.org/wiki/Schl%C3%A4fli_symbol#Regular_polygons_.28plane.29">拓扑符号</a>" <code>{5/2}</code>来表示。也就是说,正五角星形有<code>5</code>个顶点。这5个顶点均匀分布在它的外接圆上,间隔是 <code>360°/5 = 72°</code>。我们从第一个点开始,跳过紧挨着的下一个点,连接到紧挨着的第二个点(这就是符号<code>{5/2}</code>中<code>2</code>的含义;<code>1</code> 代表的意思是连接到第一个点,不跳过任何点,构成一个五边形)。照这样一直连接,就可以画出正五角星形了。</p> <p>在下边的演示中,点击五边形或者五角星形按钮,查看它们是怎样被绘制的。</p> <div style="margin-bottom: 20px;"><iframe id="VyNMey" src="//codepen.io/airen/embed/VyNMey?height=400&amp;theme-id=0&amp;slug-hash=VyNMey&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>这样,我们得到正五角星形的边所对应的的圆心角是正五边形的边所对应的圆心角的二倍。那么正五边形是<code>1 * (360°/5) = 1 * 72° = 72°</code> (或者<code>1 * (2 * π / 5)</code>弧度),那正五角星形就是<code>2 * (360° / 5) = 2 * 72° = 144°</code> (<code>2 * (2 * π / 5)</code>弧度)。通常,一个用拓扑符号表示为<code>{p,q}</code>的正多边形,它的一个边所对应的圆心角就是 <code>q * (360° / p)</code>(<code>q * (2 * π / p)</code> 弧度)。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1250 -1250 5000 2500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="live3"> <style> svg.live3 { font: 150px consolas, monaco, monospace } .live3 [d], .live3 [r], .live3 line { fill: none; stroke: #333; stroke-width: 10 } .live3 #e * { fill: #333; stroke: none } .live3 [id] [d] { marker-end: url(#e) } .live3 [d] + text { font: 1em times new roman, serif } .live3 #u [r] { stroke: #ccc } .live3 g ~ [d] { stroke: #f90; stroke-width: 15 } .live3 g + [d] { fill: rgba(255, 238, 51, .7) } .live3 use ~ [d] { stroke: #95a } .live3 #e + [d] { stroke: #777 } .live3 [d] + [d] { stroke: #b53; stroke-width: 20 } .live3 #v [r] { fill: #f90 } .live3 [r] ~ [x] { font-style: italic } .live3 tspan { font-size: .5em } </style> <marker id="e" markerWidth="10" markerHeight="10" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <path d="M2805,940 1701,581 1701,-581 2805,-940 3488,0 M-799,581 305,-940 305,940 -799,-581 988,0"></path> <g id="u"> <circle r="988"></circle> <text x="-113" y="-30">O</text> <path d="M-1228 0H1228"></path> <text x="1153.125" y="-75">x</text> <path d="M0-1228V1228"></path> <text x="60" y="1198">y</text> <line></line> <text x="988" y="-30">V<tspan>0</tspan></text> </g> <path d="M2500 0 2747 0 A247 247 0 0 1 2576 235 M0 0 247 0 A247 247 0 0 1 -200 145"></path> <use xlink:href="http://xxysy.com/quot;#u"" x="2500"></use> <path d="M3488 0H2500L2805,940M988 0H0L-799,581"></path> <path d="M3488,0 2805,940 M988,0 -799,581"></path> <g id="v"> <text x="3488" y="135">0°</text> <text x="2730" y="1090">72°</text> <text x="1364" y="712">144°</text> <text x="1401" y="-619">216°</text> <text x="2693" y="-996">288°</text> <circle cx="3488" cy="0" r="25"></circle> <circle cx="2805" cy="940" r="25"></circle> <circle cx="1701" cy="581" r="25"></circle> <circle cx="1701" cy="-581" r="25"></circle> <circle cx="2805" cy="-940" r="25"></circle> <circle cx="2500" r="18"></circle> </g> <use xlink:href="http://xxysy.com/quot;#v"" x="-2500"></use> </svg> </div> <p><em>正多边形的一条边所对应的圆心角:正五角星形(左,<code>144°</code>)vs 正五边形(右,``72°`)(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/KXeqzy?editors=1000">Live</a>)。</em></p>" <p>已知正五角星形外接圆半径,也就是的<code>viewBox</code>尺寸。那么,已知直角三角形斜边的长(即正五角星形外接圆的半径)和锐角的度数(正五角星形一条边所对应的角度的一半),这意味着我们可以算出正五角星形的内径(这个内径与正五角星形内部的小正五边形的内径相等)。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 2500" xmlns="http://www.w3.org/2000/svg" class="live4"> <style>svg.live4 { fill: #333; font: 150px consolas, monaco, monospace } svg.live4 > [d], [r], .live4 line { fill: none; stroke: #ccc; stroke-width: 10 } .live4 #f *, .live4 [d] + [x] { fill: #ccc } .live4 [x] + [d] { marker-end: url(#f) } .live4 [d] + [x] { font: 1em times new roman, serif } .live4 [d] ~ [x] { font-style: italic } .live4 [r] + * + [d] { fill: hsla(0, 0%, 83%, .7) } .live4 line, .live4 [y2] ~ [d] { stroke: #95a; stroke-width: 15 } .live4 [r] + [d] { stroke: #999 } .live4 line ~ [r], .live4 [y2] ~ [d] { stroke: #333 } .live4 line ~ [r] { fill: #f90 } .live4 [d] + * + [d] { stroke: #cb0; fill: rgba(255,238,51,.7) } .live4 [y2] { stroke: #b9c } .live4 [d] + [d] { stroke: #d97 } .live4 [y2] + * + [d] { stroke: #b53 } .live4 [cx] + line { stroke: #f90; stroke-width: 20 } .live4 tspan { font-size: .5em } </style> <marker id="f" markerWidth="10" markerHeight="10" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="988" y="120">0°</text> <text x="230" y="1090">72°</text> <text x="-1080" y="731">144°</text> <path d="M-1230 0H1230"></path> <text x="1155" y="-75">x</text> <text x="-113" y="-30">O</text> <text x="19" y="440">M</text> <path d="M0-1230V1230"></path> <text x="60" y="1200">y</text> <circle r="305"></circle> <path d="M-799,581 305,-940 305,940 -799,-581 988,0M305,940 94,290"></path> <path d="M988,0 0 0 94,290"></path> <circle r="988"></circle> <path d="M0 0 153 0A153 153 0 0 1 -124 90"></path> <line x2="988"></line> <line x2="-799" y2="581"></line> <path d="M69,214 145,189 170,265"></path> <path d="M988,0 94,290"></path> <path d="M94,290 -799,581"></path> <circle cx="988" r="25"></circle> <circle cx="-799" cy="581" r="18"></circle> <circle cx="305" cy="-940" r="18"></circle> <circle cx="305" cy="940" r="18"></circle> <circle cx="-799" cy="-581" r="18"></circle> <line x2="94" y2="290"></line> <circle cx="119.5" cy="239.5" r="5"></circle> <circle r="25"></circle> <circle cx="94" cy="290" r="25"></circle> <text x="988" y="-30">V<tspan>0</tspan></text> <text x="-1024" y="581">V<tspan>1</tspan></text> </svg> </div> <p><em>通过直角,可以计算出正五角星形的内径长。这个直角的斜边等于正五角星形外接圆半径,其中一个锐角的角度等于正五角星形一条边所对应的角度的一半 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/QqBBQg?editors=1000">Live</a>)。</em></p>" <p>圆心角一半的余弦等于五角星形的内径比外接圆半径。就可以得出,五角星形的内径等于外接圆半径乘以这个余弦值。</p> <p>现在我们得到了正五角星形内部小正五边形的内接圆半径,我们就可以计算出这个正五边形的外接圆半径了。还是通过一个小直角来计算。这个直角的斜边等于正五边形外接圆半径。一个锐角等于正五边形一条边所对应的圆心角的一半。这个锐角的一条边是这个圆心角的中直线,这个中直线是正五边形的外接圆半径。</p> <p>下边的说明图中高亮标注了一个直角三角形,它是由正五边形的一条外接圆半径、内接圆半径、一个圆心角的一半构成的。如果我们已知内接圆半径和正五边形一条边所对应的圆心角,这个圆心角的一半也就是两条外接圆半径的夹角的话。用这个直角三角形我们可以计算出外接圆半径的长。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 2500" xmlns="http://www.w3.org/2000/svg" class="live5"> <style>svg.live5 { fill: #333; font: 150px consolas, monaco, monospace } svg.live5 > [d], .live5 [r], .live5 line { fill: none; stroke: #ccc; stroke-width: 10 } .live5 #g *, .live5 [d] + [x] { fill: #ccc } .live5 [x] + [d] { marker-end: url(#g) } .live5 [d] + [x] { font: 1em times new roman, serif } .live5 [d] ~ [x] { font-style: italic } .live5 [r] + * + [d] { fill: hsla(0, 0%, 83%, .7) } .live5 line, .live5 [y2] ~ [d] { stroke: #95a; stroke-width: 15 } .live5 [d] + line { stroke-width: 20 } .live5 [r] + [d] { stroke: #999 } .live5 line ~ [r], .live5 [y2] ~ [d] { stroke: #333 } .live5 line ~ [r] { fill: #f90 } .live5 [d] + * + [d] { stroke: #cb0; fill: rgba(255,238,51,.7) } .live5 [y2] { stroke: #b9c } .live5 [d] + [d] { stroke: #d97 } .live5 [y2] + * + [d] { stroke: #b53 } .live5 [cx] + line { stroke: #f90 } .live5 tspan { font-size: .5em } </style> <marker id="g" markerWidth="10" markerHeight="10" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="988" y="120">0°</text> <text x="818" y="694">36°</text> <text x="230" y="1090">72°</text> <path d="M-1230 0H1230"></path> <text x="1155" y="-75">x</text> <text x="-112.5" y="-30">O</text> <text x="496.5" y="545">M</text> <path d="M0-1230V1230"></path> <text x="60" y="1200">y</text> <circle r="988"></circle> <path d="M305,940 -799,581 -799,-581 305,-940 988,0M646.5,470 799,581"></path> <path d="M988,0 0 0 646.5,470"></path> <path d="M0 0 229 0 A229 229 0 0 1 71 218"></path> <line x2="988"></line> <line x2="305" y2="940"></line> <path d="M582,423 629,358 694,405"></path> <path d="M988,0 646.5,470"></path> <path d="M646.5,470 305,940"></path> <circle cx="988" cy="0" r="25"></circle> <circle cx="305" cy="940" r="18"></circle> <circle cx="-799" cy="581" r="18"></circle> <circle cx="-799" cy="-581" r="18"></circle> <circle cx="305" cy="-940" r="18"></circle> <line x2="647" y2="470"></line> <circle cx="638" cy="414" r="5"></circle> <circle r="25"></circle> <circle cx="647" cy="470" r="25"></circle> <circle cx="799" cy="581" r="18"></circle> <text x="988" y="-30">V<tspan>0</tspan></text> <text x="305" y="1240">V<tspan>1</tspan></text> </svg> </div> <p><em>通过一个直角三角形计算正五边形外接圆的半径 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/eGPOOq?editors=1000">Live</a>)。</em></p>" <p>前文提到过,正五边形圆心角的度数与正五角星形的圆心角度数是不相等的。前者是后者的一半 (<code>360° / 5 = 72°</code>)。</p> <p>好,现在我们有了这个半径,就可以得到所有想要的点的坐标了。这些点均匀分布在两个圆上。有<code>5</code>个点在外层的圆上(正五角星形的外接圆),还有<code>5</code>个在内层的圆上(小正五边形的外接圆)。共计<code>10</code>个点,他们所在的半径射线的夹角是 <code>360° / 10 = 36°</code>。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 2500" xmlns="http://www.w3.org/2000/svg" class="live6"> <style>.live6 * { fill: #333; font: 150px consolas, monaco, monospace } svg.live6 > [d], .live6 [r] { fill: none; stroke: #ccc; stroke-width: 10 } .live6 [x] + [d], .live6 [cx] { stroke: #333; marker-end: url(#h) } .live6 [d] + [x] { font: italic 1em times new roman, serif } .live6 [cx] { fill: #f90 } .live6 [cx] ~ [d] { stroke: #95a; stroke-width: 15 } .live6 [d] + [d] { stroke: #b53 } </style> <marker id="h" markerWidth="10" markerHeight="10" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="988" y="120">0°</text> <text x="818" y="694">36°</text> <text x="230" y="1090">72°</text> <text x="-493" y="1090">108°</text> <text x="-1155" y="712">144°</text> <text x="-1326" y="-19">180°</text> <text x="-1062" y="-619">216°</text> <text x="-455" y="-996">252°</text> <text x="193" y="-996">288°</text> <text x="799" y="-619">324°</text> <path d="M-1230 0H1230"></path> <text x="1155" y="-75">x</text> <path d="M0-1230V1230"></path> <text x="60" y="1200">y</text> <circle r="988"></circle> <path d="M799,581 0 0 305,940M-305,940 0 0 -799,581M-799,-581 0 0 -305,-940M305,-940 0 0 799,-581"></path> <circle r="377"></circle> <circle cx="" r="18"></circle> <path d="M988,0 305,222 305,940 -117,359 -799,581 -377,0 -799,-581 -117,-359 305,-940 305,-222z"></path> <path d="M305,222 -117,359 -377,0 -117,-359 305,-222z"></path> <circle cx="988" r="25"></circle> <circle cx="305" cy="222" r="25"></circle> <circle cx="799" cy="581" r="18"></circle> <circle cx="305" cy="940" r="25"></circle> <circle cx="-117" cy="359" r="25"></circle> <circle cx="-305" cy="940" r="18"></circle> <circle cx="-799" cy="581" r="25"></circle> <circle cx="-377" r="25"></circle> <circle cx="-988" r="18"></circle> <circle cx="-799" cy="-581" r="25"></circle> <circle cx="-117" cy="-359" r="25"></circle> <circle cx="-305" cy="-940" r="18"></circle> <circle cx="305" cy="-940" r="25"></circle> <circle cx="305" cy="-222" r="25"></circle> <circle cx="799" cy="-581" r="18"></circle> </svg> </div> <p><em>终点均匀分布在小正五边形的外接圆上,控制点均匀分布在正五角星形的外接圆上 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/oGawmm?editors=1000">Live</a>)</em>。</p>" <p>已知两个圆的半径。外层圆的半径等于正五角星形外接圆半径,也就是我们定的有点儿随意的<code>viewBox</code> 尺寸的一部分(<code>.5</code> 或 <code>.25</code> 或 <code>.32</code>或者我们认为效果更好地尺寸)。内层圆的半径等于正五角星形内部构成的小正五边形的外接圆半径。计算这个半径的方法是:首先,通过正五角星形的外接圆半径和它的一条边所对应的圆心角计算出正五角星形的内接圆半径。这个内接圆半径与小正五边形的内接圆半径相等;然后,再通过小正五边形一条边所对应的圆心角和它的内接圆半径来计算。</p> <p>所以,基于这点,我们就能够生成绘制星形的路径的数据了。绘制它所需要的数据,我们都已经有了。</p> <p>那么让我们来绘制吧!并且把上边的思考过程写成代码。</p> <p>首先,先创建一个<code>getStarPoints(f)</code> 的函数。参数 <code>(f)</code> 将决定根据 <code>viewBox</code>的尺寸获取的正五角星形外接圆半径是多少。这个函数返回一个由坐标组成的数组,之后我们会给这个数组增加数组项。</p> <p>在这个函数中,我们首先计算常量:正五角星形外接圆半径(外层圆的半径)、正五角星形一条边所对应的圆心角、正五角星形内部构成的正五边形的一条边所对应的圆心角、正五角星形内部构成的正五边形的一条边所对应的圆心角、正五角星形和内部构成的正五边形共用的内接圆的半径(正五变形的顶点是正五角星形边的交叉点)、内部小正五变形的外接圆半径、需要计算坐标的点的总数、所有点所在的径向线的夹角。</p> <p>然后,用一个循环来计算我们想要的点的坐标,并将它们插入坐标数组中。</p> <pre><code>const P = 5; // 三次曲线、多边形顶点数 function getStarPoints(f = .5) { const RCO = f*D, // outer (pentagram) circumradius BAS = 2*(2*Math.PI/P), // base angle for star poly BAC = 2*Math.PI/P, // base angle for convex poly RI = RCO*Math.cos(.5*BAS),// pentagram/ inner pentagon inradius RCI = RI/Math.cos(.5*BAC),// inner pentagon circumradius ND = 2*P, // total number of distinct points we need to get BAD = 2*Math.PI/ND, // base angle for point distribution PTS = []; // array we fill with point coordinates for(let i = 0; i &lt; ND; i++) { } return PTS; } </code></pre> <p>计算坐标需要的条件:用点所在圆的半径,以及一条半径与水平轴线构成的夹角。如下面的交互式演示所示(拖动点来查看它的笛卡尔坐标如何变化):</p> <div style="margin-bottom: 20px;"><iframe id="BJEmBy" src="//codepen.io/airen/embed/BJEmBy?height=400&amp;theme-id=0&amp;slug-hash=BJEmBy&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>在我们的例子里,当前的半径有两个。一个是外圆的半径(正五角星形的外接圆半径<code>RCO</code>),可以帮助算出索引值为偶数的点的的坐标(<code>0</code>, <code>2</code>, <code>...</code>)。还有一个是内接圆的半径(内部小正五边形的外接圆半径<code>RCI</code>),可以帮助算出索引值为奇数的点的的坐标(<code>1</code>, <code>3</code>, <code>...</code>)。当前点与圆心点的连线所构成的径向线的夹角等于点的索引值(<code>i</code>)乘以所有点所在的径向线的夹角(<code>BAD</code>,在我们的例子里恰巧是<code>36°</code> 或 <code>π / 10</code>)。</p> <p>因此,循环体里的代码如下:</p> <pre><code>for(let i = 0; i &lt; ND; i++) { let cr = i%2 ? RCI : RCO, ca = i*BAD, x = Math.round(cr*Math.cos(ca)), y = Math.round(cr*Math.sin(ca)); } </code></pre> <p>由于我们给<code>viewBox</code> 设定的尺寸足够大,所以我们可以放心的给坐标值做四舍五入计算,舍弃小数部分,这样我们的代码看起来会更干净。</p> <p>我们会把外层圆(索引值是偶数的情况)计算出的坐标值推入坐标数组中两次。因为实际上星形在这个点上有两个重叠的控制点。如果要绘制成心形,就要把这两个重叠的控制点放在别的的位置上。</p> <pre><code>for(let i = 0; i &lt; ND; i++) { // same as before PTS.push([x, y]); if(!(i%2)) PTS.push([x, y]); } </code></pre> <p>接下来,我们给对象O添加数据。添加一个属性(<code>d</code>)来储存有关路径的数据。设置一个初始值来储存数组,这个数组是由上文提到的函数计算出的点的坐标组成的。我们还创建了一个函数用来生成实际的属性值(这个例子中,曲线的两个终点坐标的差值范围是路径的数据串,浏览器根据这个数据串绘制图形)。最后,我们获得了所有已经保存了数据的属性,并将这些属性的值作为前面提到的函数的返回值:</p> <pre><code>(function init() { // same as before O.d = { ini: getStarPoints(), afn: function(pts) { return pts.reduce((a, c, i) =&gt; { return a + (i%3 ? ' ' : 'C') + c }, `M${pts[pts.length - 1]}`) } }; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].ini)) })(); </code></pre> <p>绘制的结果可以在下边的演示中看到:</p> <div style="margin-bottom: 20px;"><iframe id="dJLZow" src="//codepen.io/airen/embed/dJLZow?height=400&amp;theme-id=0&amp;slug-hash=dJLZow&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>这是一个很有前途的星形。但我们想让生成的五角星形第一个尖朝下并且由它生成的星形的第一个尖朝上。目前,他们的指向都偏右了。这是因为我们是从 <code>0°</code>开始的(对应时钟的三点位置)。所以为了能从时钟<code>6</code>点的位置开始,我们给<code>getStarPoints()</code> 函数中的每个角加 <code>90°</code> (<code>π / 2</code> 弧度)。</p> <pre><code>ca = i*BAD + .5*Math.PI </code></pre> <p>这样生成的五角星形和由它生成的星形的第一个角就都朝下了。为了旋转星形,我们需要给它的 <code>transform</code> 属性设置成旋转半个圆的角度。为了到达这个效果,我们首先设置初始的旋转角度为<code>-180</code> 。然后,我们把生成实际属性值的函数设置成这样一个函数。这个函数接收两个参数,一个是函数名字,另一个为参数,函数返回由这两个参数组成的字符串:</p> <pre><code>function fnStr(fname, farg) { return `${fname}(${farg})` }; (function init() { // same as before O.transform = { ini: -180, afn: (ang) =&gt; fnStr('rotate', ang) }; // same as before })(); </code></pre> <p>我们用类似的方式给我们的星形填充(<code>fill</code>)金色。我们给初始值设置一个 <code>RGB</code> 字符串,用同一个函数来给属性(<code>fill</code>)设置值:</p> <pre><code>(function init() { // same as before O.fill = { ini: [255, 215, 0], afn: (rgb) =&gt; fnStr('rgb', rgb) }; // same as before })(); </code></pre> <p>现在我们用 SVG 绘制好了一个漂亮的金色星形,它是由五个三次贝塞尔曲线构成的:</p> <div style="margin-bottom: 20px;"><iframe id="wrRWJN" src="//codepen.io/airen/embed/wrRWJN?height=400&amp;theme-id=0&amp;slug-hash=wrRWJN&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h3>心形</h3> <p>我们已经绘制好星形了,现在来看下如何绘制心形吧!</p> <p>我们先从两个半径相等并横向相交的圆开始,这两个圆都是 <code>viewBox</code> 尺寸的一部分(暂时定位<code>.25</code>)。这两个圆相交的方式为:它们中心点相连的线落在 <code>x</code> 轴上,它们相交点相连的线落在 <code>y</code> 轴上。这两条线要相等。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -875 3500 1750" xmlns="http://www.w3.org/2000/svg" class="live7"> <style>.live7 * { fill: #333; font: 105px consolas, monaco, monospace } svg.live7 > [d], .live7 [r] { fill: none; stroke: #333; stroke-width: 7 } .live7 [x] + [d] { marker-end: url(#o) } .live7 [d] + [x] { font: italic 1em times new roman, serif } .live7 [r='625'] { stroke: #ccc } .live7 [r] + [d] { stroke: #95a; stroke-width: 14 } .live7 [d] + [d] { stroke: #f90; stroke-width: 10 } .live7 [r] ~ [d] ~ * { fill: #f90 } </style> <marker id="o" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-274" y="-242">R</text> <text x="211" y="-242">R</text> <path d="M-1230 0H1230"></path> <text x="1178" y="-53">x</text> <path d="M0-861V861"></path> <text x="42" y="840">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M-442 0 0-442 442 0"></path> <path d="M-442 0H442M0-442V442"></path> <circle cx="-442" r="13"></circle> <circle cx="442" r="13"></circle> <circle cy="-442" r="18"></circle> <circle cy="442" r="13"></circle> </svg> </div> <p><em>我们先从两个半径相等的相交的圆开始。这两个圆的圆心落在水平轴上,他们相交的点落在垂直轴上 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/aLPYQy?editors=1000">Live</a>)。</em></p>" <p>接着,我们画两条直径,这两条直径穿过靠上的那个交点。在直径与圆的另一个交点处画一条正切线。这两条正切线在 <code>y</code> 轴相交。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 3375" xmlns="http://www.w3.org/2000/svg" class="live8"> <style> .live8 * { fill: #333; font: 150px consolas, monaco, monospace } svg.live8 > [d],.live8 [r] { fill: none; stroke: #333; stroke-linecap: round; stroke-width: 10 } .live8 [x] + [d] { marker-end: url(#p) } .live8 [d] + [x] { font: italic 1em times new roman, serif } .live8 [r='625'] { stroke: #ccc } .live8 [r] + [d] { stroke: #95a; stroke-width: 20 } .live8 [d] + [d] { stroke: #f90; stroke-width: 15 } .live8 [d] + [d] ~ * { fill: #f90 } </style> <marker id="p" markerWidth="10" markerHeight="10" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-296" y="-251">R</text> <text x="206" y="-251">R</text> <text x="-738" y="191">R</text> <text x="648" y="191">R</text> <path d="M-1230 0H1230"></path> <text x="1155" y="-75">x</text> <path d="M0-1230V1660.5"></path> <text x="60" y="1630.5">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M-964 362l80 -80 80 80 M-884 442 0-442 884 442 M-884 362h1 M964 362l-80 -80 -80 80 M884 362h1"></path> <path d="M-1105 221 0 1326 1105 221"></path> <circle cx="-442" r="13"></circle> <circle cx="442" r="13"></circle> <circle cy="-442" r="25"></circle> <circle cy="1326" r="18"></circle> <circle cx="-884" cy="442" r="25"></circle> <circle cx="884" cy="442" r="25"></circle> </svg> </div> <p><em>画两条直径,穿过两个圆相交的点中靠上的那个,并在直径与圆的另一个交点处画正切线,两条正切线在垂直轴相交 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/qPLvbq?editors=1000">Live</a>)。</em></p>" <p>两个圆上边的交点和两个直径与圆的另两个交点构成了我们需要的<code>5</code>个点中的<code>3</code>个。另外两个终点则是把外侧的半圆切割成两个相等弧线的中点,这使我们得到<code>4</code>个四分之一圆弧。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 3375" xmlns="http://www.w3.org/2000/svg" class="live9"> <style> .live9 * { fill: #333; stroke-linecap: round; font: 150px consolas, monaco, monospace } .live9 [x] + [d], .live9 [r]:not([r='625']) { fill: #f90; stroke: #333; marker-end: url(#q) } svg.live9 > [d],.live9 [r] { fill: none; stroke: #ccc; stroke-width: 10 } .live9 [r] + [d] { stroke-width: 15 } .live9 [d] + [x] { font: italic 1em times new roman, serif } .live9 [d] + [d] { stroke: #f90; stroke-width: 20; mix-blend-mode: darken } </style> <marker id="q" markerWidth="10" markerHeight="10" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-296" y="-251">R</text> <text x="206" y="-251">R</text> <text x="-738" y="191">R</text> <text x="648" y="191">R</text> <path d="M-1230 0H1230"></path> <text x="1155" y="-75">x</text> <path d="M0-1230V1660.5"></path> <text x="60" y="1630.5">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M-964 362l80 -80 80 80 M-884 442 0-442 884 442 M-884 362h1 M964 362l-80 -80 -80 80 M-1105 221 0 1326 1105 221 M884 362h1"></path> <path d="M-884 442 A625 625 0 0 1 0 -442 A625 625 0 0 1 884 442 C0 1326 0 1326 -884 442"></path> <circle cx="-442" r="13"></circle> <circle cx="442" r="13"></circle> <circle cy="-442" r="25"></circle> <circle cy="1326" r="18"></circle> <circle cx="-884" cy="-442" r="25"></circle> <circle cx="884" cy="-442" r="25"></circle> <circle cx="-884" cy="442" r="25"></circle> <circle cx="884" cy="442" r="25"></circle> </svg> </div> <p><em>高亮显示了构成心形的三次贝塞尔曲线的终点以及靠下的那条曲线的控制点(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/wrRZBw?editors=1000">Live</a>)。</em></p>" <p>靠下的曲线控制点很明显已经得到了,就是两条切线的交点。但是另外四条曲线的控制点呢?我们怎么能把圆弧变成三次贝塞尔曲线呢?</p> <p>我们无法得到四分之一圆弧的三次贝塞尔曲线,但我们可以得到一个近似的,在<a href="http://xxysy.com/quot;//spencermortensen.com/articles/bezier-circle/">这篇文章</a>中有阐述。</p>" <p>这篇文章告诉我们,可以用一个值为<code>R</code> 的半径,和半径的切线(<code>N</code> 和 <code>Q</code>)来绘制四分之一圆弧。两条半径的切线相交于点 <code>P</code>。四边形 <code>ONPQ</code> 的四个角都等于<code>90°</code> (或<code>π / 2</code>,其中三个是公理得出的(<code>O</code> 是<code>90°</code> ,两条切线与半径的夹角也是<code>90°</code>),最后一个是计算得出的(内角的合是 <code>360°</code>,其它三个角都是<code>90°</code>, 最后一个角也就是<code>90°</code>了)。这样 <code>ONPQ</code> 就是一个矩形。同时 <code>ONPQ</code> 有两个相邻的边是相等的(<code>OQ</code> 和 <code>ON</code> 的长度都等于半径<code>R</code>),这样它就是一个边长为<code>R</code>的正方形。所以 <code>NP</code> 和 <code>QP</code> 长也等于<code>R</code>。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1500 -250 5000 2500" xmlns="http://www.w3.org/2000/svg" class="live10"> <style> .live10 * { fill: #333; stroke-linecap: round; font: 150px consolas, monaco, monospace } .live10 [d], .live10 [r] { fill: none; stroke: #f90; stroke-width: 10 } .live10 [x] + [d] { stroke: #ccc } .live10 [d*=A], .live10 [d*=h] { stroke-width: 20 } .live10 [d*=A] { stroke: #f90 } .live10 [d*=A] + * { stroke: #95a; stroke-width: 15 } .live10 [r] { fill: #f90; stroke: #333 } .live10 [r] + * { word-spacing: -52.5px } .live10 [r] + [x] ~ * { font-style: italic } </style> <text x="727" y="-30">R</text> <path d="M-238 0H2138M0-238V2138M160 0V160H0M1444 0V160H1604M160 1604V1444H0M1604 0V1604H0"></path> <text x="-120" y="877">R</text> <path d="M80 80h1M1524 80h1M80 1524h1"></path> <path d="M1604 0A1604 1604 0 0 1 0 1604"></path> <path d="M1604 0V885M0 1604H885"></path> <text x="292.5" y="1724">C·R</text> <text x="1634" y="502.5">C·R</text> <circle r="18"></circle> <circle cx="1604" r="25"></circle> <circle cy="1604" r="25"></circle> <circle cx="1604" cy="885" r="25"></circle> <circle cx="885" cy="1604" r="25"></circle> <circle cx="1604" cy="1604" r="13"></circle> <text x="2138" y="2138">C = .551915</text> <text x="-120" y="-30">O</text> <text x="1574" y="-45">N</text> <text x="1619" y="1694">P</text> <text x="-120" y="1634">Q</text> </svg> </div> <p><em>用三次贝塞尔曲线绘制近似四分之一圆弧的弧线 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/jGXoWY?editors=1000">Live</a>)。</em></p>" <p>我们用三次贝塞尔曲线绘制的近似四分之一圆弧的弧线的控制点就在切线 <code>NP</code> 和 <code>QP</code> 上,也就是从终点算起<code>C * R</code>的长度,<code>C</code>在之前提到文章中算出的值是<code>.551915</code>。</p> <p>知道了上边这些,我们可以开始计算三次贝塞尔曲线终点和控制点的坐标了,有了这些坐标,就可以构建我们的心形了。</p> <p>由于我们选择这种方式构建心形, <code>TO0SO1</code>是一个四边相等(四个边都是由两个圆的半径构成)的 <a href="http://xxysy.com/quot;//en.wikipedia.org/wiki/Square#Characterizations">正方形</a>" ,并且它的对角线也是相等的(这点前文有说过,两个圆心的连线与两个交点的连线相等)。这里,<code>O</code> 是两个对角线的交点,并且 <code>OT</code> 等于对角线 <code>ST</code> 的一半。<code>T</code> 和 <code>S</code> 在 <code>y</code> 轴上,所以他们的 <code>x</code> 坐标为 <code>0</code>。他们的 <code>y</code> 坐标对应 <code>OT</code> 的绝对值,也就是对角线的一半(<code>OS</code> 同理)。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -875 3500 1750" xmlns="http://www.w3.org/2000/svg" class="live11"> <style>.live11 * { fill: #333 } .live11 [x] { font: 105px consolas, monaco, monospace } svg.live11 > [d],.live11 [r] { fill: none; stroke: #333; stroke-width: 7 } .live11 [x] + [d] { marker-end: url(#R) } .live11 [d] + [x] { font: 105px times new roman, serif } .live11 [d] ~ [x] { font-style: italic } .live11 tspan { font-size: .5em } .live11 [r='625'] { stroke: #ccc } .live11 [r] + [d] { stroke: #95a; stroke-width: 10 } .live11 [d] + [d] { stroke: #f90; stroke-width: 14 } .live11 [r] ~ [d] ~ [r] { fill: #f90 } </style> <marker id="R" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-274" y="-242">R</text> <text x="211" y="-242">R</text> <text x="-274" y="305">R</text> <text x="211" y="305">R</text> <path d="M-1230 0H1230"></path> <text x="1178" y="-53">x</text> <path d="M0-861V861"></path> <text x="42" y="840">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M-442 0 0-442 442 0 0 442z"></path> <path d="M-442 0H442M0-442V442"></path> <circle cx="-442" r="13"></circle> <circle cx="442" r="13"></circle> <circle cy="-442" r="18"></circle> <circle cy="442" r="13"></circle> <text x="-84" y="-442">T</text> <text x="-73.5" y="-21">O</text> <text x="400" y="105">O<tspan>0</tspan></text> <text x="-484" y="105">O<tspan>1</tspan></text> <text x="21" y="526">S</text> </svg> </div> <p><em>正方形 <code>TO0SO1</code> (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/PJVmzG?editors=1000">Live</a>)</em>。</p>" <p>我们可以把任意边长为<code>l</code>的正方形切割成两个等腰三角形。这个等腰三角形的直角边与正方形的边重合,斜边与正方形对角线重合。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -500 5000 2500" xmlns="http://www.w3.org/2000/svg" class="live12"> <style>.live12 * { fill: #333; stroke-linecap: round; font: 150px consolas, monaco, monospace } .live12 [d], .live12 [r] { fill: none; stroke: #333; stroke-width: 15 } .live12 [d] + [d] { fill: #fe9; stroke: #95a } .live12 [x] + [d] { stroke: #b53; stroke-width: 20 } .live12 [r] { fill: #f90 } </style> <path d="M1500 0V1500H0M1340 1500v-160h160M1420 1420h1"></path> <path d="M1500 0 H0V1500"></path> <text x="675" y="-30">l</text> <text x="675" y="1620">l</text> <text x="-120" y="810">l</text> <text x="1530" y="810">l</text> <text x="810" y="810">d</text> <path d="M1500 0 0 1500"></path> <path d="M160 0V160H0M80 80h1"></path> <circle r="25"></circle> <circle cx="1500" r="25"></circle> <circle cy="1500" r="25"></circle> </svg> </div> <p><em>任意正方形可以被切割成两个等腰三角形(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/aLXjrJ?editors=1000">Live</a>)。</em></p>" <p>利用勾股定理:<code>d² = l² + l²</code>,我们可以计算出其中一个直角的斜边(也就是正方形的对角线)。这样根据边长就可以得出正方形对角线的长 <code>d = √(2 * l) = l * √2</code>(相反,根据对角线的长就可以得出边的长 <code>l = d / √2</code>)。还能计算出对角线的一半<code>d / 2 = (l * √2) / 2 = l / √2</code>。</p> <p>把这个应用到我们的边长为 <code>R</code>的 <code>TO0SO1</code> 正方形上,我们得到 <code>T</code> 点(它的绝对值等于正方形对角线的一半)的 <code>y</code> 坐标是 <code>-R / √2</code> ,同时 <code>S</code> 点的 <code>y</code> 坐标是<code>R / √2</code>。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -875 3500 1750" xmlns="http://www.w3.org/2000/svg" class="live13"> <style>svg.live13 { fill: #333; font: 105px consolas, monaco, monospace } svg.live13 > [d], .live13 [r] { fill: none; stroke: #333; stroke-width: 7 } .live13 [y] + [d]:not([d*=z]) { marker-end: url(#A) } .live13 [d] + * { font: 105px times new roman, serif } .live13 [d] ~ * { font-style: italic } .live13 [d] + [d] ~ [y] { fill: #b53 } .live13 [r='625'] { stroke: #ccc } .live13 [d][d*=z] { stroke: #95a; stroke-width: 10.5 } .live13 [d] + [d] { stroke: #f90; stroke-width: 14 } .live13 [r]:not([r='625']) { fill: #f90 } .live13 tspan { font-size: .5em } </style> <marker id="A" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <text x="-274" y="-242">R</text> <text x="211" y="-242">R</text> <text x="-274" y="305">R</text> <text x="211" y="305">R</text> <text y="-484">(0,-R/√2)</text> <text y="526">(0,R/√2)</text> <text y="-21">(0,0)</text> <text x="484" y="105">(R/√2,0)</text> <text x="-925" y="105">(-R/√2,0)</text> <path d="M-1230 0H1230"></path> <text x="1178" y="-53">x</text> <path d="M0-861V861"></path> <text x="42" y="840">y</text> <path d="M-442 0 0-442 442 0 0 442z"></path> <path d="M-442 0H442M0-442V442"></path> <circle cx="-442" r="13"></circle> <circle cx="442" r="13"></circle> <circle cy="-442" r="18"></circle> <circle cy="442" r="13"></circle> <circle r="9.10"></circle> <text x="-74" y="-484">T</text> <text x="-74" y="-21">O</text> <text x="400" y="105">O<tspan>0</tspan></text> <text x="-1009" y="105">O<tspan>1</tspan></text> <text x="-74" y="526">S</text> </svg> </div> <p><em><code>TO0SO1</code> 正方形四个顶点的坐标 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/qPgJbv?editors=1000">Live</a>)</em>。</p>" <p>类似的,<code>O1</code> 点在 <code>x</code> 轴上,所以他们的 <code>y</code> 轴坐标为<code>0</code>,他们的 <code>x</code> 轴坐标是对角线 <code>OO1</code> 的一半:<code>±R/√2</code>。</p> <p><code>TO0SO1</code> 是个正方形,那么它的四个角都是<code>90°</code>(<code>π / 2</code>圆弧)。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -875 3500 1750" xmlns="http://www.w3.org/2000/svg" class="live14"> <style>svg.live14 { fill: #333; font: 105px consolas, monaco, monospace } svg.live14 > [d], .live14 [r] { fill: none; stroke: #ccc; stroke-linecap: round; stroke-width: 7 } .live14 [y] + [d], .live14 [d] + [d] ~ [r] { fill: #f90; stroke: #333; marker-end: url(#B) } .live14 [d] + [y] { font: 105px times new roman, serif } .live14 [r] + [d] { stroke: #f90; stroke-width: 10 } .live14 [d] + [d] { stroke: #95a; stroke-width: 14 } .live14 [d] ~ [y] { font-style: italic } .live14 tspan { font-size: .5em } </style> <marker id="B" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-274" y="-242">R</text> <text x="211" y="-242">R</text> <text x="-716" y="200">R</text> <text x="653" y="200">R</text> <text x="-274" y="305">R</text> <text x="211" y="305">R</text> <text x="-674" y="-242">R</text> <text x="611" y="-242">R</text> <path d="M-1230 0H1230"></path> <text x="1178" y="-53">x</text> <path d="M0-861V861"></path> <text x="42" y="840">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M-884 442 0 -442 884 442 M-884 -442 0 442 884 -442 M386 56l-56-56 112-112 56 56 M-386 56l56-56 -112-112-56 56 M442-56h1M-442-56h1M386 0h1M-386 0h1"></path> <path d="M0-442H884V442H-884V-442H0V442"></path> <circle cx="-442" r="9"></circle> <circle cx="442" r="9"></circle> <circle cy="-442" r="18"></circle> <circle cy="442" r="9"></circle> <circle cx="-884" cy="-442" r="18"></circle> <circle cx="884" cy="-442" r="18"></circle> <circle cx="-884" cy="442" r="18"></circle> <circle cx="884" cy="442" r="18"></circle> <text y="-463">T</text> <text x="400" y="105">O<tspan>0</tspan></text> <text x="-484" y="105">O<tspan>1</tspan></text> <text x="11" y="526">S</text> <text x="863" y="-484">A<tspan>0</tspan></text> <text x="-947" y="-484">A<tspan>1</tspan></text> <text x="863" y="547">B<tspan>0</tspan></text> <text x="-947" y="547">B<tspan>1</tspan></text> </svg> </div> <p><em>四边形 <code>TA1B1S</code> (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/qPgwRx?editors=1000">Live</a>)</em>。</p>" <p>如上图所示,直线 <code>TB1</code> 是对角线,也就是说圆弧 <code>TB1</code> 是圆形的一半,或者叫做<code>180°</code>弧线。我们用 <code>A1</code> 点将这个弧分割成了相等的两半儿,得到两个相等的 <code>90°</code> 弧线:<code>TA1</code> 和 <code>A1B1</code> 。他们对应两个相等的 <code>90°</code> 角:<code>∠TO1A1</code> 和 <code>∠A1O1B1</code> 。</p> <p>根据公理 <code>∠TO1S</code> 和 <code>∠TO1A1</code> 都是<code>90°</code>的角,这证明直线 <code>SA1</code> 也是直径。这告诉我们在四边形 <code>TA1B1S</code> 中,对角线 <code>TB1</code> 和 <code>SA1</code> 是垂直且相等的,并且相交于各自的中心点(<code>TO1</code>、<code>O1B1</code>、<code>SO1</code> 和 <code>O1A1</code> 都等于圆形的的半径<code>R</code>)。这说明四边形 <code>TA1B1S</code> 是正方形,且它的对角线等于<code>2 * R</code>。</p> <p>到这里我们就可以得到四边形 <code>TA1B1S</code> 的边等于<code>2 * R / √2 = R * √2</code>。由于正方形所有的角都是<code>90°</code> ,并且边 <code>TS</code> 与垂直轴重叠,所以边 <code>TA1</code> 和 <code>SB1</code> 是水平的,且平行于 <code>x</code> 轴。根据他们的长度可以算出 <code>A1</code> 和 <code>B1</code> 两点的 <code>x</code> 轴坐标:<code>±R * √2</code>。</p> <p>因为 <code>TA1</code> 和 <code>SB1</code> 是水平的, 所以 <code>A1</code> 和 <code>B1</code> 两点的 <code>y</code> 轴坐标分别等于 <code>T (-R / √2)</code> 和 <code>S (R / √2)</code> 点。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -875 3500 1750" xmlns="http://www.w3.org/2000/svg" class="live15"> <style>svg.live15 { fill: #333; font: 105px consolas, monaco, monospace } svg.live15 > [d],.live15 [r] { fill: none; stroke: #333; stroke-width: 7 } .live15 [y] + [d]:not(#C) { marker-end: url(#D) } .live15 [d] + [y] { font: 105px times new roman, serif } .live15 [d] ~ [y] { font-style: italic } .live15 #C ~ [y] { fill: #b53 } .live15 tspan { font-size: .5em } .live15 [r='625'] { stroke: #ccc } .live15 #C { stroke: #f90; stroke-width: 10 } .live15 #C + [d] { stroke: #95a; stroke-width: 14 } .live15 [r]:not([r='625']) { fill: #f90 } </style> <marker id="D" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <text x="-274" y="-242">R</text> <text x="211" y="-242">R</text> <text x="-716" y="200">R</text> <text x="653" y="200">R</text> <text x="-274" y="305">R</text> <text x="211" y="305">R</text> <text x="-674" y="-242">R</text> <text x="611" y="-242">R</text> <text y="-484">(0,-R/√2)</text> <text y="547">(0,R/√2)</text> <text x="947" y="-484">(R·√2,-R/√2)</text> <text x="947" y="547">(R·√2,R/√2)</text> <text x="-1619" y="-484">(-R·√2,-R/√2)</text> <text x="-1556" y="547">(-R·√2,R/√2)</text> <path d="M-1230 0H1230"></path> <text x="1178" y="-53">x</text> <path d="M0-861V861"></path> <text x="42" y="840">y</text> <path id="C" d="M-884 442 0 -442 884 442 M-884 -442 0 442 884 -442"></path> <path d="M0-442H884V442H-884V-442H0V442"></path> <circle cx="-442" r="13"></circle> <circle cx="442" r="13"></circle> <circle cy="-442" r="18"></circle> <circle cy="442" r="13"></circle> <circle cx="-884" cy="-442" r="18"></circle> <circle cx="884" cy="-442" r="18"></circle> <circle cx="-884" cy="442" r="18"></circle> <circle cx="884" cy="442" r="18"></circle> <text x="-74" y="-484">T</text> <text x="-74" y="547">S</text> <text x="863" y="-484">A<tspan>0</tspan></text> <text x="-1703" y="-484">A<tspan>1</tspan></text> <text x="863" y="547">B<tspan>0</tspan></text> <text x="-1640" y="547">B<tspan>1</tspan></text> </svg> </div> <p><em>正方形 <code>TA1B1S</code> 四个顶点坐标(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/Oxqbzb?editors=1000">Live</a>)</em>。</p>" <p>我们从这里得到的另一个结论是,因为 <code>TA1B1S</code> 是正方形,所以 <code>A1B1</code> 平行于 <code>TS</code> ,因为 <code>TS</code> 在 <code>y</code> (垂直)轴上,所以 <code>A1B1</code> 也是垂直的。此外,因为 <code>x</code> 轴平行于 <code>TA1</code> 和 <code>SB1</code> ,并且将 <code>TS</code> 平分切为两断,所以 <code>x</code> 轴也将 <code>A1B1</code> 平分切为了两断。</p> <p>现在让我来看看控制点。</p> <p>我们先从最下边弧线的重叠的控制点开始。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-2500 -1250 5000 3375" xmlns="http://www.w3.org/2000/svg" class="live16"> <style>svg.live16 { fill: #333; font: 150px consolas, monaco, monospace } svg.live16 > [d], [r] { fill: none; stroke: #ccc; stroke-linecap: round; stroke-width: 10 } .live16 [y] + [d] { stroke: #333; marker-end: url(#E) } .live16 [d] + [d] ~ [r] { fill: #f90; stroke: #333 } .live16 [d] + [y] { font: 150px times new roman, serif } .live16 [r] + [d] { stroke: #f90; stroke-width: 15 } .live16 [d] + [d] { stroke: #95a; stroke-width: 20 } .live16 [d] ~ [y] { font-style: italic } .live16 tspan { font-size: .5em } </style> <marker id="E" markerWidth="10" markerHeight="10" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-296" y="-251">R</text> <text x="206" y="-251">R</text> <text x="-738" y="191">R</text> <text x="648" y="191">R</text> <path d="M-1230 0H1230"></path> <text x="1155" y="-75">x</text> <path d="M0-1230V1661"></path> <text x="60" y="1631">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M0-442V1326M-884 442H884"></path> <path d="M0-442 884 442 0 1326-884 442z M-80-362l80 80 80-80M0-362h1 M-804 362l80 80-80 80M-804 442h1 M804 362l-80 80 80 80M804 442h1"></path> <circle r="12.5"></circle> <circle cx="-442" r="13"></circle> <circle cx="442" r="13"></circle> <circle cy="-442" r="25"></circle> <circle cy="1326" r="18"></circle> <circle cy="442" r="13"></circle> <circle cx="-884" cy="442" r="25"></circle> <circle cx="884" cy="442" r="25"></circle> <text y="-472">T</text> <text x="382" y="150">O<tspan>0</tspan></text> <text x="-502" y="150">O<tspan>1</tspan></text> <text x="15" y="562">S</text> <text x="854" y="592">B<tspan>0</tspan></text> <text x="-974" y="592">B<tspan>1</tspan></text> <text x="-120" y="1416">C</text> </svg> </div> <p><em>四边形 <code>TB0CB1</code> (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/GMerwx?editors=1000">Live</a>)</em>。</p>" <p>四边形 <code>TB0CB1</code> 的所有角都等于 <code>90°</code> (因为 <code>TO0SO1</code> 是正方形所以 <code>∠T</code> 是直角;因为 <code>B1C</code> 是圆的切线,它与半径 <code>O1B1</code> 垂直,并相交于 <code>B1</code> 点, 所以 <code>∠B1</code> 是直角;因为其他三个都是直角,所以 <code>∠C</code> 也是直角),所以它是个矩形。同样它有两个相邻的边相等:<code>TB0</code> 和 <code>TB1</code>。这两条线都是圆形的直径,且都等于 <code>2 * R</code>。最后得出结论四边形 <code>TB0CB1</code> 是一个边长为<code>2 * R</code>的正方形。</p> <p>然后我们可以得到它的对角线 <code>TC</code> : <code>2 * R * √2</code>。因为 <code>C</code> 在 <code>y</code> 轴上,它的 <code>x</code> 轴坐标为 <code>0</code>。它的 <code>y</code> 轴坐标是 <code>OC</code> 的长度。<code>OC</code> 的长度等于 <code>TC</code> 减去 <code>OT</code>:<code>2 * R * √2 - R / √2 = 4 * R / √2 - R / √2 = 3 * R / √2</code>。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -700 3500 2362.5" xmlns="http://www.w3.org/2000/svg" class="live17"> <style>svg.live17 { fill: #333; font: 105px consolas, monaco, monospace } svg.live17 > [d],.live17 [r] { fill: none; stroke: #333; stroke-width: 7 } .live17 [y] + [d]:not([d*=Z]) { marker-end: url(#a1) } .live17 [d] + [y] { font: 105px times new roman, serif } .live17 [d] ~ [y] { font-style: italic } .live17 [d] + [d] ~ [y] { fill: #b53 } .live17 tspan { font-size: .5em } .live17 [r='625'] { stroke: #ccc } .live17 [d][d*=Z] { stroke: #95a; stroke-width: 14 } .live17 [d] + [d] { stroke: #f90; stroke-width: 10.5 } .live17 [r]:not([r='625']) { fill: #f90 } </style> <marker id="a1" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <text x="-274" y="-242">R</text> <text x="211" y="-242">R</text> <text x="-716" y="200">R</text> <text x="653" y="200">R</text> <text x="-610" y="968">2·R</text> <text x="432" y="968">2·R</text> <text y="-484">(0,-R/√2)</text> <text x="947" y="547">(R·√2,R/√2)</text> <text x="-1535" y="547">(-R·√2,R/√2)</text> <text y="-21">(0,0)</text> <text y="1410">(0,3·R/√2)</text> <path d="M-1230 0H1230"></path> <text x="1178" y="-53">x</text> <path d="M0-646V1593"></path> <text x="42" y="1572">y</text> <path d="M0-442 884 442 0 1326-884 442Z"></path> <path d="M0-442V1326M-884 442H884"></path> <circle r="9"></circle> <circle cx="-442" r="9"></circle> <circle cx="442" r="9"></circle> <circle cy="-442" r="18"></circle> <circle cy="1326" r="13"></circle> <circle cy="442" r="9"></circle> <circle cx="-884" cy="442" r="18"></circle> <circle cx="884" cy="442" r="18"></circle> <text x="-74" y="-484">T</text> <text x="-74" y="-21">O</text> <text x="400" y="105">O<tspan>0</tspan></text> <text x="-484" y="105">O<tspan>1</tspan></text> <text x="863" y="547">B<tspan>0</tspan></text> <text x="-1619" y="547">B<tspan>1</tspan></text> <text x="-74" y="1410">C</text> </svg> </div> <p><em>正方形 <code>TB0CB1</code> 四个顶点的坐标 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/WZmjgZ?editors=1000">Live</a>)</em>。</p>" <p>现在我们得到了最下边弧线两个重叠的控制点的坐标为<code>(0,3 * R / √2)</code>。</p> <p>为了获得其他曲线控制点的坐标,我们在他们的终点上画切线,并且获得这些切线的交叉点 <code>D1</code> 和 <code>E1</code> 。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -997 3500 1750" xmlns="http://www.w3.org/2000/svg" class="live18"> <style>svg.live18 { fill: #333; font: 105px consolas, monaco, monospace } svg.live18 > [d], [r] { fill: none; stroke: #ccc; stroke-linecap: round; stroke-width: 7 } .live18 [y] + [d] { stroke: #333; marker-end: url(#a2) } .live18 [d] + [d] ~ [r] { fill: #f90; stroke: #333 } .live18 [d] + [y], .live18 #a2 * { font: 105px times new roman, serif } .live18 [r] + [d] { stroke: #f90; stroke-width: 14 } .live18 [d] + [d] { stroke: #95a; stroke-width: 10 } .live18 [d] ~ [y] { font-style: italic } .live18 tspan { font-size: .5em } </style> <marker id="a2" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-221" y="-137">R</text> <text x="169" y="-137">R</text> <text x="-663" y="305">R</text> <text x="611" y="305">R</text> <text x="-674" y="-242">R</text> <text x="611" y="-242">R</text> <path d="M-1538 0H1538"></path> <text x="1485" y="-53">x</text> <path d="M0-861V749.07"></path> <text x="42" y="728">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M442-884V0h884 M-442-884V0h-884 M-884 442V-442H884V442"></path> <path d="M442 0 l-442-442 442-442 884 884-442 442-442-442 442-442 M-442 0 l442-442-442-442-884 884 442 442 442-442-442-442 M-498 56l-56-56 112 -112 56 56 M-498 0h1M-442-56h1 M498 56l56-56-112 -112-56 56 M498 0h1M442-56h1 M-828 -498l56 56 -112 112-56 -56 M-828 -442h1M-884-386h1 M828 -498l-56 56 112 112 56 -56 M828 -442h1M884-386h1 M-828 386l-56-56-56 56 M-884 386h1 M828 386l56-56 56 56 M884 386h1 M-56-498l-56 56 56 56 M-56-442h1 M56-498l56 56-56 56 M56-442h1"></path> <circle cx="-442" r="9"></circle> <circle cx="442" r="9"></circle> <circle cy="-442" r="18"></circle> <circle cx="-884" cy="-442" r="18"></circle> <circle cx="884" cy="-442" r="18"></circle> <circle cx="-884" cy="442" r="18"></circle> <circle cx="884" cy="442" r="18"></circle> <circle cx="-1326" r="13"></circle> <circle cx="1326" r="13"></circle> <circle cx="-442" cy="-884" r="13"></circle> <circle cx="442" cy="-884" r="13"></circle> <text y="-495">T</text> <text x="400" y="105">O<tspan>0</tspan></text> <text x="-484" y="105">O<tspan>1</tspan></text> <text x="863" y="-484">A<tspan>0</tspan></text> <text x="-947" y="-484">A<tspan>1</tspan></text> <text x="863" y="547">B<tspan>0</tspan></text> <text x="-947" y="547">B<tspan>1</tspan></text> <text x="421" y="-926">D<tspan>0</tspan></text> <text x="-505" y="-926">D<tspan>1</tspan></text> <text x="1284" y="105">E<tspan>0</tspan></text> <text x="-1368" y="105">E<tspan>1</tspan></text> </svg> </div> <p><em>四边形 <code>TO1A1D1</code> 和 <code>A1O1B1E1</code> (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/XeGVdj?editors=1000">Live</a>)</em>。</p>" <p>在四边形 <code>TO1A1D1</code> 中,已知所有角都是直角(<code>90°</code> ),其中三个是公理得出的(<code>∠D1TO1</code> 和 <code>∠D1A1O1</code> 是由半径和切线获得的;<code>∠TO1A1</code> 是对应四分之一弧 <code>TA1</code> 的角),那么第四个角通过计算就得出也是直角。这证明 <code>TO1A1D1</code> 是矩形。又因为它有两个相邻的边相等(<code>O1T</code> 和 <code>O1A1</code> 等于半径 <code>R</code>),所以 <code>TO1A1D1</code> 是正方形。</p> <p>这说明对角线 <code>TA1</code> 和 <code>O1D1</code> 等于 <code>R * √2</code>。已知 <code>TA1</code> 是水平的,又正方形两个对角线是垂直的,就证明 <code>O1D1</code> 是垂直的。那么点 <code>O1</code> 和 <code>D1</code> 的 <code>x</code> 轴坐标相等,<code>O1</code> 的 <code>x</code> 轴坐标是<code>±R / √2</code>。因为我们知道 <code>O1D1</code> 的长,所以我们可以算出 <code>y</code> 轴坐标:如前文提到的那样用对角线的长( <code>R * √2</code>)做减法。</p> <p>四边形 <code>A1O1B1E1</code> 的情况类似。已知所有角都是直角(<code>90°</code>),其中三个是公理得出的(<code>∠E1A1O1</code> 和 <code>∠E1B1O1</code> 是由半径和切线获得的;<code>∠A1O1B1</code> 是对应四分之一弧 <code>A1B1</code> 的角),那么第四个角通过计算就得出也是直角。这证明 <code>A1O1B1E1</code> 是矩形。又因为它有两个相邻的边相等(<code>O1A1</code> 和 <code>O1B1</code> 等于半径<code>R</code>),所以 <code>A1O1B1E1</code> 是正方形。</p> <p>至此,我们得到对角线 <code>A1B1</code> 和 <code>O1E1</code> 的长为<code>R * √2</code>。我们知道 <code>A1B1</code> 是垂直的,并且被水平轴切割成相等的两半儿,也就是 <code>O1E1</code> 在水平轴上,点 <code>E1</code> 的 <code>y</code> 轴坐标为<code>0</code>。因为点 <code>O1</code> 的 <code>x</code> 轴坐标为<code>±R / √2</code>,并且 <code>O1E1</code> 等于<code>R * √2</code>,我们就可以计算出点 <code>E1</code> 的 <code>x</code> 轴坐标为:<code>±3 * R / √2</code>。</p> <div style="margin-bottom: 20px; padding: 2px; border: 1px solid #ccc;"> <svg viewBox="-1750 -997 3500 1750" xmlns="http://www.w3.org/2000/svg" class="live19"> <style>svg.live19 { fill: #333; font: 84px consolas, monaco, monospace } svg.live19 > [d], .live19 [r] { fill: none; stroke: #ccc; stroke-width: 7 } .live19 [y] + [d] { stroke: #333; marker-end: url(#a3) } .live19 [d] + [d] ~ [r] { fill: #f90; stroke: #333 } .live19 [d] + [y],.live19 #a3 * { font: 84px times new roman, serif } .live19 [r] + [d] { stroke: #f90; stroke-width: 14 } .live19 [d] + [d] { stroke: #95a; stroke-width: 10 } .live19 [d] ~ [y] { font-style: italic } .live19 tspan { font-size: .5em } .live19 [d] + [d] ~ [y] { fill: #b53 } </style> <marker id="a3" markerWidth="7" markerHeight="7" viewBox="0 -4 8 8" orient="auto" refX="7"> <path d="M8 0 0-4V4"></path> </marker> <text x="-221" y="-154">R</text> <text x="179" y="-154">R</text> <text x="-663" y="288">R</text> <text x="621" y="288">R</text> <text x="-671" y="-238">R</text> <text x="621" y="-238">R</text> <text x="173" y="151">(R/√2,0)</text> <text x="-543" y="151">(-R/√2,0)</text> <text x="476" y="-918">(R/√2,-R·√2)</text> <text x="-996" y="-918">(-R/√2,-R·√2)</text> <text x="1225" y="151">(3·R/√2,0)</text> <text x="-1729" y="151">(-3·R/√2,0)</text> <path d="M-1538 0H1538"></path> <text x="1496" y="-42">x</text> <path d="M0-861V732"></path> <text x="33.6" y="715">y</text> <circle cx="-442" r="625"></circle> <circle cx="442" r="625"></circle> <path d="M442-884V0h884 M-442-884V0h-884 M-884 442V-442H884V442"></path> <path d="M442 0 l-442-442 442-442 884 884-442 442-442-442 442-442 M-442 0 l442-442-442-442-884 884 442 442 442-442-442-442"></path> <circle cx="-442" r="9"></circle> <circle cx="442" r="9"></circle> <circle cy="-442" r="18"></circle> <circle cx="-884" cy="-442" r="18"></circle> <circle cx="884" cy="-442" r="18"></circle> <circle cx="-884" cy="442" r="18"></circle> <circle cx="884" cy="442" r="18"></circle> <circle cx="-1326" r="13"></circle> <circle cx="1326" r="13"></circle> <circle cx="-442" cy="-884" r="13"></circle> <circle cx="442" cy="-884" r="13"></circle> <text y="-484">T</text> <text x="408" y="84">O<tspan>0</tspan></text> <text x="-476" y="84">O<tspan>1</tspan></text> <text x="867" y="-476">A<tspan>0</tspan></text> <text x="-934" y="-476">A<tspan>1</tspan></text> <text x="867" y="526">B<tspan>0</tspan></text> <text x="-934" y="526">B<tspan>1</tspan></text> <text x="408" y="-918">D<tspan>0</tspan></text> <text x="-1064" y="-918">D<tspan>1</tspan></text> <text x="1292" y="84">E<tspan>0</tspan></text> <text x="-1360" y="84">E<tspan>1</tspan></text> </svg> </div> <p><em>四边形 <code>TO1A1D1</code> 和 <code>A1O1B1E1</code> 的顶点坐标(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/xXorRQ?editors=1000">Live</a>)</em>。</p>" <p>但是这些切线的交叉点并不是控制点,所以我们需要用近似圆弧形的方法来计算。我们想要的控制点在 <code>TD1</code>、<code>A1D1</code>、<code>A1E1</code> 和 <code>B1E1</code> 上,距离弧线终点(<code>T</code>、<code>A1</code>、<code>B1</code>)大约<code>55%</code>(这个值来源于前文提到的那篇文章中算出的常量<code>C</code>的值)的位置。也就是说从终点到控制点的距离是<code>C * R</code>。</p> <p>在这种情况下,我们的控制点坐标为:终点(<code>T</code>、<code>A1</code> 和 <code>B1</code>)坐标的<code>1 - C</code>,加上,切线交点(<code>D1</code> 和 <code>E1</code>)坐标的 <code>C</code> 。</p> <p>让我们把这些写入JavaScript代码吧!</p> <p>跟星形的例子一样,我们先从函数<code>getStarPoints(f)</code> 开始。根据这个函数的参数 <code>(f)</code> ,我们可以从<code>viewBox</code> 的尺寸中获得辅助圆的半径。这个函数同样会返回一个坐标构成的数组,以便我们后边插入数组项。</p> <p>在函数中,我们先声明常量。</p> <ul> <li>辅助圆的半径。</li> <li>边与这个辅助圆半径相等的小正方形对角线的一半。对角线的一半也是这些正方形外接圆半径。</li> <li>三次贝塞尔曲线终点的坐标值(点<code>T</code>、<code>A1</code>、<code>B1</code>),沿水平轴的绝对值。</li> </ul> <p>然后我们把注意力放在切线交点的坐标上( 点 <code>C</code>、<code>D1</code>、<code>E1</code> )。这些点或者与控制点(<code>C</code>)重合,或者可以帮助我们获得控制点(例如点 <code>D1</code> 和 <code>E1</code>)。</p> <pre><code>function getHeartPoints(f = .25) { const R = f*D, // helper circle radius RC = Math.round(R/Math.SQRT2), // circumradius of square of edge R XT = 0, YT = -RC, // coords of point T XA = 2*RC, YA = -RC, // coords of A points (x in abs value) XB = 2*RC, YB = RC, // coords of B points (x in abs value) XC = 0, YC = 3*RC, // coords of point C XD = RC, YD = -2*RC, // coords of D points (x in abs value) XE = 3*RC, YE = 0; // coords of E points (x in abs value) } </code></pre> <p>点击下边交互演示上的点,可以展示这些点的坐标:</p> <div style="margin-bottom: 20px;"><iframe id="EoJQxy" src="//codepen.io/airen/embed/EoJQxy?height=400&amp;theme-id=0&amp;slug-hash=EoJQxy&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>现在我们可以通过终点和切线交点来获得控制点:</p> <pre><code>function getHeartPoints(f = .25) { // same as before // const for cubic curve approx of quarter circle const C = .551915, CC = 1 - C, // coords of ctrl points on TD segs XTD = Math.round(CC*XT + C*XD), YTD = Math.round(CC*YT + C*YD), // coords of ctrl points on AD segs XAD = Math.round(CC*XA + C*XD), YAD = Math.round(CC*YA + C*YD), // coords of ctrl points on AE segs XAE = Math.round(CC*XA + C*XE), YAE = Math.round(CC*YA + C*YE), // coords of ctrl points on BE segs XBE = Math.round(CC*XB + C*XE), YBE = Math.round(CC*YB + C*YE); // same as before } </code></pre> <p>下一步,我们要把相关的坐标合成一个数组,并将这个数组返回。在星形的例子中,我们是从最下边的弧形开始的,然后按照顺时针方向绘制,所以在这里我们用同样的方法。每个曲线,我们为控制点放入两组坐标,为终点放入一组坐标。</p> <div style="margin-bottom: 20px;"><iframe id="zpXRvB" src="//codepen.io/airen/embed/zpXRvB?height=400&amp;theme-id=0&amp;slug-hash=zpXRvB&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>请注意,第一个曲线(最下边的那个),他的两个控制点重叠了,所以我们把相同的坐标组合推入两次。代码看起来也许并不像绘制星形时那样整洁好看,但可以满足我们的需求:</p> <pre><code>return [ [XC, YC], [XC, YC], [-XB, YB], [-XBE, YBE], [-XAE, YAE], [-XA, YA], [-XAD, YAD], [-XTD, YTD], [XT, YT], [XTD, YTD], [XAD, YAD], [XA, YA], [XAE, YAE], [XBE, YBE], [XB, YB] ]; </code></pre> <p>现在我们可以把星形的最终状态设置成函数<code>getHeartPoints()</code>,没有旋转,没有填充( <code>fill</code>)深红色。然后把当前状态设置成最终状态,以便能看到心形:</p> <pre><code>function fnStr(fname, farg) { return `${fname}(${farg})` }; (function init() { _SVG.setAttribute('viewBox', [-.5*D, -.5*D, D, D].join(' ')); O.d = { ini: getStarPoints(), fin: getHeartPoints(), afn: function(pts) { return pts.reduce((a, c, i) =&gt; { return a + (i%3 ? ' ' : 'C') + c }, `M${pts[pts.length - 1]}`) } }; O.transform = { ini: -180, fin: 0, afn: (ang) =&gt; fnStr('rotate', ang) }; O.fill = { ini: [255, 215, 0], fin: [220, 20, 60], afn: (rgb) =&gt; fnStr('rgb', rgb) }; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p].fin)) })(); </code></pre> <p>这个心形看上去很不错:</p> <div style="margin-bottom: 20px;"><iframe id="EoJQyN" src="//codepen.io/airen/embed/EoJQyN?height=400&amp;theme-id=0&amp;slug-hash=EoJQyN&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h2>确保两个图形是对齐的</h2> <p>如果我们不给图形填充( <code>fill</code>)颜色、不旋转(<code>transform</code>)图形,只是看他们的骨架(<code>stroke</code>)叠在一起。就会发现它们并没有对齐:</p> <div style="margin-bottom: 20px;"><iframe id="baJLwJ" src="//codepen.io/airen/embed/baJLwJ?height=400&amp;theme-id=0&amp;slug-hash=baJLwJ&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>解决这个问题最简单的方法就是利用辅助圆的半径把心形向上移动一些:</p> <pre><code>return [ /* same coords */ ].map(([x, y]) =&gt; [x, y - .09*R]) </code></pre> <p>现在我们已经对齐了,忽略我们是如何调整这两个例子的f参数的。这个参数在星形中决定了五角星形外接圆半径与<code>viewBox</code>尺寸的对应关系(默认值是 <code>.5</code>),在心形中决定了辅助圆的半径与<code>viewBox</code>尺寸的对应关系(默认值是 <code>.25</code>)。</p> <div style="margin-bottom: 20px;"><iframe id="opOEBo" src="//codepen.io/airen/embed/opOEBo?height=400&amp;theme-id=0&amp;slug-hash=opOEBo&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h2>在两个图形中切换</h2> <p>当点击的时候,我们希望能从一种图形转换成另一种。为了做到这个,我们设置一个<code>dir</code>变量,当我们从星形变成心形时,它的值是<code>1</code>。当我们从心形转换成星形时,它的值是<code>-1</code>。初始值是<code>-1</code>,已达到刚刚从心形转换成星形的效果。</p> <p>然后我们在元素<code>_SHAPE</code>上添加一个<code>click</code>事件监听,监听的函数内容为:改变变量<code>dir</code>的值、改变图形的属性。这样就可以获得从一个金色星形转换成深红色心形,再变回星形的效果:</p> <pre><code>let dir = -1; (function init() { // same as before _SHAPE.addEventListener('click', e =&gt; { dir *= -1; for(let p in O) _SHAPE.setAttribute(p, O[p].afn(O[p][dir &gt; 0 ? 'fin' : 'ini'])); }, false); })(); </code></pre> <p>现在我们可以通过点击图形在两种图形中转换了:</p> <div style="margin-bottom: 20px;"><iframe id="RxOQVB" src="//codepen.io/airen/embed/RxOQVB?height=400&amp;theme-id=0&amp;slug-hash=RxOQVB&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h2>在两个图形中转变</h2> <p>我们最终想要的并不是两个图形间唐突的切换,而是柔和的渐变效果。所以我们用以前的文章说明的插值技术来实现。</p> <p>首先我们要决定转变动画的总帧数(<code>NF</code>),然后选择一种我们想要的时间函数:从星形到心形的的路径(<code>path</code>)转变我们选择<code>ease-in-out</code>函数,旋转角度的转变我们选择 <code>bounce-ini-fin</code> 函数,填充(<code>fill</code>)颜色转变我们选择<code>ease-out</code> 函数。我们先只做这些,如果之后我们改变注意了想探索其它的选项,也可以添加。</p> <pre><code>/* same as before */ const NF = 50, TFN = { 'ease-out': function(k) { return 1 - Math.pow(1 - k, 1.675) }, 'ease-in-out': function(k) { return .5*(Math.sin((k - .5)*Math.PI) + 1) }, 'bounce-ini-fin': function(k, s = -.65*Math.PI, e = -s) { return (Math.sin(k*(e - s) + s) - Math.sin(s))/(Math.sin(e) - Math.sin(s)) } }; </code></pre> <p>然后我们为每种属性指定转换时使用的时间函数。</p> <pre><code>(function init() { // same as before O.d = { // same as before tfn: 'ease-in-out' }; O.transform = { // same as before tfn: 'bounce-ini-fin' }; O.fill = { // same as before tfn: 'ease-out' }; // same as before })(); </code></pre> <p>我们继续添加请求变量 <code>ID</code>(<code>rID</code>)、当前帧变量 (<code>cf</code>) 、点击时第一个被调用并在每次显示刷新的时候都会被调用的函数<code>update()</code> 、当过渡结束时被调用的函数<code>stopAni()</code>,这个函数用来退出循环动画。在 <code>update()</code>函数里我们更新当前帧 <code>cf</code>,计算进程变量 <code>k</code>,判断过渡是否结束,是退出循环动画还是继续动画。</p> <p>我们还会添加一个乘数变量 <code>m</code> ,用于防止我们从最终状态(心形)返归到最初状态(星形)时倒转时间函数。</p> <pre><code>let rID = null, cf = 0, m; function stopAni() { cancelAnimationFrame(rID); rID = null; }; function update() { cf += dir; let k = cf/NF; if(!(cf%NF)) { stopAni(); return } rID = requestAnimationFrame(update) }; </code></pre> <p>然后我们需要改变点击时所做的事情:</p> <pre><code>addEventListener('click', e =&gt; { if(rID) stopAni(); dir *= -1; m = .5*(1 - dir); update(); }, false); </code></pre> <p>在 <code>update()</code>函数中,我们需要设置当过渡到中间值(取决于进程变量k)时的属性。如同前边的文章中所述,最好是在开始时计算出最终值和初始值之间的差值范围,甚至是在设置监听之前就设置好,所以我们的下一步是:创建一个计算数字间差值范围的函数。无论在这种情况下,还是在数组中,无论数组的嵌套有多深,都可以这个函数来设置我们想要转变的属性的范围值。</p> <pre><code>function range(ini, fin) { return typeof ini == 'number' ? fin - ini : ini.map((c, i) =&gt; range(ini[i], fin[i])) }; (function init() { // same as before for(let p in O) { O[p].rng = range(O[p].ini, O[p].fin); _SHAPE.setAttribute(p, O[p].afn(O[p].ini)); } // same as before })(); </code></pre> <p>现在只剩下 <code>update()</code> 函数中有关插值的部分了。使用一个循环,我们会遍历所有我们想要从一个状态顺滑转换到另一个状态的属性。在这个循环中,我们先得到插值函数的运算结果,然后将这些属性设置成这个值。插值函数的运算结果取决于初始值(<code>s</code>)、当前属性(<code>ini</code>和 <code>rng</code>)的范围(<code>s</code>)、我们使用的定时函数(<code>tfn</code>) 和进度(<code>k</code>):</p> <pre><code>function update() { // same as before for(let p in O) { let c = O[p]; _SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k))); } // same as before }; </code></pre> <p>最后一步是编写这个插值函数。这跟获得范围值的那个函数非常相似:</p> <pre><code>function int(ini, rng, tfn, k) { return typeof ini == 'number' ? Math.round(ini + (m + dir*tfn(m + dir*k))*rng) : ini.map((c, i) =&gt; int(ini[i], rng[i], tfn, k)) }; </code></pre> <p>最后获得了一个形状,当点击它时可以从星形过渡转换成心形,第二次点击的时候会变回星形!</p> <div style="margin-bottom: 20px;"><iframe id="xpeYzP" src="//codepen.io/airen/embed/xpeYzP?height=400&amp;theme-id=0&amp;slug-hash=xpeYzP&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>这几乎就是我们想要的了:但还有一个小问题。对于像角度值这样的循环值,我们并不想在第二次点击的时候将他调转。相反,我们希望他继续顺着同一个方向旋转。通过两次点击后,正好能旋转一周,回到起点。</p> <p>我们通过给代码添加一个可选的属性,稍稍调整更新函数和插值函数:</p> <pre><code>function int(ini, rng, tfn, k, cnt) { return typeof ini == 'number' ? Math.round(ini + cnt*(m + dir*tfn(m + dir*k))*rng) : ini.map((c, i) =&gt; int(ini[i], rng[i], tfn, k, cnt)) }; function update() { // same as before for(let p in O) { let c = O[p]; _SHAPE.setAttribute(p, c.afn(int(c.ini, c.rng, TFN[c.tfn], k, c.cnt ? dir : 1))); } // same as before }; (function init() { // same as before O.transform = { ini: -180, fin: 0, afn: (ang) =&gt; fnStr('rotate', ang), tfn: 'bounce-ini-fin', cnt: 1 }; // same as before })(); </code></pre> <p>现在我们得到了我们想要的最终结果:一个从金色星形变成深红色心形的形状,每次从一个状态到另一个状态顺时针旋转半圈:</p> <div style="margin-bottom: 20px;"><iframe id="eyoVPv" src="//codepen.io/airen/embed/eyoVPv?height=400&amp;theme-id=0&amp;slug-hash=eyoVPv&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <blockquote> <p><strong>特别声明</strong>:本文转载@粒儿 发布于众成翻译的《<a href="//zcfy.cc/article/creating-a-star-to-heart-animation-with-svg-and-vanilla-javascript-css-tricks">用SVG和Vanilla JS框架创建一个“星形变心形”的动画效果</a>》一文,如需转载,烦请注明原文出处:<a href="//zcfy.cc/article/creating-a-star-to-heart-animation-with-svg-and-vanilla-javascript-css-tricks">http://zcfy.cc/article/creating-a-star-to-heart-animation-with-svg-and-vanilla-javascript-css-tricks</a></p> </blockquote> </div></div></div><div class="field field-name-field-taxonomy field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/624.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">转载</a></div></div></div><div class="field field-name-field-blog-tag field-type-taxonomy-term-reference field-label-hidden"><div class="field-items"><div class="field-item even"><a href="http://xxysy.com/quot;/svg-tutorial"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">SVG</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/JavaScript"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">JavaScript</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/29.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Animation</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/585.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">SVG Animation</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/532.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Web动画</a></div></div></div> Tue, 23 Jan 2018 15:08:32 +0000 Airen 2355 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com