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/vue-render-function.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中怎么编写可复用的伟德1946手机版,提到要对Vue的<code>render</code>函数有所了解。可仔细一想,对于Vue的<code>render</code>函数自己只是看了官方的一些介绍,并未深入一点去了解这方面的知识。为了更好的学习后续的知识,又折回来了解Vue中的<code>render</code>函数,这一切主要都是为了后续能更好的学习Vue的知识。</p> <h2>回忆Vue的一些基本概念</h2> <p>今天我们学习的目的是了解和学习Vue的<code>render</code>函数。如果想要更好的学习Vue的<code>render</code>函数相关的知识,我们有必要重温一下Vue中的一些基本概念。那么先上一张图,这张图从宏观上展现了Vue整体流程:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-1.jpg" alt="" /></p> <p>从上图中,不难发现一个Vue的应用程序是如何运行起来的,模板通过编译生成AST,再由AST生成Vue的<code>render</code>函数(渲染函数),渲染函数结合数据生成Virtual DOM树,Diff和Patch后生成新的UI。从这张图中,可以接触到Vue的一些主要概念:</p> <ul> <li><strong>模板</strong>:Vue的模板基于纯HTML,基于Vue的模板语法,我们可以比较方便地声明数据和UI的关系。</li> <li><strong>AST</strong>:AST是<strong>Abstract Syntax Tree</strong>的简称,Vue使用HTML的Parser将HTML模板解析为AST,并且对AST进行一些优化的标记处理,提取最大的静态树,方便Virtual DOM时直接跳过Diff。</li> <li><strong>渲染函数</strong>:渲染函数是用来生成Virtual DOM的。Vue推荐使用模板来构建我们的应用界面,在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制 (这部分是我们今天主要要了解和学习的部分)。</li> <li><strong>Virtual DOM</strong>:虚拟DOM树,Vue的Virtual DOM Patching算法是基于<strong><a href="http://xxysy.com/quot;//github.com/snabbdom/snabbdom">Snabbdom</a></strong>的实现,并在些基础上作了很多的调整和改进。</li>" <li><strong>Watcher</strong>:每个Vue伟德1946手机版都有一个对应的<code>watcher</code>,这个<code>watcher</code>将会在伟德1946手机版<code>render</code>的时候收集伟德1946手机版所依赖的数据,并在依赖有更新的时候,触发伟德1946手机版重新渲染。你根本不需要写<code>shouldComponentUpdate</code>,Vue会自动优化并更新要更新的UI。</li> </ul> <p>上图中,<code>render</code>函数可以作为一道分割线,<code>render</code>函数的左边可以称之为<strong>编译期</strong>,将Vue的模板转换为<strong>渲染函数</strong>。<code>render</code>函数的右边是Vue的运行时,主要是基于渲染函数生成Virtual DOM树,Diff和Patch。</p> <h2>渲染函数的基础</h2> <p>Vue推荐在绝大多数情况下使用<code>template</code>来创建你的HTML。然而在一些场景中,需要使用JavaScript的编程能力和创建HTML,这就是<strong><code>render</code>函数</strong>,它比<code>template</code>更接近编译器。</p> <pre><code>&lt;h1&gt; &lt;a name="hello-world" href="http://xxysy.com/quot;#hello-world"&gt" Hello world! &lt;/a&gt; &lt;/h1&gt; </code></pre> <p>在HTML层,我们决定这样定义伟德1946手机版接口:</p> <pre><code>&lt;anchored-heading :level="1"&gt;Hello world!&lt;/anchored-heading&gt; </code></pre> <p>当我们开始写一个通过<code>level</code>的<code>prop</code>动态生成<code>heading</code>标签的伟德1946手机版,你可能很快想到这样实现:</p> <pre><code>&lt;!-- HTML --&gt; &lt;script type="text/x-template" id="anchored-heading-template"&gt; &lt;h1 v-if="level === 1"&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/h1&gt; &lt;h2 v-else-if="level === 2"&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/h2&gt; &lt;h3 v-else-if="level === 3"&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/h3&gt; &lt;h4 v-else-if="level === 4"&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/h4&gt; &lt;h5 v-else-if="level === 5"&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/h5&gt; &lt;h6 v-else-if="level === 6"&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/h6&gt; &lt;/script&gt; &lt;!-- Javascript --&gt; Vue.component('anchored-heading', { template: '#anchored-heading-template', props: { level: { type: Number, required: true } } }) </code></pre> <p>在这种场景中使用 <code>template</code> 并不是最好的选择:首先代码冗长,为了在不同级别的标题中插入锚点元素,我们需要重复地使用 <code>&lt;slot&gt;&lt;/slot&gt;</code>。</p> <p>虽然模板在大多数伟德1946手机版中都非常好用,但是在这里它就不是很简洁的了。那么,我们来尝试使用 <code>render</code> 函数重写上面的例子:</p> <pre><code>Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // tag name 标签名称 this.$slots.default // 子伟德1946手机版中的阵列 ) }, props: { level: { type: Number, required: true } } }) </code></pre> <p>简单清晰很多!简单来说,这样代码精简很多,但是需要非常熟悉 Vue 的实例属性。在这个例子中,你需要知道当你不使用 <code>slot</code> 属性向伟德1946手机版中传递内容时,比如 <code>anchored-heading</code> 中的 <code>Hello world!</code>,这些子元素被存储在伟德1946手机版实例中的 <code>$slots.default</code>中。</p> <h2>节点、树以及虚拟DOM</h2> <p>对Vue的一些概念和渲染函数的基础有一定的了解之后,我们需要对一些浏览器的工作原理有一些了解,这样对我们学习<code>render</code>函数是很重要的。比如下面的这段HTML代码:</p> <pre><code>&lt;div&gt; &lt;h1&gt;My title&lt;/h1&gt; Some text content &lt;!-- TODO: Add tagline --&gt; &lt;/div&gt; </code></pre> <p>当浏览器读到这些代码时,它会建立一个<strong><a href="//javascript.info/dom-nodes">DOM节点树</a></strong>来保持追踪,如果你会画一张家谱树来追踪家庭成员的发展一样。</p> <p>HTML的DOM节点树如下图所示:</p> <p><img src="/sites/default/files/blogs/2018/1804/dom-tree.png" alt="" /></p> <p>每个元素都是一个节点。每片文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样,每个节点都可以有孩子节点 (也就是说每个部分可以包含其它的一些部分)。</p> <p>高效的更新所有这些节点会是比较困难的,不过所幸你不必再手动完成这个工作了。你只需要告诉 Vue 你希望页面上的 HTML 是什么,这可以是在一个模板里:</p> <pre><code>&lt;h1&gt;{{ blogTitle }}&lt;/h1&gt; </code></pre> <p>或者一个渲染函数里:</p> <pre><code>render: function (createElement) { return createElement('h1', this.blogTitle) } </code></pre> <p>在这两种情况下,Vue 都会自动保持页面的更新,即便 <code>blogTitle</code> 发生了改变。</p> <h2>虚拟DOM</h2> <p>在Vue 2.0中,渲染层的实现做了根本性改动,那就是引入了虚拟DOM。</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-2.png" alt="" /></p> <p>Vue的编译器在编译模板之后,会把这些模板编译成一个渲染函数。而函数被调用的时候就会渲染并且返回一个<strong>虚拟DOM的树</strong>。</p> <p>当我们有了这个虚拟的树之后,再交给一个<strong>Patch函数</strong>,负责把这些虚拟DOM真正施加到真实的DOM上。在这个过程中,Vue有自身的响应式系统来侦测在渲染过程中所依赖到的数据来源。在渲染过程中,侦测到数据来源之后就可以精确感知数据源的变动。到时候就可以根据需要重新进行渲染。当重新进行渲染之后,会生成一个新的树,将新的树与旧的树进行对比,就可以最终得出应施加到真实DOM上的改动。最后再通过Patch函数施加改动。</p> <p>简单点讲,在Vue的底层实现上,Vue将模板编译成虚拟DOM渲染函数。结合Vue自带的响应系统,在应该状态改变时,Vue能够智能地计算出重新渲染伟德1946手机版的最小代价并应到DOM操作上。</p> <p><img src="/sites/default/files/blogs/2017/1711/vue-r-1.png" alt="" /></p> <p>Vue支持我们通过<code>data</code>参数传递一个JavaScript对象做为伟德1946手机版数据,然后Vue将遍历此对象属性,使用<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-two-way-binding-object-defineproperty.html"><code>Object.defineProperty</code>方法</a>设置描述对象,通过存取器函数可以追踪该属性的变更,Vue创建了一层<code>Watcher</code>层,在伟德1946手机版渲染的过程中把属性记录为依赖,之后当依赖项的<code>setter</code>被调用时,会通知<code>Watcher</code>重新计算,从而使它关联的伟德1946手机版得以更新,如下图:</p>" <p><img src="/sites/default/files/blogs/2017/1711/Object-defineProperty-14.png" alt="" /></p> <p>有关于Vue的响应式相关的内容,可以阅读下列文章:</p> <ul> <li><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/understanding-vue-js-reactivity-depth-object-defineproperty.html">深入理解Vue.js响应式原理</a></li>" <li><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-two-way-binding-object-defineproperty.html">Vue双向绑定的实现原理<code>Object.defineproperty</code></a></li>" <li><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-two-way-binding.html">Vue的双向绑定原理及实现</a></li>" <li><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-reactivity.html">Vue中的响应式</a></li>" <li><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/reactive.html">从JavaScript属性描述器剖析Vue.js响应式视图</a></li>" </ul> <p>对于Vue自带的响应式系统,并不是咱们今天要聊的东西。我们还是回到Vue的虚拟DOM中来。对于虚拟DOM,咱们来看一个简单的实例,就是下图所示的这个,详细的阐述了<code>模板 → 渲染函数 → 虚拟DOM树 → 真实DOM</code>的一个过程</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-3.png" alt="" /></p> <p>其实Vue中的虚拟DOM还是很复杂的,我也是一知半解,如果你想深入的了解,可以阅读@JoeRay61的《<a href="http://xxysy.com/quot;//segmentfault.com/a/1190000008291645">Vue原理解析之Virtua" DOM</a>》一文。</p> <p>通过前面的学习,我们初步了解到Vue通过建立一个<strong>虚拟DOM</strong>对真实DOM发生的变化保持追踪。比如下面这行代码:</p> <pre><code>return createElement('h1', this.blogTitle) </code></pre> <p><code>createElement</code> 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 <code>createNodeDescription</code>,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,及其子节点。我们把这样的节点描述为“虚拟节点 (Virtual Node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 伟德1946手机版树建立起来的整个 VNode 树的称呼。</p> <p>Vue伟德1946手机版树建立起来的整个VNode树是唯一的。这意味着,下面的<strong><code>render</code>函数是无效</strong>的:</p> <pre><code>render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 错误-重复的 VNodes myParagraphVNode, myParagraphVNode ]) } </code></pre> <p>如果你真的需要重复很多次的元素/伟德1946手机版,你可以使用工厂函数来实现。例如,下面这个例子 <code>render</code> 函数完美有效地渲染了 <code>20</code> 个重复的段落:</p> <pre><code>render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) ) } </code></pre> <h2>Vue的渲染机制</h2> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-4.png" alt="" /></p> <p>上图展示的是独立构建时的一个渲染流程图。</p> <p>继续使用上面用到的模板到真实DOM过程的一个图:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-3.png" alt="" /></p> <p>这里会涉及到Vue的另外两个概念:</p> <ul> <li><strong>独立构建</strong>:包含模板编译器,渲染过程<code>HTML字符串 → render函数 → VNode → 真实DOM节点</code></li> <li><strong>运行时构建</strong>:不包含模板编译器,渲染过程<code>render函数 → VNode → 真实DOM节点</code></li> </ul> <p>运行时构建的包,会比独立构建少一个模板编译器。在<code>$mount</code>函数上也不同。而<code>$mount</code>方法又是整个渲染过程的起始点。用一张流程图来说明:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-5.png" alt="" /></p> <p>由此图可以看到,在渲染过程中,提供了三种渲染模式,自定义<code>render</code>函数、<code>template</code>、<code>el</code>均可以渲染页面,也就是对应我们使用Vue时,三种写法:</p> <h3>自定义<code>render函数</code></h3> <pre><code>Vue.component('anchored-heading', { render: function (createElement) { return createElement ( 'h' + this.level, // tag name标签名称 this.$slots.default // 子伟德1946手机版中的阵列 ) }, props: { level: { type: Number, required: true } } }) </code></pre> <h3><code>template</code>写法</h3> <pre><code>let app = new Vue({ template: `&lt;div&gt;{{ msg }}&lt;/div&gt;`, data () { return { msg: '' } } }) </code></pre> <h3><code>el</code>写法</h3> <pre><code>let app = new Vue({ el: '#app', data () { return { msg: 'Hello Vue!' } } }) </code></pre> <p>这三种渲染模式最终都是要得到<code>render</code>函数。只不过用户自定义的<code>render</code>函数省去了程序分析的过程,等同于处理过的<code>render</code>函数,而普通的<code>template</code>或者<code>el</code>只是字符串,需要解析成AST,再将AST转化为<code>render</code>函数。</p> <blockquote> <p><strong>记住一点,无论哪种方法,都要得到<code>render</code>函数。</strong></p> </blockquote> <p>我们在使用过程中具体要使用哪种调用方式,要根据具体的需求来。</p> <p>如果是比较简单的逻辑,使用<code>template</code>和<code>el</code>比较好,因为这两种都属于声明式渲染,对用户理解比较容易,但灵活性比较差,因为最终生成的<code>render</code>函数是由程序通过AST解析优化得到的;而使用自定义<code>render</code>函数相当于人已经将逻辑翻译给程序,能够胜任复杂的逻辑,灵活性高,但对于用户的理解相对差点。</p> <h2>理解<code>createElement</code></h2> <p>在使用<code>render</code>函数,其中还有另一个需要掌握的部分,那就是<code>createElement</code>。接下来我们需要熟悉的是如何在<code>createElement</code>函数中生成模板。那么我们分两个部分来对<code>createElement</code>进行理解。</p> <h3><code>createElement</code>参数</h3> <p><code>createElement</code>可以是接受多个参数:</p> <h4>第一个参数:<code>{String | Object | Function}</code></h4> <p>第一个参数对于<code>createElement</code>而言是一个必须的参数,这个参数可以是字符串<code>string</code>、是一个对象<code>object</code>,也可以是一个函数<code>function</code>。</p> <pre><code>&lt;div id="app"&gt; &lt;custom-element&gt;&lt;/custom-element&gt; &lt;/div&gt; Vue.component('custom-element', { render: function (createElement) { return createElement('div') } }) let app = new Vue({ el: '#app' }) </code></pre> <p>上面的示例,给<code>createElement</code>传了一个<code>String</code>参数<code>'div'</code>,即传了一个HTML标签字符。最后会有一个<code>div</code>元素渲染出来:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-6.png" alt="" /></p> <p>接着把上例中的<code>String</code>换成一个<code>Object</code>,比如:</p> <pre><code>Vue.component('custom-element', { render: function (createElement) { return createElement({ template: `&lt;div&gt;Hello Vue!&lt;/div&gt;` }) } }) </code></pre> <p>上例传了一个<code>{template: '&lt;div&gt;Hello Vue!&lt;/div&gt;'}</code>对象。此时<code>custom-element</code>伟德1946手机版渲染出来的结果如下:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-7.png" alt="" /></p> <p>除此之外,还可以传一个<code>Function</code>,比如:</p> <pre><code>Vue.component('custom-element', { render: function (createElement) { var eleFun = function () { return { template: `&lt;div&gt;Hello Vue!&lt;/div&gt;` } } return createElement(eleFun()) } }) </code></pre> <p>最终得到的结果和上图是一样的。这里传了一个<code>eleFun()</code>函数给<code>createElement</code>,而这个函数返回的是一个对象。</p> <h4>第二个参数:<code>{Object}</code></h4> <p><code>createElement</code>是一个可选参数,这个参数是一个<code>Object</code>。来看一个小示例:</p> <pre><code>&lt;div id="app"&gt; &lt;custom-element&gt;&lt;/custom-element&gt; &lt;/div&gt; Vue.component('custom-element', { render: function (createElement) { var self = this // 第一个参数是一个简单的HTML标签字符 “必选” // 第二个参数是一个包含模板相关属性的数据对象 “可选” return createElement('div', { 'class': { foo: true, bar: false }, style: { color: 'red', fontSize: '14px' }, attrs: { id: 'boo' }, domProps: { innerHTML: 'Hello Vue!' } }) } }) let app = new Vue({ el: '#app' }) </code></pre> <p>最终生成的DOM,将会带一些属性和内容的<code>div</code>元素,如下图所示:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-8.png" alt="" /></p> <h4>第三个参数:{String | Array}</h4> <p><code>createElement</code>还有第三个参数,这个参数是可选的,可以给其传一个<code>String</code>或<code>Array</code>。比如下面这个小示例:</p> <pre><code>&lt;div id="app"&gt; &lt;custom-element&gt;&lt;/custom-element&gt; &lt;/div&gt; Vue.component('custom-element', { render: function (createElement) { var self = this return createElement( 'div', // 第一个参数是一个简单的HTML标签字符 “必选” { class: { title: true }, style: { border: '1px solid', padding: '10px' } }, // 第二个参数是一个包含模板相关属性的数据对象 “可选” [ createElement('h1', 'Hello Vue!'), createElement('p', '开始学习Vue!') ] // 第三个参数是传了多个子元素的一个数组 “可选” ) } }) let app = new Vue({ el: '#app' }) </code></pre> <p>最终的效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-9.png" alt="" /></p> <p>其实从上面这几个小例来看,不难发现,以往我们使用<code>Vue.component()</code>创建伟德1946手机版的方式,都可以用<code>render</code>函数配合<code>createElement</code>来完成。你也会发现,使用<code>Vue.component()</code>和<code>render</code>各有所长,正如文章开头的一个示例代码,就不适合<code>Vue.component()</code>的<code>template</code>,而使用<code>render</code>更方便。</p> <p>接下来看一个小示例,看看<code>template</code>和<code>render</code>方式怎么创建相同效果的一个伟德1946手机版:</p> <pre><code>&lt;div id="app"&gt; &lt;custom-element&gt;&lt;/custom-element&gt; &lt;/div&gt; Vue.component('custom-element', { template: `&lt;div id="box" :class="{show: show}" @click="handleClick"&gt;Hello Vue!&lt;/div&gt;`, data () { return { show: true } }, methods: { handleClick: function () { console.log('Clicked!') } } }) </code></pre> <p>上面<code>Vue.component()</code>中的代码换成<code>render</code>函数之后,可以这样写:</p> <pre><code>Vue.component('custom-element', { render: function (createElement) { return createElement('div', { class: { show: this.show }, attrs: { id: 'box' }, on: { click: this.handleClick } }, 'Hello Vue!') }, data () { return { show: true } }, methods: { handleClick: function () { console.log('Clicked!') } } }) </code></pre> <p>最后声明一个Vue实例,并挂载到<code>id</code>为<code>#app</code>的一个元素上:</p> <pre><code>let app = new Vue({ el: '#app' }) </code></pre> <h3><code>createElement</code>解析过程</h3> <p>简单的来看一下<code>createElement</code>解析的过程,这部分需要对JS有一些功底。不然看起来有点蛋疼:</p> <pre><code>const SIMPLE_NORMALIZE = 1 const ALWAYS_NORMALIZE = 2 function createElement (context, tag, data, children, normalizationType, alwaysNormalize) { // 兼容不传data的情况 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } // 如果alwaysNormalize是true // 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值 if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE // 调用_createElement创建虚拟节点 return _createElement(context, tag, data, children, normalizationType) } function _createElement (context, tag, data, children, normalizationType) { /** * 如果存在data.__ob__,说明data是被Observer观察的数据 * 不能用作虚拟节点的data * 需要抛出警告,并返回一个空节点 * * 被监控的data不能被用作vnode渲染的数据的原因是: * data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作 */ if (data &amp;&amp; data.__ob__) { process.env.NODE_ENV !== 'production' &amp;&amp; warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // 当伟德1946手机版的is属性被设置为一个falsy的值 // Vue将不会知道要把这个伟德1946手机版渲染成什么 // 所以渲染一个空节点 if (!tag) { return createEmptyVNode() } // 作用域插槽 if (Array.isArray(children) &amp;&amp; typeof children[0] === 'function') { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } // 根据normalizationType的值,选择不同的处理方法 if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns // 如果标签名是字符串类型 if (typeof tag === 'string') { let Ctor // 获取标签名的命名空间 ns = config.getTagNamespace(tag) // 判断是否为保留标签 if (config.isReservedTag(tag)) { // 如果是保留标签,就创建一个这样的vnode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) // 如果不是保留标签,那么我们将尝试从vm的components上查找是否有这个标签的定义 } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) { // 如果找到了这个标签的定义,就以此创建虚拟伟德1946手机版节点 vnode = createComponent(Ctor, data, context, children, tag) } else { // 兜底方案,正常创建一个vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } // 当tag不是字符串的时候,我们认为tag是伟德1946手机版的构造类 // 所以直接创建 } else { vnode = createComponent(tag, data, context, children) } // 如果有vnode if (vnode) { // 如果有namespace,就应用下namespace,然后返回vnode if (ns) applyNS(vnode, ns) return vnode // 否则,返回一个空节点 } else { return createEmptyVNode() } } } </code></pre> <p>简单的梳理了一个流程图,可以参考下</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-10.png" alt="" /></p> <blockquote> <p>这部分代码和流程图来自于@JoeRay61的《<a href="http://xxysy.com/quot;//segmentfault.com/a/1190000008291645">Vue原理解析之Virtua" DOM</a>》一文。</p> </blockquote> <h2>使用JavaScript代替模板功能</h2> <p>在使用Vue模板的时候,我们可以在模板中灵活的使用<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/v-if-vs-v-show.html"><code>v-if</code></a>、<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/v-for.html"><code>v-for</code></a>、<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/v-model.html"><code>v-model</code></a>和<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-slot.html"><code>&lt;slot&gt;</code></a>之类的。但在<code>render</code>函数中是没有提供专用的API。如果在<code>render</code>使用这些,需要使用原生的JavaScript来实现。</p>" <h3><code>v-if</code>和<code>v-for</code></h3> <p>在<code>render</code>函数中可以使用<code>if/else</code>和<code>map</code>来实现<code>template</code>中的<code>v-if</code>和<code>v-for</code>。</p> <pre><code>&lt;ul v-if="items.length"&gt; &lt;li v-for="item in items"&gt;{{ item }}&lt;/li&gt; &lt;/ul&gt; &lt;p v-else&gt;No items found.&lt;/p&gt; </code></pre> <p>换成<code>render</code>函数,可以这样写:</p> <pre><code>Vue.component('item-list',{ props: ['items'], render: function (createElement) { if (this.items.length) { return createElement('ul', this.items.map((item) =&gt; { return createElement('item') })) } else { return createElement('p', 'No items found.') } } }) &lt;div id="app"&gt; &lt;item-list :items="items"&gt;&lt;/item-list&gt; &lt;/div&gt; let app = new Vue({ el: '#app', data () { return { items: ['大漠', 'W3cplus', 'blog'] } } }) </code></pre> <p>得到的效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-11.gif" alt="" /></p> <h3><code>v-model</code></h3> <p><code>render</code>函数中也没有与<code>v-model</code>相应的API,如果要实现<code>v-model</code>类似的功能,同样需要使用原生JavaScript来实现。</p> <pre><code>&lt;div id="app"&gt; &lt;el-input :name="name" @input="val =&gt; name = val"&gt;&lt;/el-input&gt; &lt;/div&gt; Vue.component('el-input', { render: function (createElement) { var self = this return createElement('input', { domProps: { value: self.name }, on: { input: function (event) { self.$emit('input', event.target.value) } } }) }, props: { name: String } }) let app = new Vue({ el: '#app', data () { return { name: '大漠' } } }) </code></pre> <p>刷新你的浏览器,可以看到效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-render-12.png" alt="" /></p> <p>这就是深入底层要付出的,尽管麻烦了一些,但相对于 <code>v-model</code> 来说,你可以更灵活地控制。</p> <h3>插槽</h3> <p>你可以从<code>this.$slots</code>获取VNodes列表中的静态内容:</p> <pre><code>render: function (createElement) { // 相当于 `&lt;div&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/div&gt;` return createElement('div', this.$slots.default) } </code></pre> <p>还可以从<code>this.$scopedSlots</code>中获得能用作函数的作用域插槽,这个函数返回VNodes:</p> <pre><code>props: ['message'], render: function (createElement) { // `&lt;div&gt;&lt;slot :text="message"&gt;&lt;/slot&gt;&lt;/div&gt;` return createElement('div', [ this.$scopedSlots.default({ text: this.message }) ]) } </code></pre> <p>如果要用渲染函数向子伟德1946手机版中传递作用域插槽,可以利用VNode数据中的<code>scopedSlots</code>域:</p> <pre><code>&lt;div id="app"&gt; &lt;custom-ele&gt;&lt;/custom-ele&gt; &lt;/div&gt; Vue.component('custom-ele', { render: function (createElement) { return createElement('div', [ createElement('child', { scopedSlots: { default: function (props) { return [ createElement('span', 'From Parent Component'), createElement('span', props.text) ] } } }) ]) } }) Vue.component('child', { render: function (createElement) { return createElement('strong', this.$scopedSlots.default({ text: 'This is Child Component' })) } }) let app = new Vue({ el: '#app' }) </code></pre> <h2>JSX</h2> <p>如果写习惯了<code>template</code>,然后要用<code>render</code>函数来写,一定会感觉好痛苦,特别是面对复杂的伟德1946手机版的时候。不过我们在Vue中使用JSX可以让我们回到更接近于模板的语法上。</p> <pre><code>import AnchoredHeading from './AnchoredHeading.vue' new Vue({ el: '#demo', render: function (h) { return ( &lt;AnchoredHeading level={1}&gt; &lt;span&gt;Hello&lt;/span&gt; world! &lt;/AnchoredHeading&gt; ) } }) </code></pre> <blockquote> <p>将 <code>h</code> 作为 <code>createElement</code> 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的,如果在作用域中 <code>h</code> 失去作用,在应用中会触发报错。</p> </blockquote> <h2>总结</h2> <p>回过头来看,Vue中的渲染核心关键的几步流程还是非常清晰的:</p> <ul> <li><code>new Vue</code>,执行初始化</li> <li>挂载<code>$mount</code>方法,通过自定义<code>render</code>方法、<code>template</code>、<code>el</code>等生成<code>render</code>函数</li> <li>通过<code>Watcher</code>监听数据的变化</li> <li>当数据发生变化时,<code>render</code>函数执行生成VNode对象</li> <li>通过<code>patch</code>方法,对比新旧VNode对象,通过DOM Diff算法,添加、修改、删除真正的DOM元素</li> </ul> <p>至此,整个<code>new Vue</code>的渲染过程完毕。</p> <p>而这篇文章,主要把精力集中在<code>render</code>函数这一部分。学习了怎么用<code>render</code>函数来创建伟德1946手机版,以及了解了其中<code>createElement</code>。</p> <p>最后要说的是,上文虽然以学习<code>render</code>函数,但文中涉及了Vue不少的知识点,也有点零乱。初学者自己根据自己获取所要的知识点。由于本人也是初涉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/vue-render-function.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-render-function.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/vue2"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue 2.0学习笔记</a></div></div></div> Sat, 21 Apr 2018 02:40:30 +0000 Airen 2391 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-extend.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/blog/tags/642.html">Vue的伟德1946手机版</a>,虽然中间浅尝了Vue伟德1946手机版的基础,体验了其魅力,但还是有很多深层的东西未掌握,也还不能非常灵活的使用Vue的伟德1946手机版。但这一切并不重要,随着后面的学习,我想会对Vue越来越熟悉。这两天在看Vue中的<code>Vue.extend</code>构造器,今天简单的对这方面的东西做个笔记。</p>" <h2>什么是Vue.extend</h2> <p><code>Vue.extend</code>返回的是一个<strong>扩展实例构造器</strong>,也就是预设了部分选项的Vue实例构造器。其主要用来服务于<code>Vue.component</code>,用来生成伟德1946手机版。可以简单的理解为当在模板中遇到该伟德1946手机版名称作为标签的自定义元素时,会自动调用扩展实例构造器来生产伟德1946手机版实例,并挂载到自定义元素上。</p> <h2>创建一个Vue.extend</h2> <p>在学习<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-instances-and-life-cycles.html">Vue实例和生命周期</a>时,了解了在Vue中创建一个Vue实例,大致经会经历四个过程:</p>" <p><img src="/sites/default/files/blogs/2017/1711/vue-instances-and-life-cycles-2.png" alt="" /></p> <p>当Vue实例创建之后,可以看到Vue实例预设了很多参数:</p> <p><img src="/sites/default/files/blogs/2017/1711/vue-instances-and-life-cycles-3.png" alt="" /></p> <p>那么创建一个<code>Vue.extend</code>其实也有点类似,首先通过<code>Vue.extend()</code>来创建一个扩展实例构造器:</p> <pre><code>let baseExtend = Vue.extend({ template: `&lt;p&gt; {{ firstName }} {{ lastName }} aka {{ alias }}&lt;/p&gt;`, data () { return { firstName: '大漠', lastName: '伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】', alias: '大漠_伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】' } }, created () { console.log('onCreated-1'); } }) </code></pre> <p>为了后面更好的使用<code>Vue.extend()</code>中的<code>options</code>,直接把上面示例中的一些选项提出来,这些选项其实就是前面所说的预设选项:</p> <pre><code>let baseOptions = { template: `&lt;p&gt; {{ firstName }} {{ lastName }} aka {{ alias }}&lt;/p&gt;`, data () { return { firstName: '大漠', lastName: '伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】', alias: '大漠_伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】' } } } </code></pre> <p>这个时候可以这样使用:</p> <pre><code>let baseExtend = Vue.extend(baseOptions); </code></pre> <p>可以借用<code>new</code>关键词来声明这个构造器:</p> <pre><code>new baseExtend() </code></pre> <p>可以看到,这个有<code>new Vue()</code>有点类似,除了我们预设的一些东西之外,Vue还给其预设了很多其他的东西:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-extend-1.png" alt="" /></p> <p>Vue的实例最终是要挂载到一个元素上,那么<code>Vue.extend</code>也是一样的,也需要挂载到一个元素上:</p> <pre><code>new baseExtend().$mount('#app') </code></pre> <p>我尝试了一下像Vue的实例挂载的方式:</p> <pre><code>let app = new baseExtend({ el: '#app' }) </code></pre> <p>最终得到的效果是相同的。除此之外,还可以将其挂载到自定义的标签上,比如:</p> <pre><code>&lt;!-- HTML --&gt; &lt;custom-element&gt;&lt;/custom-element&gt; &lt;!-- Script --&gt; new baseExtend().$mount('custom-element') </code></pre> <p>结果如下:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-extend-2.png" alt="" /></p> <p>整个过程是不是有点类似于Vue实例创建的过程:</p> <p><img src="/sites/default/files/blogs/2018/1804/vue-extend-3.png" alt="" /></p> <h2>Vue.extend的作用</h2> <p><code>Vue.extend</code>常和Vue的伟德1946手机版配合在一起使用。简单点说:<strong><code>Vue.extend</code>是构造一个伟德1946手机版的语法器,你给这个构造器预设一些参数,而这个构造器给你一个伟德1946手机版,然后这个伟德1946手机版你就可以用到<code>Vue.component</code>这个全局注册方法里,也可以在任意Vue模板里使用这个构造器</strong>。</p> <p>基于上面的实例,咱们通过<code>Vue.extend</code>构建了一个<code>baseExtend()</code>构造器,这个构造器具有<code>baseOptions</code>设置的参数。</p> <p>咱们先忽略上面这段描述,来看<code>Vue.component()</code>创建的伟德1946手机版:</p> <pre><code>Vue.component('base-component',baseOptions) let app = new Vue({ el: '#app' }) &lt;!-- HTMl --&gt; &lt;div id="app"&gt; &lt;base-component&gt;&lt;/base-component&gt; &lt;/div&gt; </code></pre> <p>如果我们把<code>Vue.component()</code>中的<code>baseOptions</code>换成前面已创建的扩展器<code>baseExtend</code>:</p> <pre><code>Vue.component('base-component',baseExtend) // 其等于 Vue.component('base-component', Vue.extend(baseOptions)) </code></pre> <p>可以看到效果是相同的。</p> <p>也就是说:</p> <pre><code>Vue.component('base-component', Vue.extend(baseOptions)) Vue.component('base-component',baseExtend) Vue.component('base-component',baseOptions) </code></pre> <p>三者是等同的。即:</p> <blockquote> <p><code>Vue.component()</code>会注册一个全局的伟德1946手机版,其会自动判断第二个传进来的是Vue继续对象(<code>Vue.extend</code>)还是普通对象(<code>{...}</code>),如果传进来的是普能对象的话会自动调用<code>Vue.extend</code>,所以你先继承再传,还是直接传普通对象对<code>Vue.component()</code>的最终结果是没差的。</p> </blockquote> <h2>Vue.extend()和Vue.component()的区别</h2> <p>Vue允许你将注册的ViewModel构造函数视为可重用的伟德1946手机版,这些伟德1946手机版在概念上与<a href="http://xxysy.com/quot;//www.w3.org/TR/components-intro/">Web伟德1946手机版</a>相似,不需要任何Polyfill。要注册一个伟德1946手机版,首先使用<code>Vue.extend()</code>创建一个Vue的子类构造函数,然后使用<code>Vue.component()</code>方法注册该构造函数:</p>" <pre><code>// 扩展Vue来获取可重用的构造函数 var MyComponent = Vue.extend({ template: '一个自定义的伟德1946手机版!' }) // 使用`id:my-component`注册构造函数 Vue.component('my-component', MyComponent) </code></pre> <p>平时我们看到的是直接在<code>Vue.component()</code>中使用<code>options</code>对象,而不是实际的构造函数来注册一个伟德1946手机版,比如:</p> <pre><code>Vue.component('my-component', { template: '一个自定义的伟德1946手机版!' }) </code></pre> <p>其实上面这段代码隐式的调用了<code>Vue.extend()</code>,然后注册返回的构造函数。当你不需要编程实例化伟德1946手机版时,使用此语法。注意:这个方法返回的是<code>Vue</code>,而不是注册的构造函数。</p> <p>然后,你可以在父ViewModel的<code>template</code>中使用它:</p> <pre><code>&lt;div v-component="my-component"&gt;&lt;/div&gt; </code></pre> <p>如果你喜欢,伟德1946手机版也可以使用自定义元素标记的形式:</p> <pre><code>&lt;my-component&gt;&lt;/my-component&gt; </code></pre> <blockquote> <p>为了避免使用的时候与原生元素命名冲突,并且与W3C自定义元素规范保持一致,伟德1946手机版的<code>ID</code>必须包含一个连字符<code>-</code>作为自定义标签。</p> </blockquote> <p>理解<code>Vue.extend()</code>和<code>Vue.component()</code>是很重要的。由于Vue本身是一个构造函数(<code>constructor</code>),<code>Vue.extend()</code>是一个继承于方法的类(<code>class</code>),参数是一个包含伟德1946手机版选项的对象。它的目的是创建一个Vue的子类并且返回相应的构造函数。而<code>Vue.component()</code>实际上是一个类似于<code>Vue.directive()</code>和<code>Vue.filter()</code>的注册方法,它的目的是给指定的一个构造函数与一个字符串<code>ID</code>关联起来。之后Vue可以把它用作模板,实际上当你直接传递选项给<code>Vue.component()</code>的时候,它会在背后调用<code>Vue.extend()</code>。</p> <p>Vue.js支持两种不同的API模型:一种是基于类的,命令式的,Backbone 类型的API;另一种是基于标记语言的,声明式的,Web伟德1946手机版类型的API。如果还是困惑的话,可以想象你是怎么通过<code>new Image()</code>或者 <code>&lt;img&gt;</code>标签创建 <code>image</code>元素的就知道了。这两种方法都对指定的类型很有用,Vue提供这两者只是为了更好的灵活性。</p> <h2>总结</h2> <p>这篇文章简单的了解了Vue中的<code>Vue.extend()</code>相关的基础知识。另外简单的学习了<code>Vue.extend()</code>和<code>Vue.component()</code>之间的关系。本来想看看<code>Vue.extend()</code>一般用于什么情景之下,目前看到在写Vue插件的时候,会时常运用到<code>Vue.extend()</code>,我想后续在学习这方面知识的时候,我们应该又会回来重温<code>Vue.extend()</code>。另外,在学习中我发现,在Vue中,还有一个<code>extends</code>和<code>Vue.mixin</code>。感觉好乱,要学的东西特多,昨天就把<code>Vue.extend()</code>和<code>extends</code>给混淆了。我想有必要花点时间来理清楚这些基础的东东。</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/vue-extend.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/vue-extend.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/vue2"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue 2.0学习笔记</a></div></div></div> Fri, 20 Apr 2018 08:37:20 +0000 Airen 2390 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/scooped-corners.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="//css-tricks.com/author/thebabydino/">@ANA TUDOR</a>翻译的《<a href="//css-tricks.com/scooped-corners-in-2018/">Scooped Corners in 2018</a>》一文。</p> </blockquote> <p>记得@Lea Verou的<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/css-secrets/cutout-corners.html">《CSS Secrets》一书</a>和前几天<a href="//css-tricks.com/notched-boxes/">@Chris Coyier刚发的帖子</a>都介绍了CSS怎么实现元素斜切口的效果。我也尝试着借助Vue的能力,把这种效果构建成<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-notched-boxes-component-with-vue.html">一个Vue伟德1946手机版</a>。我把这种效果定义为<strong>外切口</strong>。而今天将要聊的是与其刚好相反的一个效果:<strong>CSS如何实现内凹角的效果</strong>。</p>" <p><img src="/sites/default/files/blogs/2018/1804/design.png" alt="" /></p> <p>上图展示的效果就是接下来所要聊的内凹角的效果。也就是说,通过下文的介绍,我们可以知道这种效果是如何做的,而且如何在多个元素上实现这样的内凹角效果。在实现这样的效果当中,将会遇到些什么棘手的问题,又是怎么绕过这些问题的。</p> <h2>最初的想法:<code>box-shadow</code></h2> <p>对于<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/content/css3-box-shadow"><code>box-shadow</code>的属性</a>,想必大家已经非常了解了,如果你从未接触过<code>box-shadow</code>属性,那么强烈建议您花一点时间去了解一下<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/28.html"><code>box-shadow</code>相关的知识</a>。这样能帮助你更好的理解后续的内容。</p>" <p>我先假设你对<code>box-shadow</code>有了一定的了解。就算你不了解,也没有关系。你也可以继续后面的内容。假设我们有一个<code>div</code>的元素。给这个元素添加了一个<code>.box</code>的类名:</p> <pre><code>&lt;div class="box"&gt;&lt;/div&gt; </code></pre> <p>我们可以显式的给这个<code>.box</code>元素设置大小或者通过其自己的内容来决定大小,不管是哪种方式,都并不很重要。这里为了简单起见,给其设置了<code>max-width</code>和<code>min-height</code>(也是用来设置其大小的)。另外为了能在浏览器中看到效果,其添加了一个<code>outline</code>的效果,让其看起来有边框的样子。或许你会问,为什么不直接使用<code>border</code>呢?这个问题留给大家去思考吧,因为不是这篇文章要探讨的内容。</p> <pre><code>.box { outline: solid 2px; max-width: 15em; min-height: 10em; } </code></pre> <p>接下来,通过伪元素<code>::before</code>来创建一个正方形,其边长等于圆角的直径(或者半径<code>--r</code>的两倍),而且对这个伪元素使用绝对定位。另外为了能在浏览器中看到效果,给这个伪元素添加了一个<code>box-shadow</code>和<code>background</code>属性。这只是用来辅助大家理解的,后续会删除的。</p> <pre><code>:root { --r: 2em; } .box { position: relative; &amp;::before { content: ''; position: absolute; padding: var(--r); box-shadow: 0 0 7px #b53; background: #95a; } } </code></pre> <blockquote> <p>特别声明:本文的实例代码都来自于<a href="//css-tricks.com/author/thebabydino/">@ANA TUDOR</a>的《<a href="//css-tricks.com/scooped-corners-in-2018/">Scooped Corners in 2018</a>》一文。不同的是我把文章中的Sass变量换成了CSS自定义变量。后续内容如无特别说明,都将类似的做了修改。</p> </blockquote> <p>这个时候看到的效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="KRKvdE" src="//codepen.io/airen/embed/KRKvdE?height=300&amp;theme-id=0&amp;slug-hash=KRKvdE&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="300" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>效果如你所期望的一样。接下来对伪元素<code>::before</code>的<code>border-radius</code>值设置为<code>50%</code>,让它成为一个圆形,并且给它设置一个<code>margin</code>的负值,值等于它的半径<code>--r</code>。伪元素的中心点和它的父容器<code>.box</code>的左上角(<code>0,0</code>)重合。为了让溢出的<code>.box</code>的伪元素能隐藏起来,需要在<code>.box</code>中添加一个<code>overflow:hidden</code>。</p> <pre><code>:root { --r: 2em; } .box { position: relative; overflow: hidden; &amp;::before { content: ''; position: absolute; padding: var(--r); box-shadow: 0 0 7px #b53; background: #95a; margin: calc(var(--r) * (-1)); border-radius: 50%; } } </code></pre> <p>现在的结果是这样的:</p> <div style="margin-bottom: 20px;"><iframe id="RywZgQ" src="//codepen.io/airen/embed/RywZgQ?height=300&amp;theme-id=0&amp;slug-hash=RywZgQ&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="300" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>但这样的效果仍然不是我们想要的。为了达到我们想要的效果,我们需要使用<code>box-shadow</code>的第四个参数值:<strong><a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/css3-box-shadows-unnoticed-spread">阴影扩展半径</a></strong>。如果你想了解<code>box-shadow</code>添加第四个参数值的效果,可以看下面这个Demo:</p> <div style="margin-bottom: 20px;"><iframe id="deyzJV" src="//codepen.io/airen/embed/deyzJV?height=300&amp;theme-id=0&amp;slug-hash=deyzJV&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="300" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>你可能已经猜到我们下一步要做什么了。把<code>background</code>和<code>box-shadow</code>前三个值(<code>x</code>和<code>y</code>轴的偏移值以及模糊半径)设置为<code>0</code>,并给<code>box-shadow</code>的扩展半径设置为一个较大的值。</p> <pre><code>box-shadow: 0 0 0 300px; </code></pre> <p>下面的这个示例演示了<code>box-shadow</code>的扩展半径如何让阴影效果覆盖容器更多的面积。</p> <div style="margin-bottom: 20px;"><iframe id="gzOxdB" src="//codepen.io/airen/embed/gzOxdB?height=300&amp;theme-id=0&amp;slug-hash=gzOxdB&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="300" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>这里用到的一个技巧是让<code>box-shadow</code>有足够大的扩展半径,这样让伪元素的阴影能覆盖其容器更多的面积。这是非常有意思的一点,给<code>.box</code>设置<code>box-shadow</code>以及给其伪元素添加一个半透明的阴影效果。</p> <pre><code>.box { overflow: hidden; position: relative; margin: .25em auto; min-width: 15em; max-width: 15em; min-height: 10em; border-radius: 1em; &amp;:before { position: absolute; margin: calc(var(--r) * -1); padding: var(--r); border-radius: 50%; box-shadow: 0 0 0 300px rgba(#95a, .75); content: '' } } </code></pre> <div style="margin-bottom: 20px;"><iframe id="qYBXgV" src="//codepen.io/airen/embed/qYBXgV?height=400&amp;theme-id=0&amp;slug-hash=qYBXgV&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>box-shadow</code>实现的。或许你和我一样会纳闷,<code>box-shadow</code>是如何实现透明凹角的效果。事实并非如此,透明凹角部分是伪元素<code>::before</code>的<code>background-color</code>为<code>transparent</code>,而整个紫色部分是由<code>::before</code>的<code>box-shadow</code>实现的(就是阴影扩散半径有足够大的值,能铺满<code>.box</code>的容器大小)。我录一个视频给大家看看,或许能比文字更好的说明一切原理:</p> <p><img src="/sites/default/files/blogs/2018/1804/corner-1.gif" alt="" /></p> <p>是不是一图胜过千言万语呀。</p> <p>上面看到的效果,不难发现,凹角的大小是固定的。好在我们这里使用了CSS的自定义属性。因为使用CSS自定义属性之后,可以很容易的通过JavaScript来修改这个属性。这样一来,就可以很好的控制凹角的大小。比如:</p> <pre><code>:root { --r: 50px } .box { padding: var(--r); &amp;:before { margin: calc(-1*var(--r)); padding: inherit; } } </code></pre> <p>这是实现凹角效果的关键样式。具体的不多说了,能只要仔细阅读上面的内容,你就能明白为什么。</p> <p>值得一提的是,我们前面看到的效果都是<code>.box</code>中没有任何内容。也就是说<code>.box</code>里有内容的时候,我们是需要在样式上做一定的调整的。为什么这么说呢?先来看一个效果:</p> <p><img src="/sites/default/files/blogs/2018/1804/corner-2.png" alt="" /></p> <p>要解决这个问题,很简单,咱们只需要在<code>.box</code>的伪元素<code>::before</code>上添加<code>z-index</code>属性,并且给其设置值为<code>-1</code>。</p> <p>另外通过<code>.setProperty()</code>来修改<code>--r</code>的值。这需要一些JavaScript代码来支持:</p> <pre><code>// 获取id为r的input元素 和 output元素 const _R = document.getElementById('r'), _O = _R.nextElementSibling.querySelector('output'); // 设置一个变量v let v; // 创建一个update函数,更新--r的值 function update() { if(v !== +_R.value) { document.body.style.setProperty(`--r`, `${_O.value = v = +_R.value}px`) } }; update(); _R.addEventListener('change', update, false); _R.addEventListener('input', update, false); </code></pre> <p>最终效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="mLdppL" src="//codepen.io/airen/embed/mLdppL?height=400&amp;theme-id=0&amp;slug-hash=mLdppL&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>box-shadow</code>给单个<code>.box</code>设置单个凹角的效果。那么如果我们想要给一个元素添加四个凹角效果,怎么实现呢?想想,如果你想得出来,可以立马动手试试,就算你想不出来,也并不要紧,后面我们会介绍怎么给<code>.box</code>盒子的每个角添加凹角的效果。</p> <p>那么到这一步,咱们先暂停一下。上面我们看到的是CSS的自定义属性和JavaScript来实现想要的凹角效果。那么咱们先暂停一步,来看看怎么通过Vue来实现上面示例的效果。</p> <div style="margin-bottom: 20px;"><iframe id="jxOYvN" src="//codepen.io/airen/embed/jxOYvN?height=400&amp;theme-id=0&amp;slug-hash=jxOYvN&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>有关于Vue的代码这里就不展示了。详细的可以查看上面Demo的代码,其实你还可以添加其他的参数,比如除了给<code>scooped-corners</code>伟德1946手机版传凹角半径值之外,还可以传<code>border-radius</code>和<code>background-color</code>之类。感兴趣的可以尝试一下,并且欢迎在下面的评论中分享您的成果。</p> <h2>就用这种技术</h2> <p>接下来,咱们再深入一点,看看怎么运用这种技术来实现文章开头展示的效果。这里有一点不一样,伪元素的中心点与盒子不致,但他们都有一个共同点,伪元素的中心点,在每个盒子的顶点处。</p> <p>使用的HTML结构非常简单,这里使用了<code>4</code>个<code>&lt;article&gt;</code>元素(相当于前面所讲的<code>.box</code>元素),在<code>&lt;article&gt;</code>元素中包含了一些文本内容:</p> <pre><code>&lt;article&gt; &lt;h3&gt;Yogi Bear&lt;/h3&gt; &lt;section&gt; &lt;p&gt;Smaaaarter than the average bear!&lt;/p&gt;&lt;a href="http://xxysy.com/quot;#"&gt;go&lt;/a&gt" &lt;/section&gt; &lt;/article&gt; ... </code></pre> <p><code>&lt;body&gt;</code>包含了四个<code>&lt;article&gt;</code>元素,还有一个<code>&lt;header&gt;</code>元素,整个布局效果采用的是Flexbox。从文章开头的效果上来看,<code>&lt;header&gt;</code>宽度非常宽,然后每行有一个个或两个<code>&lt;article&gt;</code>元素。具体每行展示一个还是两个,这取决于浏览器视窗的宽度。</p> <p><img src="/sites/default/files/blogs/2018/1804/design.png" alt="" /></p> <p>如果我们每一行只有一个<code>&lt;article&gt;</code>时,那么元素上就不会有凹角的效果,这个时候需要把凹角的半径设置为<code>0</code>。否则我们就要设置一个非零的半径,也就是说<code>--r</code>的值不为<code>0</code>。</p> <pre><code>:root { --minW: 15rem; /* 每个article元素的最小宽度 */ --m: 1rem; /* 每个article元素的margin值 */ --r: 0px; /* 每个article元素上凹角的半径 */ } article { margin: var(--m); min-width: var(--minW); width: 21em; } @media (min-width: 2*($min-w + 2*$m)) { html { --r: 4rem; } article { width: 40%; } } </code></pre> <blockquote> <p>特别注意,CSS自定义属性不能用于媒体查询的条件中,但可以用于媒体查询的区块内。</p> </blockquote> <p>现在我们考虑一下,每行有两个<code>&lt;article&gt;</code>元素(当然,每个元素都有一个内凹角,因为这个才是我们感兴趣的东东)。</p> <p>第一个元素中,凹角的圆形在它的父元素的最右边边,也就是<code>left:100%</code>。为了将凹角圆中心点<code>x</code>坐标移到其父元素的右边缘,我们就需要减去圆的半径<code>--r</code>,那么<code>left</code>的值就变成了<code>calc(100% - var(--r))</code>。但我们不想让它出现在右边,而是希望它在<code>&lt;article&gt;</code>元素向右移<code>--m</code>。这样我们就可以算出我们最终想要的一个值:</p> <pre><code>left: calc(100% - var(--r) + var(--m)); </code></pre> <div style="margin-bottom: 20px;"><iframe id="MGWVNR" src="//codepen.io/airen/embed/MGWVNR?height=400&amp;theme-id=0&amp;slug-hash=MGWVNR&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>y</code>轴,我们给凹角设置<code>top: 100%</code>,可以把整个凹角圆移到盒子的底部边缘,同样让圆心能和边缘重合,需要把凹角上移凹角的半径<code>--r</code>。如此一来,<code>top</code>的值就变成了<code>calc(100% - var(--r))</code>。最后,类似于<code>left</code>一样,为了让凹角中心点在其父元素底线边缘下,需要加上一定的偏移量<code>--m</code>。</p> <pre><code>top: calc(100% - var(--r) + var(--m)); </code></pre> <div style="margin-bottom: 20px;"><iframe id="yjLjad" src="//codepen.io/airen/embed/yjLjad?height=400&amp;theme-id=0&amp;slug-hash=yjLjad&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>&lt;article&gt;</code>元素(同一行的第二部分),其垂直方向的偏移量具有相同的值:</p> <div style="margin-bottom: 20px;"><iframe id="JvjvWb" src="//codepen.io/airen/embed/JvjvWb?height=400&amp;theme-id=0&amp;slug-hash=JvjvWb&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>left</code>处<code>0%</code>开始,如此要把凹角放到元素左边缘上,需要向左移动一个<code>--r</code>,如此得到<code>left:calc(0% - var(--r))</code>。然而,最后的位置,咱们同样还需要向左偏移<code>--m</code>。最终<code>left</code>的值:</p> <pre><code>left: calc(0% - var(--r) - var(--m)); </code></pre> <div style="margin-bottom: 20px;"><iframe id="VxwxzM" src="//codepen.io/airen/embed/VxwxzM?height=400&amp;theme-id=0&amp;slug-hash=VxwxzM&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>&lt;article&gt;</code>元素,在<code>x</code>轴上的偏移量与第一个<code>&lt;article&gt;</code>相同:</p> <div style="margin-bottom: 20px;"><iframe id="erYreN" src="//codepen.io/airen/embed/erYreN?height=400&amp;theme-id=0&amp;slug-hash=erYreN&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>top: 0%</code>。如果把凹角圆中心点放在父容器的顶部缘,同样要向上移动一个半径<code>--r</code>,如此得到<code>top: calc(0% - var(--r))</code>。但要把凹角的圆心高于父容器的顶部边缘,那么还需要向上移动<code>--m</code>。这样可以得到<code>top</code>的值:</p> <pre><code>top: calc(0% - var(--r) - var(--m)); </code></pre> <div style="margin-bottom: 20px;"><iframe id="ZoEoob" src="//codepen.io/airen/embed/ZoEoob?height=400&amp;theme-id=0&amp;slug-hash=ZoEoob&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="PeoeBy" src="//codepen.io/airen/embed/PeoeBy?height=400&amp;theme-id=0&amp;slug-hash=PeoeBy&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>left</code>和<code>top</code>的偏移量如下:</p> <pre><code>article:nth-of-type(1) { /* 1st */ left: calc(100% - var(--r) + var(--m)); top: calc(100% - var(--r) + var(--m)); } article:nth-of-type(2) { /* 2nd */ left: calc( 0% - var(--r) - var(--m)); top: calc(100% - var(--r) + var(--m)); } article:nth-of-type(3) { /* 3rd */ left: calc(100% - var(--r) + var(--m)); top: calc( 0% - var(--r) - var(--m)); } article:nth-of-type(4) { /* 4th */ left: calc( 0% - var(--r) - var(--m)); top: calc( 0% - var(--r) - var(--m)); } </code></pre> <p>这意味着凹角圆的中心位置取决于<code>&lt;article&gt;</code>元素之间的间距(这个间距是我们设置的<code>margin: var(--m)</code>的两倍),凹角的半径是<code>--r</code>。在一对水平和垂直的乘数因子分别是<code>--i</code>和<code>--j</code>,而且他们的最初的值都是<code>-1</code>。</p> <p>对于第一行的两个<code>&lt;article&gt;</code>元素(第一行是一个<code>2 x 2</code>的网格),我们需要改变垂直方向的乘数因子<code>--j</code>为<code>1</code>,这样就可以让凹角的圆心在<code>y</code>轴上低于父容器底部边缘;而对于奇数的<code>&lt;article&gt;</code>(第一列),需要改变水平方向的乘数因子<code>--i</code>为<code>1</code>,这样就可以让凹角的圆心在<code>x</code>轴上位于父容器的右侧边缘。</p> <pre><code>/* multipliers initially set to -1 */ html { --i: -1; --j: -1 } h3, section { &amp;:before { /* set generic offsets */ top: calc((1 + var(--j)) * 50% - var(--r) + var(--j) * var(--m)); left: calc((1 + var(--i)) * 50% - var(--r) + var(--i) * var(--m)); } } @media (min-width: 2*($min-w + 2*$m)) { article { /* change vertical multiplier for first two (on 1st row of 2x2 grid) */ &amp;:nth-of-type(-n + 2) { --j: 1 } /* change horizontal multiplier for odd ones (on 1st column) */ &amp;:nth-of-type(odd) { --i: 1 } } } </code></pre> <p>注意,第一行的的两个凹角位置位于<code>&lt;article&gt;</code>中的<code>&lt;section&gt;</code>元素上,所以这两个元素的凹角使用的是<code>&lt;section&gt;</code>的伪元素<code>::before</code>;另第二行的两个凹角位置位于<code>&lt;article&gt;</code>中的<code>&lt;h3&gt;</code>元素上,因此这两个凹角用的是<code>&lt;h3&gt;</code>的伪元素<code>::before</code>。前两个元素的<code>&lt;h3&gt;</code>元素的<code>::before</code>的半径<code>--r</code>设置为<code>0</code>,后两个元素中<code>&lt;section&gt;</code>的<code>::before</code>的<code>--r</code>设置为<code>0</code>。</p> <pre><code>@media (min-width: 2*($min-w + 2*$m)) { article { &amp;:nth-of-type(-n + 2) h3, &amp;:nth-of-type(n + 3) section { &amp;:before { --r: 0 ; } } } } </code></pre> <p>以类似的方式,我们为<code>&lt;article&gt;</code>元素的子元素添加不同的样式:</p> <pre><code>h3, section { --p: .5rem; padding: $p; } @media (min-width: 2*($min-w + 2*$m)) { article { &amp;:nth-of-type(-n + 2) section, &amp;:nth-of-type(n + 3) h3 { padding-right: calc(.5*(1 + var(--i))*(var(--r) - var(--m)) + var(--p)); padding-left: calc(.5*(1 - var(--i))*(var(--r) - var(--m)) + var(--p)); } } } </code></pre> <p>最终效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="RyNwEo" src="//codepen.io/airen/embed/RyNwEo?height=500&amp;theme-id=0&amp;slug-hash=RyNwEo&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="500" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p><strong>特别声明:</strong>今天使用CSS自定义属性,在媒体查询的条件中使用自定义属性踩了一个坑。那是因为我想在代码中统一使用CSS自定义属性来替代Sass这样处理器的变量。一直以为在CSS的媒体查询的条件中使用CSS自定义属性是OK的,结果实测代码的时候才发现不支持。最后查找了一下原因:</p> <blockquote> <p>The <code>var()</code> function can be used in place of any part of a value in any property on an element. The <code>var()</code> function can not be used as property names, selectors, or anything else besides property values. (Doing so usually produces invalid syntax, or else a value whose meaning has no connection to the variable.) —— From the <a href="//www.w3.org/TR/css-variables-1/#using-variables">spec</a></p> </blockquote> <p>值得庆達的是,你可以使用PostCSS插件<a href="//www.npmjs.com/package/postcss-media-variables">postcss-media-variables</a>来做处理。感兴趣的可以自己试试。说实话,再一次感叹PostCSS的神奇之处和无所不能。</p> <h2>潜在的问题</h2> <p>上面的示例看上去完美,方法简单而又能跪浏览器兼容。或许你已经发现了,上例是在一个特定情况下想的结果,但很多时候我们总不是这么的幸运。哪一天需求一变,是不是还能如此轻易而又完美的实现呢?</p> <p>首先,我们需要用一个伪元素来做这个凹角,当你只需要一个(比如上面看到的示例)或者两个的时候,都不是问题,但有的时候元素的四个角都需要这样的凹角时,那么我们就需要引入一个额外的元素。另外当你的伪元素被其他功能(比如Icon)占用时,你也不得不为此效果添加一个额外的标签元素。蛋疼了吧!</p> <p>其次上面示例中的<code>background</code>是一个纯色,但我们不可能总是在使用纯色背景的场景中。如果我们想要一个半透明的或者渐变的背景,或者在一张背景图片之下,那么凹角将会成为我们的一个痛点,甚至会说,这个没法实现。</p> <p>因此,我们需要探索其他更可靠的方案,并且也能让它得到众多浏览器的支持。</p> <h2>灵活性和良好的浏览器支持?是SVG?</h2> <p>想到SVG并不奇怪,但是如果我们想要灵活一点,浏览器兼容性全面一点,SVG可以说是一个最好的解决方案。在<code>.box</code>容器中包含了一个<code>&lt;svg&gt;</code>元素,而且放置在内容的前面。SVG中包含了一个<code>&lt;circle&gt;</code>元素,在这个元素上设置了<code>r</code>属性。</p> <pre><code>&lt;div class='box'&gt; &lt;svg&gt; &lt;circle r='50'/&gt; &lt;/svg&gt; TEXT CONTENT OF BOX GOES HERE &lt;/div&gt; </code></pre> <p>让<code>svg</code>相对于<code>.box</code>元素做相对定位,将将其大小设置为能完全覆盖父容器:</p> <pre><code>.box { position: relative; } svg { position: absolute; width: 100%; height: 100%; } </code></pre> <p>到目前为止,没有什么有趣的东西,所以给<code>&lt;circle&gt;</code>添加一个<code>id</code>属性,并且使用SVG的<code>&lt;use&gt;</code>元素来复制多个<code>id</code>相同的<code>&lt;circle&gt;</code>:</p> <pre><code>&lt;circle id='c' r='50'/&gt; &lt;use xlink:href="http://xxysy.com/#039;#c'" x='100%'/&gt; &lt;use xlink:href="http://xxysy.com/#039;#c'" y='100%'/&gt; &lt;use xlink:href="http://xxysy.com/#039;#c'" x='100%' y='100%'/&gt; </code></pre> <p>看到这里,是不是会觉得比使用<code>::before</code>伪元素要来得简便,而且也非常方便,就算你要移去一个或多个凹角(示例效果的紫色部分),你只需要少使用几个<code>&lt;use&gt;</code>去克隆就行了。</p> <div style="margin-bottom: 20px;"><iframe id="qYEbyr" src="//codepen.io/airen/embed/qYEbyr?height=300&amp;theme-id=0&amp;slug-hash=qYEbyr&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="300" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>从上例效果中可以看到,<code>.box</code>的四个角落都圆圈在那了,但这并不是我们想要的凹角,对吧!不要纳闷了,我们的做法是对的。请接着往下看。接下来要做的就是把这些圆圈放到一个<code>&lt;mask&gt;</code>中,给这个<code>&lt;mask&gt;</code>设置一个<code>white</code>的填充色(<code>fill</code>属性来搞定)。同时在其里面使用<code>&lt;rect&gt;</code>元素设置一个和SVG元素一样大小的矩形,主要用来覆盖整个SVG。然后我们在另一个再次使用<code>&lt;use&gt;</code>来调用这个已创建好的<code>&lt;mask&gt;</code>:</p> <pre><code>&lt;mask id='m' fill='#fff'&gt; &lt;rect id='r' width='100%' height='100%'/&gt; &lt;circle id='c' r='50' fill='#000'/&gt; &lt;use xlink:href="http://xxysy.com/#039;#c'" x='100%'/&gt; &lt;use xlink:href="http://xxysy.com/#039;#c'" y='100%'/&gt; &lt;use xlink:href="http://xxysy.com/#039;#c'" x='100%' y='100%'/&gt; &lt;/mask&gt; &lt;use xlink:href="http://xxysy.com/#039;#r'" fill='#f90' mask='url(#m)'/&gt; </code></pre> <p>最终效果如下:</p> <div style="margin-bottom: 20px;"><iframe id="wjBMON" src="//codepen.io/airen/embed/wjBMON?height=300&amp;theme-id=0&amp;slug-hash=wjBMON&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="300" 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/1804/corner-3.gif" alt="" /></p> <blockquote> <p>特别注意:如果<code>.box</code>中有内容,建议放置在<code>svg</code>元素之后。当然也可以放置在其前面,如果放置在前面,<code>svg</code>在做定位时,需要显式的设置<code>top</code>、<code>right</code>、<code>bottom</code>和<code>left</code>之类的值。至于为什么,这里不做过多的阐述,感兴趣的同学可以自己去深究其中的为什么。</p> </blockquote> <p>如果<code>.box</code>有文本,需要把<code>.box</code>的<code>padding</code>的值和圆角的半径设置相同,同样的,如果使用了CSS自定义属性,可以使用JavaScript来控制它。最好把圆的半径和<code>.box</code>的<code>padding</code>使用同一个CSS自定义属性<code>--r</code>。这样会更好的控制一点:</p> <div style="margin-bottom: 20px;"><iframe id="NMPNbW" src="//codepen.io/airen/embed/NMPNbW?height=400&amp;theme-id=0&amp;slug-hash=NMPNbW&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="vjEGmQ" src="//codepen.io/airen/embed/vjEGmQ?height=400&amp;theme-id=0&amp;slug-hash=vjEGmQ&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</h2> <p>或许你会说,我不懂SVG,我就是想使用CSS来实现。其实很高兴你能这样的深究与思考。事实上我们的确可以使用CSS来实现这样的效果。</p> <p>遗憾的是,使用CSS的方案目前为止并不是所有浏览器都能支持,但使用CSS让我们把事情变得更简化,而且在不远的将来,它们肯定是能得到众多浏览器支持的。</p> <h3>在HTML元素上使用CSS的<code>mask</code></h3> <p>这里我们移除SVG所有的东西,然后使用CSS,可以在<code>.box</code>元素上设置一个<code>background</code>(可以是一个纯色、半透明、渐变、图像或者多背景,甚至是你任何你想要的CSS)和<code>mask</code>属性。</p> <pre><code>.box { /* any kind of background we wish */ mask: url(#m); } </code></pre> <blockquote> <p>注意,在HTML元素上使用一个<code>svg</code>元素,这个元素里有我们前面使用的<code>mask</code>元素。不幸的是,到目前为止只在Firefox浏览器可以看到效果。</p> </blockquote> <p><a href="http://xxysy.com/quot;//codepen.io/thebabydino/full/jzVVbo/"><im" src="/sites/default/files/blogs/2018/1804/mask_css_on_html.gif" alt="" /></a></p> <h3>使用CSS设置圆半径</h3> <p>这意味着,需要把<code>&lt;circle&gt;</code>中的<code>r</code>属性删除,然后在CSS中给其设置半径的大小,这里设置的半径大小与<code>.box</code>容器的<code>padding</code>值一样:</p> <pre><code>.box { padding: var(--r); } [id='c'] { r: var(--r); } </code></pre> <p>这样一来,如果我们改变半么<code>--r</code>的值,凹角的大小和<code>.box</code>的<code>padding</code>也会随着更新。</p> <blockquote> <p>注意,在CSS中给SVG元素设置几何属性只在Blink浏览器中有效!</p> </blockquote> <div style="margin-bottom: 20px;"><iframe id="GdgZaR" src="//codepen.io/airen/embed/GdgZaR?height=400&amp;theme-id=0&amp;slug-hash=GdgZaR&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> <h3>使用渐变来做朦层</h3> <blockquote> <p>Note that CSS masking on HTML elements doesn't work at all in Edge at this point, though it's <a href="http://xxysy.com/quot;//developer.microsoft.com/en-us/microsoft-edge/platform/status/masks/">liste" as "In Development"</a> and a flag for it (that doesn't do anything for now) <a href="http://xxysy.com/quot;//pbs.twimg.com/media/DXggq8fW4AAAIbG.jpg">ha" already shown up</a> in <code>about:flags</code>.</p> </blockquote> <p>由于我们需要完全抛弃SVG,所以我们需要使用CSS的渐变为<code>mask</code>做些事情。这里将使用<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/new-css3-radial-gradient.html">CSS径向渐变</a>来画圆,下面就是CSS绘制的一个半径为<code>--r</code>的圆,并且这个圆位于<code>.box</code>的左上角。</p> <pre><code>.box { background: radial-gradient(circle at 0 0, #000 var(--r, 50px), transparent 0); } </code></pre> <blockquote> <p>如果您对CSS的渐变不太了解,建议您花点时间阅读这几篇文章:《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/new-css3-linear-gradient.html">再说CSS3渐变:线性渐变</a>》、《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/new-css3-radial-gradient.html">再说CSS3渐变:径向渐变</a>》、《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/why-do-we-have-repeating-linear-gradient-anyway.html">为什么要使用<code>repeating-linear-gradient</code></a>》、《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/do-you-really-understand-css-linear-gradients.html">你真的理解CSS的<code>linear-gradient</code>?</a>》。</p> </blockquote> <p>这个时候你可以看到像上面这样的一个效果:</p> <div style="margin-bottom: 20px;"><iframe id="RyNRGN" src="//codepen.io/airen/embed/RyNRGN?height=300&amp;theme-id=0&amp;slug-hash=RyNRGN&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="300" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>接下来在<code>mask</code>使用相同的渐变:</p> <pre><code>.box { /* same as before */ /* any CSS background we wish */ mask: radial-gradient(circle at 0 0, #000 var(--r, 50px), transparent 0); } </code></pre> <blockquote> <p>注意,Webkit浏览器仍然需要给<code>mask</code>属性添加<code>-webkit-</code>前缀。如果你不知道<code>mask</code>怎么使用,建议你花点时间阅读《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/css-masking.html">如何在CSS中使用遮罩</a>》一文。因为后面很多内容都会涉及到这个属性,这样能帮助更好的理解后续的内容。</p> </blockquote> <p>给<code>.box</code>每个角落都添加渐变绘制的圆:</p> <pre><code>$grad-list: radial-gradient(circle at 0 0 , #000 var(--r, 50px), transparent 0), radial-gradient(circle at 100% 0 , #000 var(--r, 50px), transparent 0), radial-gradient(circle at 0 100%, #000 var(--r, 50px), transparent 0), radial-gradient(circle at 100% 100%, #000 var(--r, 50px), transparent 0); .box { /* same as before */ /* any CSS background we wish */ mask: $grad-list } </code></pre> <p>看到上面的代码是不是感觉要崩溃了,有太多重复的代码要写,其实我们使用一个CSS自定义属性<code>--stop-list</code>可以让我们把事情简化不少:</p> <pre><code>$grad-list: radial-gradient(circle at 0 0 , var(--stop-list)), radial-gradient(circle at 100% 0 , var(--stop-list)), radial-gradient(circle at 0 100%, var(--stop-list)), radial-gradient(circle at 100% 100%, var(--stop-list)); .box { /* same as before */ /* any CSS background we wish */ --stop-list: #000 var(--r, 50px), transparent 0; mask: $grad-list; } </code></pre> <p>上面这样做还不是很好,可以借助CSS处理器的循环特性来做,会更好一些:</p> <pre><code>$grad-list: (); @for $i from 0 to 4 { $grad-list: $grad-list, radial-gradient(circle at ($i%2)*100% floor($i/2)*100%, var(--stop-list)); } .box { /* same as before */ /* any CSS background we wish */ --stop-list: #000 var(--r, 50px), transparent 0; mask: $grad-list; } </code></pre> <p>就代码而言,这样写已经很完美了。因为我们不需要多次编写,并且以后在任何地方使用都不需要做任何更改。但到目前为止的结果并不是我们想要的:</p> <div style="margin-bottom: 20px;"><iframe id="odgLMg" src="//codepen.io/airen/embed/odgLMg?height=400&amp;theme-id=0&amp;slug-hash=odgLMg&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>从上面的示例中可以看出,我们除了凹角部分之外的东西都剪切掉了,这正好和我们想要的东西相反。要得到我们想要的效果,咱们只需要做一件事情,把渐变反过来。<strong>让凹角的圆变成透明,剩余的部分全部是黑色</strong>。</p> <pre><code>--stop-list: transparent var(--r, 50px), #000 0; </code></pre> <p>需要注意的是,如果我们只使用一个渐变的时候,那么上面的代码就帮我们解决了问题:</p> <div style="margin-bottom: 20px;"><iframe id="ZoYOmm" src="//codepen.io/airen/embed/ZoYOmm?height=400&amp;theme-id=0&amp;slug-hash=ZoYOmm&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>mask</code>的大小,这意味着没有任何东西会被掩盖掉。</p> <p><a href="http://xxysy.com/quot;//codepen.io/thebabydino/full/oqegWy/"><im" src="/sites/default/files/blogs/2018/1804/mask_grad_layering.gif" alt="" /></a></p> <p>因此,我们需要把每个渐变的大小限制在盒子的四分之处(<code>width</code>的<code>50%</code>和<code>height</code>的<code>50%</code>),从而得到<code>25%</code>的面积:</p> <p><img src="/sites/default/files/blogs/2018/1804/mask_quarters.svg" alt="" /></p> <p>这个意思就是,我们需要设置<code>mask-size</code>的值为<code>50% 50%</code>,同时<code>mask-repeat</code>的值为<code>no-repeat</code>以及每个<code>mask-image</code>自身的位置。</p> <pre><code>$grad-list: (); @for $i from 0 to 4 { $x: ($i%2)*100%; $y: floor($i/2)*100%; $grad-list: $grad-list radial-gradient(circle at $x $y, var(--stop-list)) /* mask image */ $x $y; /* mask position */ } .box { /* same as before */ /* any CSS background we wish */ --stop-list: transparent var(--r, 50px), #000 0; mask: $grad-list; mask-size: 50% 50%; mask-repeat: no-repeat; } </code></pre> <p>但这里有一个大问题,一般情况下,我们的四分之一计算的每个部分会经过四舍五入,那么这四个部分重新组合在一起的时候,<code>width</code>和<code>height</code>都有可能产生间距。如下图所示:</p> <p><img src="/sites/default/files/blogs/2018/1804/mask_quarters_lines-an.png" alt="" /></p> <p>好吧,我们不能用<code>linear-gradient()</code>来做这个线条或者说把<code>mask-size</code>的尺寸增加到<code>51%</code>。比如下面的这个示例,增加了<code>mask-size</code>的尺寸来处理四个渐变区载之间的间距。</p> <div style="margin-bottom: 20px;"><iframe id="xjbEZG" src="//codepen.io/airen/embed/xjbEZG?height=400&amp;theme-id=0&amp;slug-hash=xjbEZG&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>但是,难道没有更优雅的方式来处理这个间距?不是的,可以使用<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/CSS/mask-composite"><code>mask-composite</code>属性</a>来帮我们处理。当我们返回全部渐变的全尺寸时,可以把<code>mask-composite</code>的值设置为<code>intersect</code>。</p>" <pre><code>$grad-list: (); @for $i from 0 to 4 { $grad-list: $grad-list, radial-gradient(circle at ($i%2)*100% floor($i/2)*100%, var(--stop-list)); } .box { /* same as before */ /* any CSS background we wish */ --stop-list: transparent var(--r, 50px), #000 0; mask: $grad-list; mask-composite: exclude; } </code></pre> <p>这非常酷,因为它是纯CSS的解决方案,没有使用任何SVG代码,但不幸的是,目前得能看到效果的也仅限于Firefox53+。</p> <p><a href="http://xxysy.com/quot;//codepen.io/thebabydino/full/pLNebZ/"><im" src="/sites/default/files/blogs/2018/1804/mask_css_rad_grad_composite.png" alt="" /></a></p> <h2><code>corner-shape</code></h2> <p>大约在五年前,@Lea Verou提出了一个想法,甚至还为它创建了一个<a href="http://xxysy.com/quot;//leaverou.github.io/corner-shape/">预览页面</a>。遗憾的是,它不仅没有被任何浏览器实现,而且在此期<" href="//drafts.csswg.org/css-backgrounds-4/#corner-shaping">规范</a>还没有得到很大的提高。对于未来,它仍然是值得期待的,因为它提供了很多灵活性,而且代码非常少。比如说,实现我们前面所说的效果,只需要以下几行代码:</p> <pre><code>padding: var(--r); corner-shape: scoop; border-radius: var(--r); </code></pre> <p>就是一个非常简单的CSS。是不是值得期待,但最终还是要看浏览器什么时候会对其支持。</p> <h2>CSS Houdini</h2> <p><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/553.html">CS" Houdini</a>慢慢的开始进入大家的世界当中,试问一下,我们使用CSS Houdini是不是可以更方便的实现这个内凹角的效果呢?比如像下面这样的一个效果,<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/say-hello-to-houdini-and-the-css-paint-api.html">它就是使用CSS Houdini实现</a>的:</p> <div style="margin-bottom: 20px;"><iframe id="PRzoKm" src="//codepen.io/airen/embed/PRzoKm?height=400&amp;theme-id=0&amp;slug-hash=PRzoKm&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>咱们不仿尝试一下使用Paint Worklet或者<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/css-paint-api.html">CSS Paint API</a>来实现呢?请开动你的大脑,动手撸一撸。希望您能把你的成果在下面的评论中与大家一起分享?如果你感兴趣,也可以在下面的评论中留言,我们后续可以专门花一点时间来看看CSS Houdini可以实现内凹角的效果,甚至是前面所讲的<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-notched-boxes-component-with-vue.html">斜外切口的效果</a>。</p>" <h2>构建一个内凹角的Vue伟德1946手机版</h2> <p>记得在《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-notched-boxes-component-with-vue.html">使用Vue制作切口盒子伟德1946手机版</a>》一文中,咱们就尝试使用Vue构建了一个斜外切口的Vue伟德1946手机版<code>c-noth</code>:</p>" <div style="margin-bottom: 20px;"><iframe id="WzpXwV" src="//codepen.io/airen/embed/WzpXwV?height=400&amp;theme-id=0&amp;slug-hash=WzpXwV&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>那么我们来看看怎么使用Vue来构建一个内凹角的伟德1946手机版,具体代码如下:</p> <div style="margin-bottom: 20px;"><iframe id="ELaNgX" src="//codepen.io/airen/embed/ELaNgX?height=400&amp;theme-id=0&amp;slug-hash=ELaNgX&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>特别声明,如果您的浏览器没有看到任何效果,请使用Firefox 53+浏览器查阅。具体原因,前面文章已经介绍过来了。</p> </blockquote> <p>为了照顾其他同学查看最终的效果,我录了一个屏:</p> <p><img src="/sites/default/files/blogs/2018/1804/corner-4.gif" alt="" /></p> <p>这就是最终的效果。由于我自己是Vue的初学者,现在有一个病,看到什么东西都想用Vue来写,而且想封装成一个伟德1946手机版。如果写得不好,或者有更好的方案,欢迎大家指点,并且希望能看到您的分享的成果。如果你和我一样,也是Vue的一个初学者,可以和我一起来学习Vue。整理了一些有关于<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/vue/vue2">Vue的学习笔记</a>,希望大家能喜欢,更希望能帮助到初学者,同时也希望不会误人子弟。</p>" <h2>总结</h2> <p>文章开头抛出了怎么实现内凹角的一个效果。首先从CSS的<code>box-shadow</code>着手,使用CSS的<code>box-shadow</code>可以轻易的实现内凹角的效果,但这个方案有一定的局限性,比如要多个内凹角时,需要通过增加元素标签来实现,特别是在面对渐变,或者有背景图像和半透明的情景之下,这个方案基本上无法来满足我们的需求。</p> <p>接着探索了SVG的方案,通过SVG的<code>mask</code>和<code>use</code>之类的一些独有的特性,可能灵活的帮助我们实现想要的效果,而且能做到<code>box-shadow</code>无法做到的事情。特别是通过CSS自定义属性来修改SVG的属性,让事情变得更具灵活性,只不过部分浏览器还不支持CSS来修改SVG的属性,这算是其中的一个坑吧。不过我们还是可以规避掉的。</p> <p>虽然SVG能实现想要的效果,但对于一位CSS执着者而言,总是希望不借助其他的外力,通过纯CSS来实现这个效果,事实上也是可以的,使用CSS的径向渐变和<code>mask</code>相关的知识,可以实现我们想要的效果。遗憾的是,目前众多浏览器对<code>mask</code>还是有所保留,未能全面支持。比如文中提到的,很多<code>mask</code>相关的特性,仅能在Firefox 53+上看到。包括咱们写的示例,有些仅能在Firefox上看到。</p> <p>随着CSS Houdini技术越来越成熟,我在试想,是否可以通过CSS Houdini来实现。正如@Lea Verou五年前提出的<code>corner-shape</code>属性。我想是可以的,后面可以尝试动手写写。当然,CSS Houdini虽然还没有得到所有浏览器支持,但这并不防碍我们去尝试着写各种效果。有兴趣的一起动手写写,看看这个想法是否能成真。</p> <p>最后为了能练习Vue相关的知识,尝试使用Vue封装了一个简单的凹角伟德1946手机版。写得比较拙逼,希望能得到大神的指点。</p> <p>最后的最后,需要特别感谢<a href="//css-tricks.com/author/thebabydino/">@ANA TUDOR</a>写了这么优秀的伟德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/css/scooped-corners.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/scooped-corners.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;/CSS3"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">CSS3</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/553.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Houdini</a></div><div class="field-item odd"><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 even"><a href="http://xxysy.com/quot;/blog/tags/41.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">gradient</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/339.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">masking</a></div><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/tags/642.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue伟德1946手机版</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/659.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">scooped-corners</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/601.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">CSS自定义属性</a></div></div></div> Thu, 19 Apr 2018 09:32:49 +0000 Airen 2389 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/using-conic-gradients-css-variables-create-doughnut-chart-output-range-input.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;//jdc.jd.com/archives/author/yewenwen">@Vicky.Ye</a>翻译的《<" href="http://xxysy.com/quot;//jdc.jd.com/archives/212063">使用圆锥渐变和CSS变量创建一个Rang" Input控制的环形图</a>》一文。英文原文来自于<a href="//css-tricks.com/author/thebabydino/">@Ana Tudor</a>的《<a href="//css-tricks.com/using-conic-gradients-css-variables-create-doughnut-chart-output-range-input/">Using Conic Gradients and CSS Variables to Create a Doughnut Chart Output for a Range Input</a>》一文。</p> </blockquote> <p>最近我在 <a href="http://xxysy.com/quot;//codepen.io/kaolay/pen/ozkovw">Codepe" 上看到了一个例子</a>,我的第一个想法是这个案例可以只用三个元素完成:一个容器,一个 <code>range</code> 类型的 <code>input</code> 和一个 <code>output</code> 。在 CSS 方面,涉及到使用一个把 CSS 自定义属性作为范围渲染参数的圆锥渐变函数 <a href="//css-tricks.com/snippets/css/css-conic-gradient/"><code>conic-gradient()</code></a> 。</p> <p><img src="/sites/default/files/blogs/2018/1804/result_chart.gif" alt="" /></p> <p>在2015年年中,@Lea Verou 在一次会议演讲中<a href="http://xxysy.com/quot;//www.youtube.com/watch?v=I_fBM1cEc_8">发布</a>了一" <a href="http://xxysy.com/quot;//leaverou.github.io/conic-gradient/"><code>conic-gradient()</code>" polyfill</a>,并演示了如何将它们用于创建饼图。这个 polyfill 非常适合 <code>conic-gradient()</code> 的入门学习,因为它使我们能够更全面的使用这个函数来构建我们想要的东西。不幸的是,它不适用于 <a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/601.html">CSS自定义属性</a>," CSS自定义属性现在已成为编写高效代码的关键组成部分。</p> <p>但好消息是,在过去的两年半时间里,情况有所转变。 一般来说,Chrome 浏览器和使用暴露标志的 Blink 引擎浏览器(例如 Opera )现在都支持原生的 <code>conic-gradient()</code>,这意味着已经有可能尝试以 CSS自定义属性作为 <code>conic-gradient()</code>的范围值 。 我们所需要做的就是在 <code>chrome://flags</code> 启用 <strong><code>Experimental Web Platform Features</code></strong> 标志(或者,如果您使用 Opera ,<code>opera://flags</code> ):</p> <p><img src="/sites/default/files/blogs/2018/1804/scr_chrome_flag_exp_web_platform.png" alt="" /></p> <p>好吧,现在我们可以开始了!</p> <h2>初始结构</h2> <p>一开始我们需要一个容器和一个 <code>range</code> 类型的 <code>input</code>:</p> <pre><code>&lt;div class="wrap"&gt; &lt;input id="r" type="range"/&gt; &lt;/div&gt; </code></pre> <p>请注意,我们没有 <code>output</code> 元素。 因为当 JavaScript 由于某种原因被禁用或加载失败时,未更新元素就会出现在页面里,所以我们需要通过 JavaScript 来动态更新 <code>output</code> 标签的值。同样的也需要通过 JavaScript 来判断浏览器是否支持 <code>conic-gradient()</code>,并在容器上动态添加一个 <code>class</code> 作为标识。</p> <p>如果我们的浏览器支持 <code>conic-gradient()</code> ,则容器将获得一个 <code>.full</code> 样式, <code>.full</code> 下的 <code>output</code> 样式将会显示到图表上。 否则,我们只有一个没有图表的简单滑动条, <code>output</code> 位于滑动条按钮上。</p> <p><img src="/sites/default/files/blogs/2018/1804/result-an.png" alt="" /></p> <p><em>浏览器支持 <code>conic-gradient()</code>(上边)和浏览器不支持时的兜底方案(下边)</em>。</p> <h2>基本样式</h2> <p>在着手之前,我们希望滑动条在所有浏览器上显示都是没问题的。</p> <p>我们先从最基本的样式重置开始,并设置 <code>body</code> 的 <code>background</code>:</p> <pre><code>$bg: #3d3d4a; * { margin: 0 } body { background: $bg } </code></pre> <p>第二步是准备滑动条在 WebKit 浏览器中的样式,这里我们要通过设置 <code>-webkit-appearance: none</code> 和其按钮样式(因为某种原因系统设置了该轨道的默认样式),为避免不同浏览器中默认属性的不一致,如 <code>padding</code> ,<code>background</code> 或 <code>font</code> ,我们要给出明确值:</p> <pre><code>[type='range'] { &amp;, &amp;::-webkit-slider-thumb { -webkit-appearance: none } display: block; padding: 0; background: transparent; font: inherit } </code></pre> <blockquote> <p>如果您需要了解滑动条及其伟德1946手机版在各种浏览器中的工作方式,<a href="//css-tricks.com/sliding-nightmare-understanding-range-input/">请查看我以前写的一篇有关于Rang Input的文章</a>。</p> </blockquote> <p>现在我们可以进入更有趣的部分了。 设置轨道和按钮的尺寸,并通过相应的<code>@mixin</code>指令将它们绑到滑动条伟德1946手机版上。通过添加 <code>background</code> 让其在屏幕上可见,设置 <code>border-radius</code> 来对其进行美化。为了与预期的效果一致,我们将这两个元素的 <code>border</code> 设为 <code>none</code> 。</p> <pre><code>$k: .1; $track-w: 25em; $track-h: .02*$track-w; $thumb-d: $k*$track-w; @mixin track() { border: none; width: $track-w; height: $track-h; border-radius: .5*$track-h; background: #343440 } @mixin thumb() { border: none; width: $thumb-d; height: $thumb-d; border-radius: 50%; background: #e6323e } [type='range'] { /* same styles as before */ width: $track-w; height: $thumb-d; &amp;::-webkit-slider-runnable-track { @include track } &amp;::-moz-range-track { @include track } &amp;::-ms-track { @include track } &amp;::-webkit-slider-thumb { margin-top: .5*($track-h - $thumb-d); @include thumb } &amp;::-moz-range-thumb { @include thumb } &amp;::-ms-thumb { margin-top: 0; @include thumb } } </code></pre> <p>我们添加一些属性,如在容器上设置 <code>margin</code> ,给定明确的 <code>width</code> 和 <code>font</code>:</p> <pre><code>.wrap { margin: 2em auto; width: $track-w; font: 2vmin trebuchet ms, arial, sans-serif } </code></pre> <p>我们不想让它变得太小或太大,所以我们限制了<code>font-size</code>:</p> <pre><code>.wrap { @media (max-width: 500px), (max-height: 500px) { font-size: 10px } @media (min-width: 1600px), (min-height: 1600px) { font-size: 32px } } </code></pre> <p>然后,现在我们有了一个不错的跨浏览器滑动条:</p> <div style="margin-bottom: 20px;"><iframe id="GdKvMY" src="//codepen.io/airen/embed/GdKvMY?height=200&amp;theme-id=0&amp;slug-hash=GdKvMY&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="200" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <h2>JavaScript</h2> <p>首先我们要获取到滑动条、容器和创建的 <code>output</code> 元素。</p> <pre><code>const _R = document.getElementById('r'), _W = _R.parentNode, _O = document.createElement('output'); </code></pre> <p>创建一个变量 <code>val</code> ,用于存储 <code>range</code> 类型的 <code>input</code> 的当前值:</p> <pre><code>let val = null; </code></pre> <p>接下来,我们创建一个 <code>update()</code> 函数,用于检查当前滑块值是否等于已存的值。 如果不是,则更新 JavaScript 里 <code>val</code>变量、 <code>output</code>的文本内容和外框上的 CSS自定义属性 <code>--val</code>。</p> <pre><code>function update() { let newval = +_R.value; if(val !== newval) _W.style.setProperty('--val', _O.value = val = newval) }; </code></pre> <p>在我们继续编写JavaScript之前,在 <code>output</code> 的 CSS 中设置一个 <code>conic-gradient()</code>:</p> <pre><code>output { background: conic-gradient(#e64c65 calc(var(--val)*1%), #41a8ab 0%) } </code></pre> <p>我们通过调用 <code>update()</code> 函数,将 <code>output</code> 作为子 DOM 元素添加到容器上,然后测试 <code>output</code> 的 <code>background-image</code> 是否可设置 <code>conic-gradient()</code> (注意,这步需要确定 DOM 元素添加之后,才可这么做)。</p> <p>如果测试出的 <code>background-image</code> 不是 <code>none</code> (如果是 <code>none</code> ,则没有原生 <code>conic-gradient()</code> 支持),则在容器上添加一个 <code>full</code> 样式。 并通过 <code>for</code> 属性将 <code>output</code> 与 <code>range</code>类型的<code>input</code> 绑在一起 。</p> <p>通过事件监听器,我们确保每次移动滑块时都能调用 <code>update()</code> 函数。</p> <pre><code>_O.setAttribute('for', _R.id); update(); _W.appendChild(_O); if(getComputedStyle(_O).backgroundImage !== 'none') _W.classList.add('full'); _R.addEventListener('input', update, false); _R.addEventListener('change', update, false); </code></pre> <p>现在我们有了一个滑动条和一个 <code>output</code> (如果我们的浏览器支持原生 <code>conic-gradient()</code> ,可以查看到它的值显示在 <code>conic-gradient()</code> 背景上)。 虽然在这个阶段仍然很丑,但它的功能基本实现——当我们拖动滑块时, <code>output</code> 的值会随着变化:</p> <div style="margin-bottom: 20px;"><iframe id="vjBJaV" src="//codepen.io/airen/embed/vjBJaV?height=200&amp;theme-id=0&amp;slug-hash=vjBJaV&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="200" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>我们给 <code>output</code> 加了一个浅色值,以便我们可以更好地看到它,并通过 <code>::after</code> 伪类在末尾添加 <code>%</code> 。 还需要 <code>display</code> 设置为 <code>none</code> 来隐藏 Edge 中的工具提示(<code>::-ms-tooltip</code>)。</p> <h2>没有图表的情况</h2> <p>当我们没有 <code>conic-gradient()</code> 支持时,就会出现没有图表这种情况。 我们想要实现的效果如下图:</p> <p><img src="/sites/default/files/blogs/2018/1804/result_nochart.gif" alt="" /></p> <h3>美化输出样式</h3> <p>在上面的基础上,我们给 <code>output</code> 设置绝对定位,获取滑块按钮的尺寸并将输出的文本居中显示:</p> <pre><code>.wrap:not(.full) { position: relative; output { position: absolute; top: 0; width: $thumb-d; height: $thumb-d } } output { display: flex; align-items: center; justify-content: center; } </code></pre> <blockquote> <p>如果您需要进一步了解 <code>align-items</code> 和 <code>justify-content</code> ,请参阅 <a href="http://xxysy.com/quot;//twitter.com/patrickbrosset">@Patric" Brosset</a> 的 《<a href="//medium.com/@patrickbrosset/demystifying-css-alignment-2d3ea7a02a36">揭开 CSS 对齐的神秘面纱</a>》或者查阅W3cplus上有<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/157.html">关于Flexbox布局相关的资料</a>。</p>" </blockquote> <p>结果可以在下面的 Codepen 中看到,我们依然增加了一个 <code>outline</code> 以便清楚地看到 <code>output</code> 的边框:</p> <div style="margin-bottom: 20px;"><iframe id="wjwqLv" src="//codepen.io/airen/embed/wjwqLv?height=200&amp;theme-id=0&amp;slug-hash=wjwqLv&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="200" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>这乍看起来像是那么回事儿,但是我们的 <code>output</code> 的文字不随着滑块按钮移动。</p> <h3>使输出文字移动</h3> <p>为了解决这个问题,我们首先要清楚滑块按钮是如何运动的。 在 Chrome 中,滑块按钮的 <code>border-box</code> 在 <code>input</code> 中的滑动轨道 <code>content-box</code> 的范围内移动,而在 Firefox 和 Edge 中,滑块按钮的 <code>border-box</code> 在实际 <code>input</code> 滑动条 <code>content-box</code> 的范围内移动。</p> <p>虽然这种差异可能会在某些情况下出现问题,但我们的用例很简单, 并没有在滑动条或其伟德1946手机版上设置 <code>margin</code> ,<code>padding</code> 或 <code>border</code> ,所以滑动条本身、滑动轨道、滑动按钮的这三个属性 (<code>content-box</code> , <code>padding-box</code> 和 <code>border-box</code> )是重合的。 此外,实际 <code>input</code> 的这三个属性的宽度与其轨道的三个属性的宽度重合。</p> <p>这意味着当滑块值最小时(我们没有明确设置,因此它最小值默认是 0 ),滑动按钮框的左边缘与 input 的左边缘(和轨道的左边缘)重合 。</p> <p>同样,当滑块值达到其最大值(未明确设置时,它最大值默认 <code>100</code> )时,滑动按钮框的右边缘与 <code>input</code> 的右边缘(以及轨道的右边缘)重合。将按钮的左边缘放在滑块的右边缘之前,向左大概一个按钮宽度( <code>$thumb-d</code> )。</p> <p>下图显示了输入框的宽度 <code>width</code>( <code>$track-w</code> )—— 显示为 <code>1</code> 。按钮宽 <code>width</code>( <code>$thumb-d</code> )设为 <code>k</code> (因为我们已将它设置为 <code>$thumb-d: $k * $track-w</code> ),长度为输入框的宽度的 一个分数 。</p> <p><img src="/sites/default/files/blogs/2018/1804/thumb_pos_ini_fin.svg" alt="" /></p> <p><em>滑块按钮处于最小值和最大值(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/NXBbem/">实例</a>)。</em></p>" <p>至此,我们得到左边按钮在 <code>input</code> 上的可滑动范围为 <code>input</code>的 <code>width</code>( <code>$track-w</code> )减去按钮宽度( <code>$thumb-d</code> ),这就是它最小值到最大值的距离。</p> <p>为了以相同的方式移动 <code>output</code> ,我们用一个 <code>translation</code> 过渡按钮位置来说明。 当滑动条值处于最小位置时,<code>output</code> 的初始位置位于按钮的最左边,所以此时 <code>transform</code> 是 <code>translate(0)</code> 。<code>output</code> 为最大值时的位置,就是滑动条值达到最大值的位置,我们需要将它转换为 <code>$track-w - $thumb-d = $track-w * (1 - $k)</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/thumb_range_motion.svg" alt="" /></p> <p><em>按钮的运动范围以及 <code>output</code> (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/dJjxeb/">实例</a>)的范围</em>。</p>" <p>好吧,但是它们之间的值呢?</p> <p>那么,记住每次更新滑动条值时,我们不仅要更新 <code>output</code> 输出的文本内容,还要将绑在容器上的 CSS自定义属性 <code>--val</code> 进行更新。 这个 CSS自定义属性在 <code>0</code>(当滑块值最小时为 <code>0</code> )到 <code>100</code>(当滑块值最大时为<code>100</code>)之间。</p> <p>所以如果我们通过 <code>calc(var(--val) / 100 * #{$track-w - $thumb-d})</code> 沿水平轴(<code>x</code>轴)平移 <code>output</code> ,它会随着滑动按钮移动,而不需要我们做任何事:</p> <div style="margin-bottom: 20px;"><iframe id="zjOEBW" src="//codepen.io/airen/embed/zjOEBW?height=200&amp;theme-id=0&amp;slug-hash=zjOEBW&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="200" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>需要注意的是,如果点击轨道上的其他位置,上述方法会工作,但如果我们尝试拖动按钮 ,则不会有反应。 这是因为 <code>output</code> 现在位于 <code>input</code> 滑动按钮之上,滑动按钮捕捉不到点击事件。</p> <p>我们通过在 <code>output</code> 设置 <code>pointer-events: none</code> 解决这个问题。</p> <div style="margin-bottom: 20px;"><iframe id="yjBzVX" src="//codepen.io/airen/embed/yjBzVX?height=200&amp;theme-id=0&amp;slug-hash=yjBzVX&amp;default-tab=result&amp;user=airen" scrolling="no" frameborder="0" height="200" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="width: 100%; overflow: hidden;"></iframe></div> <p>在上面的演示中,删除了 <code>output</code> 元素上的 <code>outline</code> 辅助线,因为我们不再需要它了。</p> <p>现在我们对不支持 <code>conic-gradient()</code> 浏览器有了很好的兜底方案,可以继续构建我们想要的结果了(有启用标志的 Chrome / Opera)。</p> <h2>有图表的情况</h2> <h3>绘制所需布局</h3> <p>在开始编写代码之前,我们需要清楚地知道我们想要实现的目标。为了明确这点,我们做了一个尺寸等于轨道 <code>width</code>( <code>$track-w</code> )的布局草图,这也是 <code>input</code> 的宽度和容器 <code>content-box</code> 的边长(容器 <code>padding</code> 不包含在内)。</p> <p>这意味着我们容器的 <code>content-box</code> 是边长为<code>1</code>的正方形(等于轨道 <code>width</code> ),<code>input</code> 是一个边长等于容器边长的长方形,且另一个边长是容器边长的分数 <code>k</code> ,则滑动按钮是一个 <code>k * k</code> 的方块。</p> <p><img src="/sites/default/files/blogs/2018/1804/layout_w_chart.svg" alt="" /></p> <p><em>有图表情况下所需的布局 (<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/ppGKBW/">实例</a>)</em>。</p>" <p>该图表是边长为 <code>1 - 2 * k</code> 的正方形,容器中图表距滑动条有 <code>k</code> 间隙,与滑动条相对方向的容器边缘没有间隙。考虑到容器的边长是 <code>1</code> ,图表的边长是 <code>1 - 2 * k</code> ,所以容器距图表上下边缘之间有<code>k</code>间隙。</p> <h3>调整我们的元素</h3> <p>获得这种布局的第一步是使容器为正方形,并将 <code>output</code> 的尺寸设置为 <code>(1 - 2 * $k) * 100%</code>:</p> <pre><code>$k: .1; $track-w: 25em; $chart-d: (1 - 2*$k)*100%; .wrap.full { width: $track-w; output { width: $chart-d; height: $chart-d } } </code></pre> <p>结果可以在下面看到,我们还添加了一些辅助线以更好地看到事物:</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_0.png" alt="" /></p> <p><em>第一阶段的结果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/wpXNzE/">实例</a>" ,只有支持原生<code>conic-gradient()</code>浏览器可见)</em>。</p> <p>这是一个好的开始,因为 <code>output</code> 已经在我们想要的位置上了。</p> <h3>制作垂直滑块</h3> <p>WebKit 浏览器的“官方”方式是在 <code>range</code> 类 <code>input</code> 上设置 <code>-webkit-appearance: vertical</code> 。 但是,这会破坏自定义样式,因为它们要求我们将 <code>-webkit-appearance</code> 设置为 <code>none</code> ,而我们不能给 <code>-webkit-appearance</code> 同时设置两个不同的值。</p> <p>所以我们只能使用简便的解决方案 <code>transform</code> 。我们想要的是在容器的底部有最小值,在容器的顶部有最大值。 但实际上,在容器的左端是滑块是最小值,最右端是最大值的。</p> <p><img src="/sites/default/files/blogs/2018/1804/to_vertical.svg" alt="" /></p> <p><em>滑块的初始位置与我们预期达到的最终位置(<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/mpvzwY/">实例</a>)</em>。</p>" <p>这看起来像是在右上角(以水平方向 <code>100%</code> 和垂直 <code>0%</code> 的中心点 <code>transform-origin</code> )沿负方向旋转 <code>90°</code>(因为顺时针方向是正方向)</p> <div style="margin-bottom: 20px;"><iframe id="RybLQw" src="//codepen.io/airen/embed/RybLQw?height=400&amp;theme-id=0&amp;slug-hash=RybLQw&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>input</code> 元素,而且还旋转了它自身的坐标系。 现在它的 <code>x</code> 轴向上, <code>y</code> 轴向右。</p> <p>因此,为了将其放入容器右侧内部,我们需要在旋转之后将其沿着其<code>y</code>轴的负方向平移自身 <code>height</code> 的距离。 这意味着我们应用的最终 <code>transform</code> 链是 <code>rotate(-90deg) translatey(-100%)</code> 。 (请记住, <code>translate()</code> 函数中使用的 <code>%</code> 值与<a href="//css-tricks.com/state-responsive-3d-shapes/#article-header-id-1">被翻转元素的尺寸有关</a>。)</p> <pre><code>.wrap.full { input { transform-origin: 100% 0; transform: rotate(-90deg) translatey(-100%) } } </code></pre> <p>通过上述操作,我们得到所需布局:</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_1.png" alt="" /></p> <p><em>第二阶段的结果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/RxBWZd/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <h3>设计图表的样式</h3> <p>当然,第一步是 <code>border-radius</code> 使图表变圆,并调整 <code>color</code> ,<code>font-size</code> 和 <code>font-weight</code> 属性。</p> <pre><code>.wrap.full { output { border-radius: 50%; color: #7a7a7a; font-size: 4.25em; font-weight: 700 } } </code></pre> <p>您可能已经注意到我们已经将图表的尺寸设置为 <code>(1 - 2 * $k) * 100%</code> 而不是 <code>(1 - 2 * $k) * $track-w</code> 。 这是因为 <code>$track-w</code> 是 <code>em</code> 值,这意味着计算出相等的像素值取决于该元素的 <code>font-size</code> 属性 。</p> <p>但是,我们希望能够通过增加 <code>font-size</code> 控制,而不必调整 <code>em</code> 值。 这是并不复杂,但与仅将尺寸设置为不依赖于 <code>font-size</code> 的 <code>%</code> 值相比,它仍然有点额外的工作。</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_2.png" alt="" /></p> <p><em>第三阶段的结果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/EopPjd/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <h3>从饼图到环形图</h3> <p>在文字中间模拟一个洞的最简单方法是在 <code>conic-gradient()</code> 上添加另一个 <code>background</code> 层。 我们也可以通过添加混合模式来完成这个目标,这需要有背景图片,但没这个必要。要做一个实心的 <code>background</code> ,一个简单的遮罩层就可以做到。</p> <pre><code>$p: 39%; background: radial-gradient($bg $p, transparent $p + .5%), conic-gradient(#e64c65 calc(var(--val)*1%), #41a8ab 0%); </code></pre> <p>好的,按以上这么做图表就完成了!</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_3.png" alt="" /></p> <p><em>第四阶段的结果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/zpLrwy/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <h3>在按钮上显示数值</h3> <p>在容器上用一个绝对定位的 <code>::after</code> 伪类来完成此操作。 设置这个伪类尺寸为按钮大小,并将它定位在容器的右下角,滑块值最小时按钮所在的位置。</p> <pre><code>.wrap.full { position: relative; &amp;::after { position: absolute; right: 0; bottom: 0; width: $thumb-d; height: $thumb-d; content: ''; } } </code></pre> <p>我们也给它一个线框,以便我们可以看到它。</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_4.png" alt="" /></p> <p><em>第五阶段的结果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/JMBGBP/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em></p> <p>将它与按钮一起移动,与没有图表的情况下类似,只是这次移动是沿<code>y</code>轴在负方向(而不是沿<code>x</code>轴正方向)移动。</p> <pre><code>transform: translatey(calc(var(--val)/-100*#{$track-w - $thumb-d})) </code></pre> <p>为了能够拖动伪类下的按钮,我们还必须在这个伪元素上设置 <code>pointer-events: none</code> 。 结果可以在下图看到 —— 拖动按钮还会移动容器的 <code>::before</code> 伪类。</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_5.gif" alt="" /></p> <p><em>第六阶段的结果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/mpjPpX/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <p>看起来不错,但我们真正想要的是使用这个伪类显示当前值。 如果将其 <code>content</code> 属性设置为 <code>var (--val)</code>,则不会执行任何操作,因为 <code>--val</code> 是数字,而不是字符串。 如果我们将它设置为字符串,则它可成为 <code>content</code> 的值,但就不能再将它用于 <code>calc()</code> 。</p> <p>幸运的是,我们可以通过使用<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/435.html">CSS计数器</a>的方法来解决这个问题:</p>" <pre><code>counter-reset: val var(--val); content: counter(val)'%'; </code></pre> <p>现在所有功能已完成,耶!</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_6.png" alt="" /></p> <p><em>第七阶段的结果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/ppZyVM/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <p>接下来,让我们继续添加一些属性来优化它。 我们把文本放在滑动按钮的中间,字体颜色设为白色,去掉所有辅助线,并在 <code>input</code> 上设置 <code>cursor: pointer</code> :</p> <pre><code>.wrap.full { &amp;::after { line-height: $thumb-d; color: #fff; text-align: center } } [type='range'] { cursor: pointer } </code></pre> <p>优化后的效果:</p> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_7.png" alt="" /></p> <p><em>图表情况的最终效果( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/rpreQe/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <h2>清除重复样式代码</h2> <p>代码中还有要优化的地方,在没有图表情况下的 <code>output</code> 样式和有图表的 <code>:after</code> 伪类中存在一堆重复的样式。</p> <p><img src="/sites/default/files/blogs/2018/1804/styles-an.png" alt="" /></p> <p><em>在无图表情况下 <code>output</code> 的样式与有图表情况下的 <code>.wrap:after</code> 样式</em></p> <p>我们可以对此做些优化, 然后我们<a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/xpJEre/">使用一个简短的扩展样式</a>:</p>" <pre><code>%thumb-val { position: absolute; width: $thumb-d; height: $thumb-d; color: #fff; pointer-events: none } .wrap { &amp;:not(.full) output { @extend %thumb-val; } &amp;:after { @extend %thumb-val; } } </code></pre> <h2>不错的焦点样式</h2> <p>比方说,我们不想在 <code>:focus</code> 时出现 <code>outline</code> ,但又希望在视觉上清楚地区分获焦这种状态。 那我们该如何做? 我们可以在 <code>input</code> 没有获焦时,缩小滑动按钮,降低色彩饱和度,并且隐藏输出的文字。</p> <p>这听起来像个很酷……但是,由于我们没有父选择器,所以当滑动条获焦或失焦时,我们无法在改变滑动条父级的 <code>::after</code> 属性。 额….</p> <p>但可以做的是使用 <code>output</code> 的其他伪元素(<code>::before</code>)来显示按钮上的值。 这并不难做到,稍后我们会讨论,它允许我们做如下操作:</p> <pre><code>[type='range']:focus + output:before { /* focus styles */ } </code></pre> <p>采取这种方法的问题在于,我们如何放大 <code>output</code> 的字体 <code>font</code> ,但又不改变容器 <code>::before</code> 伪类的大小和粗细。</p> <p>我们可以通过设置Sass变量来解决这个问题,将相对字体大小定义为变量 <code>$fsr</code> ,然后使该值在实际 <code>output</code> 上放大字体 <code>font</code> ,在 <code>output:before</code> 伪类上将其恢复为之前的大小,如下 。</p> <pre><code>$fsr: 4; .wrap { color: $fg; &amp;.full { output { font-size: $fsr*1em; &amp;:before { font-size: 1em/$fsr; font-weight: 200; } } } } </code></pre> <p>除此之外,我们只需要移动我们在 <code>.wrap:after</code> 上的 CSS自定义属性到 <code>output:before</code> 上 。</p> <p><img src="/sites/default/files/blogs/2018/1804/styles_thumbval-an.png" alt="" /></p> <p><em>容器伪元素上的样式与 <code>output</code> 伪元素上的样式</em>。</p> <p>好的,现在我们可以进入区分正常和聚焦效果的最后一步。</p> <p>当滑块没有被聚焦时,我们首先隐藏丑陋的 <code>:focus</code> 默认 <code>outline</code> 状态和按钮上的值:</p> <pre><code>%thumb-val { opacity: 0; } [type='range']:focus { outline: none; .wrap:not(.full) &amp; + output, .wrap.full &amp; + output:before { opacity: 1 } } </code></pre> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_11.gif" alt="" /></p> <p><em>只有当滑动条获得焦点时,按钮上的值才可见( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/gojwrP/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <p>接下来,我们为滑块按钮的正常和聚焦状态设置不同的样式:</p> <pre><code>@mixin thumb() { transform: scale(.7); filter: saturate(.7) } @mixin thumb-focus() { transform: none; filter: none } [type='range']:focus { &amp;::-webkit-slider-thumb { @include thumb-focus } &amp;::-moz-range-thumb { @include thumb-focus } &amp;::-ms-thumb { @include thumb-focus } } </code></pre> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_12.gif" alt="" /></p> <p><em>只要滑块没有聚焦,按钮才缩小和去饱和( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/xpJEYo/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <p>最后一步是添加这些状态之间的转换:</p> <pre><code>$t: .5s; @mixin thumb() { transition: transform $t linear, filter $t } %thumb-val { transition: opacity $t ease-in-out } </code></pre> <p><img src="/sites/default/files/blogs/2018/1804/slider_in_chart_out_13.gif" alt="" /></p> <p><em>该例子显示正常状态和聚焦状态之间的转换( <a href="http://xxysy.com/quot;//codepen.io/thebabydino/pen/opaEJL/">实例</a>" ,只有支持原生 <code>conic-gradient()</code> 浏览器可见)</em>。</p> <h3>什么是屏幕读取?</h3> <p>由于屏幕读取最近生成的内容,因此在这种情况下,我们会将 <code>%</code> 值读取两次。 所以我们通过在 <code>output</code> 上设置 <code>role='img'</code> 来解决这个问题,然后把我们想要读取的当前值放在 <code>aria-label</code> 属性中:</p> <pre><code>let conic = false; function update() { let newval = +_R.value; if(val !== newval) { _W.style.setProperty('--val', _O.value = val = newval); if(conic) _O.setAttribute('aria-label', `${val}%`) } }; update(); _O.setAttribute('for', _R.id); _W.appendChild(_O); if(getComputedStyle(_O).backgroundImage !== 'none') { conic = true; _W.classList.add('full'); _O.setAttribute('role', 'img'); _O.setAttribute('aria-label', `${val}%`) } </code></pre> <p>最后的演示可以在下面链接中找到。 请注意,如果您的浏览器没有原生 <code>conic-gradient()</code> 支持,你只会看到兜底样式。</p> <div style="margin-bottom: 20px;"><iframe id="YLKEbx" src="//codepen.io/airen/embed/YLKEbx?height=400&amp;theme-id=0&amp;slug-hash=YLKEbx&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>conic-gradient()</code> 的支持仍然很差,但情况将会有所改变。 目前只有暴露标志的 Blink 浏览器支持,但 Safari 将 <code>conic-gradient()</code> 列为正在<a href="http://xxysy.com/quot;//webkit.org/status/#feature-conic-gradients">开发中</a>" ,所以事情已经越来越好了。</p> <p>如果您希望跨浏览器支持早日成为现实,您可以通过在 <a href="//wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/8471413-implement-conic-gradients-from-css-image-values-le">Edge 中</a>投票实现 <code>conic-gradient()</code> 或通过对 <a href="http://xxysy.com/quot;//bugzilla.mozilla.org/show_bug.cgi?id=1175958">Firefo" 错误</a>发表评论来说明为什么您认为这很重要或是什么让您使用它。 <a href="//wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/8471413-implement-conic-gradients-from-css-image-values-le">这里是我</a>发表的作品。</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/css/using-conic-gradients-css-variables-create-doughnut-chart-output-range-input.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/using-conic-gradients-css-variables-create-doughnut-chart-output-range-input.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/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/601.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/68.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">CSS</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/657.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">conic-gradient</a></div><div class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/658.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">渐变</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/41.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">gradient</a></div></div></div> Tue, 17 Apr 2018 14:17:33 +0000 Airen 2388 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/javascript/7-tips-to-handle-undefined-in-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"><blockquote> <p>特别声明:此篇文章内容来源于<a href="http://xxysy.com/quot;//futu.im/author/Cynthia">@Cynthia</a>的《<" href="//futu.im/posts/2017-05-20-7-tips-to-handle-undefined-in-JavaScript/">7个处理javascript的<code>undefined</code>的Tips</a>》一文。</p> </blockquote> <p>在8年前,我刚开始学习JavaScript时,让我觉得有点奇怪的是,<code>undefined</code>和<code>null</code>同样代表空值。它们之间是否有明确的不同?它们看起来都定义为“空”,此外,比较<code>null== undefined</code>的结果是<code>true</code>。</p> <p>大多数的现代语言如Ruby, Python 或Java只有一个空值(<code>nil</code>或<code>null</code>),而这,似乎才是合理的。</p> <p>在JavaScript中,当访问一个尚未初始化的变量或对象属性时,解释器会返回<code>undefined</code>。如下:</p> <pre><code>let company; company; // =&gt; undefined let person = { name: 'John Smith' }; person.age; // =&gt; undefined </code></pre> <p>另一方面,<code>null</code>代表一个缺失的对象引用。JavaScript自身不会将变量或对象属性设为<code>null</code>。一些像<code>String.prototype.match()</code>的原生方法可以返回<code>null</code>以表示为缺失对象。看一下这个例子:</p> <pre><code>let array = null; array; // =&gt; null let movie = { name: 'Starship Troopers', musicBy: null }; movie.musicBy; // =&gt; null 'abc'.match(/[0-9]/); // =&gt; null </code></pre> <p>由于JavaScript是非常宽松的,所以开发者有可能访问到未初始化的值。我也有这样的坏习惯。</p> <p>通常这样冒险的行为会产生<code>undefined</code>的相关错误,从而导致脚本闪电般结束。常见的相关错误有:</p> <ul> <li><code>TypeError: 'undefined' is not a function</code></li> <li><code>TypeError: Cannot read property '&lt;prop-name&gt;' of undefined</code></li> <li>类似的类型错误。</li> </ul> <p>JavaScript开发人员应该可以理解这个笑话里的讽刺:</p> <pre><code>function undefined() { // problem solved } </code></pre> <p>为减少这类错误的风险,你必须了解<code>undefined</code>会在什么时候生成。更重要的是,要在你的程序中抑制它的出现和传播,以提高代码的健壮性。</p> <p>让我们详细地探明<code>undefined</code>对代码安全性的影响。</p> <h2>什么是<code>undefined</code></h2> <p>JavaScript 有6种基本类型</p> <ul> <li><strong><code>Boolean</code></strong>: <code>true</code> 或 <code>false</code></li> <li><strong><code>Number</code></strong>: <code>1</code>, <code>6.7</code>, <code>0xFF</code></li> <li><strong><code>String</code></strong>: <code>"Gorilla and banana"</code></li> <li><strong><code>Symbol</code></strong>: <code>Symbol("name")</code> (始于ES2015)</li> <li><strong><code>Null</code></strong>: <code>null</code></li> <li><strong><code>Undefined</code></strong>: <code>undefined</code></li> </ul> <p>和一种单独的对象类型:<code>{name: "Dmitri"}</code>, <code>["apple", "orange"]</code>。</p> <p>在这6种基本类型中,<code>undefined是</code>一个特殊的值,它有自己的类型<code>Undefined</code>。<a href="http://xxysy.com/quot;//www.ecma-international.org/ecma-262/7.0/#sec-undefined-value">根据ECMAScript规范</a>:</p>" <blockquote> <p>当一个变量没有被赋值时,<code>undefined</code>值作为原始值使用。</p> </blockquote> <p>规范明确定义了,在访问未初始化变量,不存在的对象属性,不存在的的数组元素等时,将得到<code>undefined</code>值。举例:</p> <pre><code>let number; number; // =&gt; undefined let movie = { name: 'Interstellar' }; movie.year; // =&gt; undefined let movies = ['Interstellar', 'Alexander']; movies[3]; // =&gt; undefined </code></pre> <p>如上所示,当访问:</p> <ul> <li>一个未初始化变量 <code>number</code></li> <li>一个不存在的对象属性 <code>movie.year</code></li> <li>或一个不存在的数组元素 <code>movies[3]</code></li> </ul> <p>会被赋值为<code>undefined</code>。</p> <p>ECMAScript规范定义了<code>undefined</code>值的类型:</p> <blockquote> <p><code>Undefined</code>类型的唯一值是<code>udnefined</code>。</p> </blockquote> <p>从这个意义上讲,用<code>typeof</code>运算符操作一个<code>undefined</code>值,返回<code>'undefined'</code>字符串。</p> <pre><code>typeof undefined === 'undefined'; // =&gt; true </code></pre> <p>当然,<code>typeof</code> 可以很好地验证一个变量是否为<code>undefined</code>值。</p> <pre><code>let nothing; typeof nothing === 'undefined'; // =&gt; true </code></pre> <h2>产生<code>undefined</code>的常见场景</h2> <h3>未初始化变量</h3> <blockquote> <p>一个未赋值(未初始化)的已声明的变量默认为<code>undefined</code>。</p> </blockquote> <p>一个平淡朴素的例子:</p> <pre><code>let myVariable; myVariable; // =&gt; undefined </code></pre> <p><code>myVariable</code>已声明,但未赋值,访问该变量得到的值为<code>undefined</code>。</p> <p>解决未初始化变量问题的一个有效方法是尽可能的赋予初始值。在未初始化状态下的变量越少越好。理想情况是当你声明变量后应立刻赋值<code>const myVariable = 'Initial value'</code>,但这并不总是可能的。</p> <h4>赞成使用<code>const</code>,其次<code>let</code>,告别<code>var</code></h4> <p>我认为,ECMAScript2015的最好的特性之一是使用<code>const</code>和<code>let</code>声明变量。这些声明是块级作用域(与旧的函数作用域的<code>var</code>相反),并且在声明语句前,变量都处于<a href="http://xxysy.com/quot;//rainsoft.io/variables-lifecycle-and-why-let-is-not-hoisted/#5letvariableslifecycle">临时死区</a>,这是一个很大的进步。</p>" <p>当一个变量只赋值一次,且不再改变时,我建议使用<code>const</code>声明。它创建了一个<a href="http://xxysy.com/quot;//mathiasbynens.be/notes/es6-const">不可变的绑定关系</a>。</p>" <p><code>const</code>的特征之一是,你必须给变量赋值,<code>const myVariable = 'initial'</code>,该变量不会暴露在未初始化状态,所以是不可能访问到<code>undefined</code>。</p> <p>让我们检查一下这个函数,验证一个单词是否为回文:</p> <pre><code>function isPalindrome(word) { const length = word.length; const half = Math.floor(length / 2); for (let index = 0; index &lt; half; index++) { if (word[index] !== word[length - index - 1]) { return false; } } return true; } isPalindrome('madam'); // =&gt; true isPalindrome('hello'); // =&gt; false </code></pre> <p><code>length</code>和<code>half</code>只被赋值一次,因此这些变量不会改变,所以似乎有理由将它们声明为<code>const</code>。</p> <p>如果你需要重新绑定变量(即多次赋值),用<code>let</code>声明,无论如何尽可能给它赋予初始值,如<code>let index = 0</code>。</p> <p>那旧的<code>var</code>了?就ES2015而言,我建议是<a href="//medium.com/javascript-scene/javascript-es6-var-let-or-const-ba58b8dcde75#.hvdxtd30t">停止使用它</a>。</p> <p><code>var</code>声明的问题是,在整个函数作用域内的变量提升。你在函数的尾部声明一个<code>var</code>变量,但仍可以在声明之前访问它:你将得到<code>undefined</code>。</p> <pre><code>function bigFunction() { // code... myVariable; // =&gt; undefined // code... var myVariable = 'Initial value'; // code... myVariable; // =&gt; 'Initial value' } bigFunction(); </code></pre> <p><code>myVariable</code>是可以访问的,但在声明行<code>var myVariable = 'Initial value'</code>之前为<code>undefined</code>。</p> <p>相反,一个<code>let</code>(包括<code>const</code>)变量在声明语句之前都无法访问。这是因为变量在声明之前处于临时死区。这很好,因为你很少有机会得到<code>undefined</code>。</p> <p>将上面的例子改为<code>let</code>(而不是<code>var</code>),会抛出<code>ReferenceError</code>,因为在临时死区的变量是不可访问的。</p> <pre><code>function bigFunction() { // code... myVariable; // =&gt; Throws 'ReferenceError: myVariable is not defined' // code... let myVariable = 'Initial value'; // code... myVariable; // =&gt; 'Initial value' } bigFunction(); </code></pre> <p>进行不可变的绑定鼓励使用<code>const</code>,否则使用<code>let</code>,以确保尽可能少暴露未初始化变量。</p> <h4>增强内聚</h4> <p>内聚)描述了模块的元素(命名空间,类,方法,代码块)紧密联系的程度。对内聚的度量通常被描述为高内聚或低内聚。</p> <p>高内聚是可取的,因为它建议设计模块的元素时只关注单任务,它使得模块:</p> <ul> <li><strong>专注和可理解的</strong>:更容易理解模块所做的事情</li> <li><strong>可维护和易于重构</strong>:模块的更改影响更少的模块</li> <li><strong>可重用</strong>:专注于单个任务,使模块更易于重用</li> <li><strong>可测试的</strong>:您将更容易地测试一个专注于单一任务的模块</li> </ul> <p><img src="/sites/default/files/blogs/2018/1804/javascript-undefinder.svg" alt="" /></p> <p>高内聚和<a href="http://xxysy.com/quot;//en.wikipedia.org/wiki/Loose_coupling">低耦合</a>是设计良好的系统的特点。</p>" <p>代码块本身就可能被认为是一个小模块。为了从高内聚的好处中获益,你需要尽可能使变量靠近调用它的代码块。</p> <p>例如,一个变量只在某块级作用域中使用,那就声明并允许变量只在那个块(使用<code>const</code>或<code>let</code>声明),不要将这变量暴露给外部的块级作用域,因为外面的块级作用域并不关心这个变量。</p> <p>在函数中使用<code>for</code>循环是变量不必要延伸的典型例子:</p> <pre><code>function someFunc(array) { var index, item, length = array.length; // some code... // some code... for (index = 0; index &lt; length; index++) { item = array[index]; // some code... } return 'some result'; } </code></pre> <p><code>index</code>,<code>item</code>,<code>length</code>在函数体的顶部就被声明,但它们却只在尾部时才被调用,那这种方法的有什么问题呢?</p> <p>在顶部的声明和<code>for</code>语句的使用之间,<code>index</code>,<code>item</code>,<code>length</code>都没有初始化,且暴露为<code>undefined</code>,它们在整个函数作用域内有一个很长的生命周期,这是不合理的。</p> <p>更好的方法是将这些变量尽可能地移到它们的使用位置附近:</p> <pre><code>function someFunc(array) { // some code... // some code... const length = array.length; for (let index = 0; index &lt; length; index++) { const item = array[index]; // some } return 'some result'; } </code></pre> <p><code>index</code>,<code>item</code>只存在于<code>for</code>语句的块级作用域中,在<code>for</code>语句外它们没有任何意义。</p> <p><code>length</code>也在接近其使用的源代码时才声明。</p> <p>为什么修改后的版本比初始版本更好?我们看:</p> <ul> <li>变量不会暴露为未初始化状态,那你就没有访问到<code>undefined</code>的风险。</li> <li>将变量尽可能地移动到它们的使用位置附近会增加代码的可读性。</li> <li>高内聚的代码块在必要时更易于重构和提取到单独的函数中。</li> </ul> <h3>访问不存在的属性</h3> <blockquote> <p>当访问一个不存在的属性,JavaScript返回<code>undefined</code>。</p> </blockquote> <p>我们用例子演示一下:</p> <pre><code>let favoriteMovie = { title: 'Blade Runner' }; favoriteMovie.actors; // =&gt; undefined </code></pre> <p>对象<code>favoriteMovie</code>只有一个属性<code>title</code>,当使用属性访问器<code>favoriteMovie.actors</code>访问一个不存在的对象属性<code>actors</code>时将返回<code>undefined</code>。</p> <p>当访问一个不存在的属性时不会抛出错误。但试图从一个不存在的属性值中获取数据时,真正的问题就出现了。这是最常见的<code>undefined</code>的相关问题,这反映在众所周知的错误消息中:<code>TypeError: Cannot read property &lt;prop&gt; of undefined</code>。</p> <p>让我们稍微修改前面的代码来说明<code>TypeError</code>的抛出:</p> <pre><code>let favoriteMovie = { title: 'Blade Runner' }; favoriteMovie.actors[0]; // TypeError: Cannot read property '0' of undefined </code></pre> <p><code>favoriteMovie</code>没有属性<code>actors</code>,所以<code>favoriteMovie.actors</code>的值为<code>undefined</code>。</p> <p>因此,访问表达式<code>favoriteMovie.actors[0]</code>,即求<code>undefined</code>值的第一项,就会抛出<code>TypeError</code>异常。</p> <p>JavaScript允许访问不存在属性的宽容本质是混乱的来源:属性可能设置了,但也可能没有。绕过这个问题的理想方法是始终定义对象的属性以限制对象。</p> <p>不幸的是,你通常无法控制你所使用的对象。在不同的场景中,这些对象可能具有不同的属性集,所以你必须手动处理这些情况。</p> <p>让我们实现一个函数<code>append(array, toAppend)</code>,在数组的头部和/或尾部添加一个新元素,<code>toAppend</code>参数接受一个带有属性的对象。</p> <ul> <li><code>first</code>:要添加到数组头部的元素</li> <li><code>last</code>: 要添加到数组尾部的元素</li> </ul> <p>该函数返回一个新的数组实例,不改变原数组(即它是一个<a href="//medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976#.tyinnrzbi">纯函数</a>)。</p> <p>第一个版本的<code>append()</code>,有些天真,看起来像这样:</p> <pre><code>function append(array, toAppend) { const arrayCopy = array.slice(); if (toAppend.first) { arrayCopy.unshift(toAppend.first); } if (toAppend.last) { arrayCopy.push(toAppend.last); } return arrayCopy; } append([2, 3, 4], { first: 1, last: 5 }); // =&gt; [1, 2, 3, 4, 5] append(['Hello'], { last: 'World' }); // =&gt; ['Hello', 'World'] append([8, 16], { first: 4 }); // =&gt; [4, 8, 16] </code></pre> <p>因为对象<code>toAppend</code>可以省略属性<code>first</code>或<code>last</code>,所以必须检查这些属性是否存在于<code>toAppend</code>中。</p> <p>如果属性不存在,属性访问器则返回<code>undefined</code>,第一个诱惑出现,检查<code>first</code>和<code>last</code>属性是否存在,是通过验证它们是否为<code>undefined</code>。这我们在条件语句<code>if(toAppend.first){}和if(toAppend.last){}</code>中验证…</p> <p>没这么快,这种方法有个严重的缺陷,<code>undefined</code>,以及<code>false</code>,<code>null</code>,<code>0</code>,<code>NaN</code>都是<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Glossary/Falsy">falsy值</a>。</p>" <p>在<code>append()</code>的实现中,函数不允许插入假值。</p> <pre><code>append([10], { first: 0, last: false }); // =&gt; [10] </code></pre> <p><code>0</code>和<code>false</code>都为falsy,因为<code>if(toAppend.first){}</code> 和 <code>if(toAppend.last){}</code>实际上和falsy比较,因此元素并没有插入数组中,函数返回初始数组<code>[10]</code>,没有被修改。</p> <p>下面的提示解释了如何正确地检查属性的存在。</p> <h4>检查属性是否存在</h4> <p>幸运的是,JavaScript有一堆方法验证对象是否存在特殊属性:</p> <ul> <li><code>obj.prop!==undefined</code>:直接和<code>undefined</code>作比较</li> <li><code>typeof obj.prop!=='undefined'</code>:验证属性值的类型</li> <li><code>obj.hasOwnProperty('prop')</code>: 验证属性是否为对象的自身属性</li> <li><code>'prop' in obj</code>: 验证属性是否为对象自身或继承的属性</li> </ul> <p>我的建议是使用<code>in</code>运算符,它是一个简短且亲切的语法。<code>in</code>运算符的存在表明了一个明确的意图,即检查对象是否具有特定的属性,而不访问实际的属性值。</p> <p><code>obj.hasOwnProperty('prop')</code>也是一个不错的选择,它稍微比<code>in</code>操作符长,且只验证对象的自身属性。</p> <p>那两种与<code>undefined</code>比较的方法可能有用…但对于我来说,<code>obj.prop !== undefined</code> 和 <code>typeof obj.prop !== 'undefined'</code>看起来冗余且怪异,而且直接处理<code>undefined</code>是一种存疑的做法。</p> <p>让我们用<code>in</code>运算符改善一下<code>append(array,toAppend)</code>函数:</p> <pre><code>function append(array, toAppend) { const arrayCopy = array.slice(); if ('first' in toAppend) { arrayCopy.unshift(toAppend.first); } if ('last' in toAppend) { arrayCopy.push(toAppend.last); } return arrayCopy; } append([2, 3, 4], { first: 1, last: 5 }); // =&gt; [1, 2, 3, 4, 5] append([10], { first: 0, last: false }); // =&gt; [0, 10, false] </code></pre> <p>当相应的属性存在,<code>'first' in toAppend</code> (和 <code>'last' in toAppend</code>)为<code>true</code>,否则为<code>false</code>。</p> <p><code>in</code>运算符的使用解决了插入falsy元素<code>0</code>或<code>false</code>的问题。现在,插入这些元素在<code>[10]</code>的头部和尾部得到了预期的结果<code>[0, 10, false]</code>。</p> <h4>解构访问对象属性</h4> <p>当访问一个对象属性时,有时如果属性不存在,则需要指出默认值。</p> <p>你可以用三元运算符实现它:</p> <pre><code>const object = { }; const prop = 'prop' in object ? object.prop : 'default'; prop; // =&gt; 'default' </code></pre> <p>当要检查的属性数量增加时,三元运算符语法的使用会变得令人生畏。对于每个属性,您必须创建一个新的代码行来处理默认值。这类似的三元运算符的使用是丑陋的。</p> <p>为了使用一种更优雅的方法,我们需要熟悉一下ES2015的新特征:<strong>对象解构(Object Destructuring)</strong>。</p> <p><a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring">对象解构</a>允许将值从对象属性直接提取到变量中,并在属性不存在时设置默认值,是一种避免直接处理<code>undefined</code>的方法。</p>" <p>实际上,现在的属性提取看起来很短,更有意义:</p> <pre><code>const object = {}; const { prop = 'default' } = object; prop; // =&gt; 'default' </code></pre> <p>为观察事情的运行,让我们定义了一个有用的函数,它将字符串包含在引号中。<code>quote(subject, config)</code>接受第一个参数作为被包含的字符串,第二个参数<code>config</code>是一个对象,有这些属性:</p> <ul> <li><code>char</code>: 引号字符,如<code>'</code>(单字符)或<code>"</code>(双字符),默认<code>"</code>。</li> <li><code>skipIfQuoted</code>: <code>boolean</code>值,确认当字符串已经被引用,是否跳过引用,默认是<code>true</code>。</li> </ul> <p>应用对象解构的好处,让我们实现<code>quote()</code>:</p> <pre><code>function quote(str, config) { const { char = '"', skipIfQuoted = true } = config; const length = str.length; if (skipIfQuoted &amp;&amp; str[0] === char &amp;&amp; str[length - 1] === char) { return str; } return char + str + char; } quote('Hello World', { char: '*' }); // =&gt; '*Hello World*' quote('"Welcome"', { skipIfQuoted: true }); // =&gt; '"Welcome"' </code></pre> <p>一行代码<code>const { char = '"', skipIfQuoted = true } = config</code>完成了从<code>config</code>中提取属性<code>char</code>和<code>skipIfQuoted</code>的解构赋值。如果<code>config</code>对象中没有对应的属性,解构赋值会设置默认值,<code>char</code>为<code>"</code>,<code>skipIfQuoted为false</code>(译者注:原文应该是写错了,应该是<code>true</code>)。</p> <p>幸运的是,这个函数还有空间改进。</p> <p>让我们将解构赋值移到参数部分。且给<code>config</code>参数设置一个默认值(一个空对象<code>{}</code>)。当默认设置够用时,跳过第二个参数。</p> <pre><code>function quote(str, { char = '"', skipIfQuoted = true } = {}) { const length = str.length; if (skipIfQuoted &amp;&amp; str[0] === char &amp;&amp; str[length - 1] === char) { return str; } return char + str + char; } quote('Hello World', { char: '*' }); // =&gt; '*Hello World*' quote('Sunny day'); // =&gt; '"Sunny day"' </code></pre> <p>注意,在函数签名时解构赋值替代了<code>config</code>参数。我喜欢这,因为<code>quote()</code>短了一行。<code>={}</code>在解构赋值的右侧,保证了当第二个参数完全没有指定时,如<code>quote('Sunny day')</code>,一个空对象能起效。</p> <p>对象解构是一种功能强大的特性,可以有效地处理对象的属性。我喜欢在访问的属性不存在时指定一个默认值作为返回值。这样,您可以避免和处理<code>undefined</code>的问题。</p> <h4>使用默认属性填充对象</h4> <p>如果没有像解析赋值那样为每个属性创建一个变量的必要的话,可以用默认值覆盖缺失某些属性的对象。</p> <p>ES2015的<code>Object.assign(target, source1, source2, ...)</code>方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。</p> <p>例如,你需要访问对象<code>unsafeOptions</code>的属性,但它并不总是包含全部属性。</p> <p>当从<code>unsafeOptions</code>访问不存在属性的时候,为避免<code>undefined</code>,我们需要做一些调整:</p> <ul> <li>定义一个对象<code>defaults</code>,它包含所有默认属性。</li> <li>调用<code>Object.assign({ }, defaults, unsafeOptions)</code>生成一个新的对象<code>options</code>。这个新对象接收<code>unsafeOptions</code>的所有属性,缺失的属性从<code>defaults</code>中获得。</li> </ul> <p>比如下面的示例:</p> <pre><code>const unsafeOptions = { fontSize: 18 }; const defaults = { fontSize: 16, color: 'black' }; const options = Object.assign({}, defaults, unsafeOptions); options.fontSize; // =&gt; 18 options.color; // =&gt; 'black' </code></pre> <p><code>unsafeOptions</code>只包含属性<code>fontSize</code>,对象<code>defaults</code>定义了属性<code>fontSize</code>和<code>color</code>的默认值。</p> <p><code>Object.assign()</code>的第一个参数作为目标对象<code>{}</code>,目标对象从源对象<code>unsafeOptions</code>中获得属性<code>fontSize</code>的值,从源对象<code>default</code>中获得属性<code>color</code>的值,这是因为<code>unsafeOptions</code>没有包含<code>color</code>。枚举源对象的顺序是很重要的:<strong>后面的源对象的相同属性会覆盖前者的</strong>。</p> <p>你现在可以安全地访问<code>options</code>里的任何属性,包括最初不能在<code>unsafeOptions</code>中访问的<code>options.color</code>。</p> <p>幸运的是,存在一种更简单、更轻松的方法来填充对象的默认属性。我建议使用一个新的JavaScript特性(现在在<a href="http://xxysy.com/quot;//tc39.github.io/process-document/">阶段3</a>),它允许<" href="http://xxysy.com/quot;//github.com/tc39/proposal-object-rest-spread">在对象初始化器中扩展属性</a>。</p>" <p>不是调用<code>Object.assign()</code>,而是用对象扩展语句,从源对象中复制自身的所有可枚举的属性到目标对象中:</p> <pre><code>const unsafeOptions = { fontSize: 18 }; const defaults = { fontSize: 16, color: 'black' }; const options = { ...defaults, ...unsafeOptions }; options.fontSize; // =&gt; 18 options.color; // =&gt; 'black' </code></pre> <p>对象初始化器从源对象<code>defaults</code>和<code>unsafeOptions</code>中扩展属性。指定的源对象的顺序很重要:后面的源对象的属性会覆盖前者的。</p> <p>用默认的属性值填充一个不完整的对象是一种有效的策略,可以使您的代码更安全、更健壮。无论什么情况,对象总要包含完整的属性集:那<code>undefined</code>不会生成。</p> <h3>函数参数</h3> <blockquote> <p>函数参数隐式默认为<strong><code>undefined</code></strong>。</p> </blockquote> <p>通常,一个用特定数量的参数定义的函数应该用相同数量的参数来调用。在这种情况下,参数得到您所期望的值:</p> <pre><code>function multiply(a, b) { a; // =&gt; 5 b; // =&gt; 3 return a * b; } multiply(5, 3); // =&gt; 15 </code></pre> <p>调用<code>multiply(5, 3)</code>使得参数<code>a</code>和<code>b</code>得到相应的值<code>5</code>和<code>3</code>。乘法按预期计算:<code>5 * 3 = 15</code>。</p> <p>当你在调用时省略一个参数会发生什么事?函数内的参数会变成<code>undefined</code>。</p> <p>让我们稍微改动一下之前的例子,使之只用一个参数调用函数。</p> <pre><code>function multiply(a, b) { a; // =&gt; 5 b; // =&gt; undefined return a * b; } multiply(5); // =&gt; NaN </code></pre> <p><code>function multiply(a, b) { }</code>含有两个参数<code>a</code>和<code>b</code>。<code>multiply(5)</code>的调用却只用一个参数执行:所以参数<code>a</code>为<code>5</code>,参数<code>b</code>为<code>undefined</code>。</p> <h4>使用默认参数值</h4> <p>有时,函数调用并不要求全部参数,你可以简单地为一些没有值的参数设定默认值。回到之前的例子,让我们做一些改善。如果参数<code>b</code>是<code>undefined</code>的话,我们为之设定默认值<code>2</code>:</p> <pre><code>function multiply(a, b) { if (b === undefined) { b = 2; } a; // =&gt; 5 b; // =&gt; 2 return a * b; } multiply(5); // =&gt; 10 </code></pre> <p>函数只使用一个参数调用<code>multiply(5)</code>。最初,参数<code>a</code>为<code>5</code>,参数<code>b</code>为<code>undefined</code>。利用条件语句验证<code>b</code>是否为<code>undefined</code>,如果是,<code>b=2</code>将赋予默认值。</p> <p>虽然提供的设置默认值方法是有效,但我不建议直接和<code>undefined</code>作比较。它有点冗余和看起来hack。</p> <p>更好的方法是使用ES2015的新特性:<a href="http://xxysy.com/quot;//www.sitepoint.com/es6-default-parameters/">默认参数值</a>。它更简明,且没有直接与<code>undefined</code>比较。</p>" <p>修改之前的例子,使之使用默认参数<code>b</code>。这看起来更好了。</p> <pre><code>function multiply(a, b = 2) { a; // =&gt; 5 b; // =&gt; 2 return a * b; } multiply(5); // =&gt; 10 multiply(5, undefined); // =&gt; 10 </code></pre> <p>在<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Glossary/Signature/Function">函数签名</a>中,<code>" = 2</code>保证了当<code>b</code>为<code>undefined</code>时,参数能默认为<code>2</code>。</p> <p>ES2015的特性默认参数值直观且有表现能力,总是使用它,为可选参数设定默认值。</p> <h3>函数返回值</h3> <blockquote> <p>没有<code>return</code>语句,JavaScript函数默认返回<code>undefined</code>。</p> </blockquote> <p>在JavaScript中,函数没有任何<code>return</code>语句,则默认返回<code>undefined</code>。</p> <pre><code>function square(x) { const res = x * x; } square(2); // =&gt; undefined </code></pre> <p>函数<code>square()</code>没有返回任何计算结果。所以调用函数的结果是<code>undefined</code>。</p> <p>当<code>return</code>语句存在,但后面没有任何表达式,将得到一样的结果。</p> <pre><code>function square(x) { const res = x * x; return; } square(2); // =&gt; undefined </code></pre> <p><code>return;</code>语句被执行,但它没有返回任何表达式。调用的结果依然是<code>undefined</code>。</p> <p>当然,如果指明<code>return</code>后的表达式,那将返回预期值。</p> <pre><code>function square(x) { const res = x * x; return res; } square(2); // =&gt; 4 </code></pre> <p>现在,函数调用的结果是<code>2</code>的平方<code>4</code>。</p> <h4>不要相信会自动插入分号</h4> <p>在JavaScript中,下列语句必须要以分号(<code>;</code>)结束:</p> <ul> <li>空语句</li> <li><code>let</code>, <code>const</code>, <code>var</code>, <code>import</code>, <code>export</code>声明</li> <li>表达式语句</li> <li><code>debugger</code>语句</li> <li><code>continue</code>语句和<code>break</code>语句</li> <li><code>throw</code>语句</li> <li><code>return</code>语句</li> </ul> <p>如果你使用了上述的语句,请确保在句末指明一个分号。</p> <pre><code>function getNum() { // Notice the semicolons at the end let num = 1; return num; } getNum(); // =&gt; 1 </code></pre> <p>在<code>let</code>声明和<code>return</code>语句的最后,必须强制编写一个分号。</p> <p>当你不想指明这些分号,会发生什么事?例如,为了减少源文件的大小。</p> <p>在这种情况下,ECMAScript提供了<a href="http://xxysy.com/quot;//www.ecma-international.org/ecma-262/6.0/index.html#sec-automatic-semicolon-insertion">自动分号插入</a>(ASI)机制,它会自动插入你所缺失的分号。</p>" <p>在ASI的帮助下,你可以对之前的例子删除分号:</p> <pre><code>function getNum() { // Notice that semicolons are missing let num = 1 return num } getNum() // =&gt; 1 </code></pre> <p>上述的文本是有效的JavaScript代码,缺失的分号会自动插入。</p> <p>乍一看,它看起来很有前途,ASI机制可以让你跳过不必要的分号。您可以使JavaScript代码更小、更容易阅读。</p> <p>这是ASI的一个小而恼人的陷阱,当换行符在<code>return</code>和<code>return</code>的表达式之间时<code>return \n expression</code>,ASI会自动在换行符前插入分号<code>return; \n expression</code>。</p> <p>在函数中有语句<code>return;</code>代表什么?函数会返回<code>undefined</code>。如果你不是很清楚ASI机制的细节,意外返回的<code>undefined</code>是具有误导性的。</p> <p>例如,我们研究一下调用<code>getPrimeNumbers()</code>后的返回结果。</p> <pre><code>function getPrimeNumbers() { return [ 2, 3, 5, 7, 11, 13, 17 ] } getPrimeNumbers() // =&gt; undefined </code></pre> <p>在<code>return</code>语句和数组字面量之间存在一个换行符,JavaScript会自动在<code>return</code>语句后插入分号,解析后的代码如下:</p> <pre><code>function getPrimeNumbers() { return; [ 2, 3, 5, 7, 11, 13, 17 ]; } getPrimeNumbers(); // =&gt; undefined </code></pre> <p><code>return ;</code>语句使得<code>getPrimeNumbers()</code>函数返回<code>undefined</code>,而不是预期的数组。</p> <p>通过移除<code>return</code>语句和数组字面量之间的换行符可以解决这个问题。</p> <pre><code>function getPrimeNumbers() { return [ 2, 3, 5, 7, 11, 13, 17 ]; } getPrimeNumbers(); // =&gt; [2, 3, 5, 7, 11, 13, 17] </code></pre> <p>我的建议是确切地研究ASI的<a href="//www.bradoncode.com/blog/2015/08/26/javascript-semi-colon-insertion/">工作原理</a>来避免这种情况。</p> <p>当然,绝不在<code>return</code>语句和返回表达式之间换行。</p> <h3><code>void</code>运算符</h3> <p><code>void expression</code>对给定的表达式进行求值,并无论结果是什么,都返回<code>undefined</code>。</p> <pre><code>void 1; // =&gt; undefined void (false); // =&gt; undefined void {name: 'John Smith'}; // =&gt; undefined void Math.min(1, 3); // =&gt; undefined </code></pre> <p><code>void</code>运算符的<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void#JavaScript_URIs">一个用例</a>是向期望一个表达式的值是<code>undefined</code>的地方,插入会产生副作用的表达式。</p>" <h2>数组中的<code>undefined</code></h2> <p>当访问超过数组的边界索引的元素时,你会得到一个<code>undefined</code>值。</p> <pre><code>const colors = ['blue', 'white', 'red']; colors[5]; // =&gt; undefined colors[-1]; // =&gt; undefined </code></pre> <p>数组<code>colors</code>有3个元素,他们的有效索引是<code>0</code>,<code>1</code>和<code>2</code>。</p> <p>因为没有数组元素的索引是<code>-1</code>和<code>5</code>,所以访问<code>colors[-1]</code>和<code>colors[5]</code>时返回<code>undefined</code>。</p> <p>在JavaScript中,你可能遇到过所谓的稀疏数组。这是有缺口的数组,即一些索引的元素没有定义。</p> <p>当在稀疏数组中访问一个缺口(也就是空隙)时,你也会得到<code>undefined</code>。</p> <p>下面的例子生成了稀疏数组,并尝试访问它们的空隙:</p> <pre><code>const sparse1 = new Array(3); sparse1; // =&gt; [&lt;empty slot&gt;, &lt;empty slot&gt;, &lt;empty slot&gt;] sparse1[0]; // =&gt; undefined sparse1[1]; // =&gt; undefined const sparse2 = ['white', ,'blue'] sparse2; // =&gt; ['white', &lt;empty slot&gt;, 'blue'] sparse2[1]; // =&gt; undefined </code></pre> <p><code>sparse1</code>通过一个数字参数的数组构造函数的调用生成。它有<code>3</code>个空隙。</p> <p><code>sparse2</code>通过数组字面量生成,它缺少第二个元素。</p> <p>当使用数组时,为避免获取到<code>undefined</code>,请确保使用有效的数组索引,并避免创建稀疏数组.</p> <h2><code>undefined</code>和<code>null</code>的区别</h2> <p>一个合理的问题出现了<code>:undefined</code>和<code>null</code>之间的主要区别是什么?两个特殊值都表示一个空状态。</p> <p>主要的区别是,<code>undefined</code>代表一个没有初始化的变量的值,<code>null</code>表示有意缺失的对象。</p> <p>让我们在一些例子中探索这些区别:</p> <pre><code>let number; number; // =&gt; undefined </code></pre> <p>变量<code>number</code>是<code>undefined</code>,它清楚地表示一个未初始化的变量。</p> <p>未初始化的对象属性被访问时,同样未初始化的概念也会发生。</p> <pre><code>const obj = { firstName: 'Dmitri' }; obj.lastName; // =&gt; undefined </code></pre> <p>因为对象<code>obj</code>中不存在属性<code>lastName</code>,JavaScript正确地将<code>obj.lastName</code>定为<code>undefined</code>。</p> <p>在其他情况下,您知道一个变量期望是一个对象或一个函数会返回一个对象。但是出于某种原因,你不能实例化这个对象。在这种情况下,<code>null</code>是丢失对象的一个有意义的指示器。</p> <p>例如,函数<code>clone()</code>是用于克隆普通的对象,它预期返回一个对象:</p> <pre><code>function clone(obj) { if (typeof obj === 'object' &amp;&amp; obj !== null) { return Object.assign({}, obj); } return null; } clone({name: 'John'}); // =&gt; {name: 'John'} clone(15); // =&gt; null clone(null); // =&gt; null </code></pre> <p>然而,函数<code>clone()</code>可能会被非对象参数调用,像<code>5</code>或<code>null</code>(通常是原始值,<code>null</code>或<code>undefined</code>)。在这种情况下不可能生成一个克隆,因此它有理由返回<code>null</code> —— 缺失对象的指示器。</p> <p><code>typeof</code>操作符能对这两个值作出区别:</p> <pre><code>typeof undefined; // =&gt; 'undefined' typeof null; // =&gt; 'object' </code></pre> <p><a href="//rainsoft.io/the-legend-of-javascript-equality-operator/#theidentityoperator">严格比较运算符</a> <code>===</code> 能正确地区分<code>undefined</code>和<code>null</code>。</p> <pre><code>let nothing = undefined; let missingObject = null; nothing === missingObject; // =&gt; false </code></pre> <h2>总结</h2> <p><code>undefined</code>的存在是JavaScript的宽松本性的结果。它允许这些用法:</p> <ul> <li>未初始化变量</li> <li>不存在的对象属性或方法</li> <li>访问边界索引的数组元素</li> <li>不返回任何结果的函数的调用结果</li> </ul> <p>通常,直接与<code>undefined</code>的比较是一种不好的做法,因为您可能依赖于上面提到的一种允许但不鼓励的实践。</p> <p>一个有效的策略是尽可能减少代码中<code>undefined</code>关键字的出现。与此同时,要记住它的潜在出现,并通过应用好习惯来防止它的发生:</p> <ul> <li>减少未初始化变量的使用</li> <li>使变量生命周期缩短,并接近其使用源</li> <li>无论如何,尽可能给变量赋值</li> <li>支持<code>const</code>,否则使用<code>let</code></li> <li>为无关紧要的函数参数使用默认值</li> <li>验证属性是否存在,或者用默认属性填充不安全的对象</li> <li>避免使用稀疏数组</li> </ul> <p>你对JavaScript中的<code>undefined</code>有什么看法,请在下面的评论中畅所欲言。</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/javascript/7-tips-to-handle-undefined-in-javascript.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/javascript/7-tips-to-handle-undefined-in-javascript.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/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;/JavaScript"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">JavaScript</a></div></div></div> Sun, 15 Apr 2018 10:06:53 +0000 Airen 2387 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/javascript/understanding-null-undefined-and-nan.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;//codeburst.io/@kubamichalski">@Kub" Michalski</a>的《<a href="http://xxysy.com/quot;//codeburst.io/understanding-null-undefined-and-nan-b603cb74b44c">Understandin" null, undefined and NaN</a>》一文。</p> </blockquote> <p>当您开始学习JavaScript时,首先需要学习的是数据类型。只要我们讨论<code>Number</code>、<code>String</code>、<code>Boolean</code>和<code>Object</code>时,一旦涉及到<code>null</code>和<code>undefined</code>出现时,作为初学者要理解清楚他们就可能会有点混乱。</p> <blockquote> <p>如果你和我一样是位JavaScript的初学者,建议您花点时间阅读《<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/javascript/variable-value-data-types.html">变量值的数据类型</a>》一文。</p> </blockquote> <h2>null</h2> <p><code>null</code>值表示一个指向不存在或无效的<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Glossary/object">对象</a>或地址(<" href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Glossary/Null">DMN</a>)引用。即使它指向不存在的东西,也没什么,它是一个全局对象(也是JavaScript的原始值之一)。</p>" <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-1.png" alt="" /></p> <p>否定<code>null</code>值返回<code>true</code>,但将其与<code>false</code>(或<code>true</code>)进行比较则会给出<code>false</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-2.png" alt="" /></p> <p>在基础数学运算中,<code>null</code>值将被转换为<code>0</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-3.png" alt="" /></p> <h2>undefined</h2> <p>全局属性<code>nundefined</code>表示原始值<code>undefined</code>。它也是JavaScript的原始数据类型(<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined">MDN</a>)。<code>undefined</code>是全局作用域的一个变量。<code>undefined</code>的最初值就是原始数据类型<code>undefined</code>。一个没有被赋值的变量的类型是<code>undefined</code>。如果方法或者是语句中操作的变量没有被赋值,则会返回<code>undefined</code>。</p>" <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-4.png" alt="" /></p> <p>当你声明一个变量但没有声明它的值时,JavaScript会给它赋值<code>undefined</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-5.png" alt="" /></p> <p>如果你尝试在任何运算中使用<code>undefined</code>,你会得到<code>NaN</code>的值。与<code>null</code>相似,否定<code>undefined</code>值返回<code>true</code>,但将其与<code>true</code>或<code>false</code>作比较则为<code>false</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-6.png" alt="" /></p> <h2>null vs undefined</h2> <p>那么<code>null</code>和<code>undefined</code>两者之间有什么区别呢?通过上面的内容,我们来比较一下他们之间的相似点和不同之处。</p> <p><strong>相似之处:</strong></p> <ul> <li>当被否定时,两者的值都是<code>true</code></li> <li>代表了一些不存在的东西...</li> </ul> <p><strong>差异之处:</strong></p> <ul> <li><code>null</code>表示无,完全不存在的;<code>undefined</code>表示东西没有定义</li> <li><code>undefined</code>有自己的数据类型(<code>undefined</code>),<code>null</code>只是一个对象</li> <li>在基本算术运算中,<code>null</code>被视为<code>0</code>,<code>undefined</code>返回的<code>NaN</code></li> </ul> <p>还有一些事情需要指出来:</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-7.png" alt="" /></p> <p><code>undefined == null</code>返回的值是<code>true</code>,因为JavaScript会尽力将两个值转换为相同类型。</p> <p>第二个语句,<code>undefined === null</code>和第一个语句有点不同,他们还在比较数据类型(除了比值,还要比两者数据类型),加上JavaScript很聪明,可以看出他们之间的区别,所以返回的值是<code>false</code>。</p> <p>第三个和最后一个语句,<code>!undefined == !null</code>和<code>!undefined === !null</code>实际上已经非常的简单。由于两个都是否定的返回值(否定的返回值都是<code>true</code>,而且其数据类型也相同),所以最终返回的值是<code>true</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-8.png" alt="" /></p> <h2>NaN (Not a Number)</h2> <p>通过前文的学习,我们知道了什么是<code>undefined</code>和<code>null</code>,以及它们之间的差异性,接下来我们来讨论一下<code>NaN</code>的值。</p> <p>全局<code>NaN</code>属性是一个表示非数字的值(<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN">MDN</a>)。</p>" <p>我认为这个定义很清楚。当我们要得到的数字不是数字时,JavaScript会返回这个值。例如,当你试图用<code>cucumber</code>减去<code>10</code>或者用<code>12</code>除以<code>R2D2</code>时,它们返回的值为<code>NaN</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-9.png" alt="" /></p> <p>在某些情况下,你可能期望得到这个值,但事实不如你预期。</p> <p>当你在字符串中添加一些东西的时候。如果JavaScript看到<code>+</code>符号和一个字符串,它会自动将第二个元素添加到字符串中。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-10.png" alt="" /></p> <p>当你用数字和布尔值一起运算的时候,布尔值会转换为<code>1</code>和<code>0</code>。<code>true</code>转为<code>1</code>,<code>false</code>转换为<code>0</code>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-11.png" alt="" /></p> <p>现在,棘手的(或者最棘手的)部分。<strong>NaN实际上是一个数字</strong>。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-12.png" alt="" /></p> <p>嗯,所以我们可以说它代表了自身的缺失,对吗?更进一步,我们得出结论,它本质上是相反的。</p> <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-13.png" alt="" /></p> <p>所以<code>NaN</code>和它自身值作比较返回的值是<code>false</code>。幸运的是,我们有一个函数可以检查参数是否为<code>NaN</code>:<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN"><code>isNaN()</code></a>。</p>" <p><img src="/sites/default/files/blogs/2018/1804/null-nunderfined-nan-14.png" alt="" /></p> <h2>总结</h2> <p><code>null</code>表示无、不存在或无效的对象或地址引用。它在简单的数学运算中会转换为<code>0</code>,它是一个全局对象。<code>null == false</code>返回的值是<code>false</code>。</p> <p><code>undefined</code>是一个全局属性,原始值<code>undefined</code>。它告诉我们有些东西没有赋值,没有定义。<code>undefined</code>不能转换成任何数字,因此在数学计算中使用它,返回的是<code>NaN</code>。</p> <p><code>NaN</code>表示一个不是数字的东西,尽管它实际上是一个数字。它不等于它本身,如果要检查是否有东西是<code>NaN</code>时,需要借助<code>isNaN()</code>函数。</p> <p>JavaScript中喜欢转换值,因此你需要使用三重等号(<code>===</code>)来确保两个元素是否相同。</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/javascript/understanding-null-undefined-and-nan.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/javascript/understanding-null-undefined-and-nan.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;/JavaScript"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">JavaScript</a></div></div></div> Sun, 15 Apr 2018 06:24:17 +0000 Airen 2386 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/another-collection-of-interesting-facts-about-css-grid.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="//css-tricks.com/author/mmatuzo/">@MANUEL MATUZOVIC</a>的《<a href="//css-tricks.com/another-collection-of-interesting-facts-about-css-grid/">Another Collection of Interesting Facts About CSS Grid</a>》一文。</p> </blockquote> <p>去年,我做了一个研讨会之后<a href="//css-tricks.com/collection-interesting-facts-css-grid-layout/">收集了一些关于CSS Grid布局有趣的东西</a>。今天年,我在另一个工作室工作,我学到了一些更令人兴奋的事情,那就是我们都<a href="//www.w3.org/TR/css-grid-1/">喜欢布局规范</a>。</p> <p>当然,我不会把这些有趣的东西独享。我很高兴能和大家一起分享这些有趣的东西。</p> <h2>理解<code>grid</code>的快捷方式是如何工作的</h2> <p>有时候,阅读和理解<code>grid</code>规范是非常困难的。例如,我花了很长时间才理解如何正确使用<code>grid</code>快捷方式。该规范声明的有效值有:</p> <pre><code>&lt;‘grid-template’&gt; | &lt;‘grid-template-rows’&gt; / [ auto-flow &amp;&amp; dense? ] &lt;‘grid-auto-columns’&gt;? | [ auto-flow &amp;&amp; dense? ] &lt;‘grid-autwo-rows’&gt;? / &lt;‘grid-template-columns’&gt; </code></pre> <p>如果你花时间或者你有阅读规范的经验,你就能理解它。我尝试了几种组合,但都失败了。最终帮助我的是规范中的一个注释:</p> <p><strong>注意:你只能在单个网格声明中指定显式或隐式网格属性。</strong></p> <blockquote> <p>@Rachel Andrew有<a href="http://xxysy.com/quot;//rachelandrew.co.uk/archives/2017/04/11/refer-to-the-spec/">一系列的文章</a>,使用CSS网格作为一个例子,解释了如何阅读规范。</p>" </blockquote> <p>因此,我们可以使用网格简写来指定大量的东西,但不是所有的都同时使用。这里有一些例子。</p> <h3>使用<code>grid</code>事实上在用<code>grid-template</code></h3> <p><code>grid-template</code>属性是<code>grid-template-columns</code>,<code>grid-template-rows</code>和<code>grid-template-areas</code>三个属性的简写方式。事实上,我们也可以使用<code>grid</code>的简写来做相同的事情。</p> <pre><code>grid: "one one" 200px "two four" "three four" / 1fr 2fr; </code></pre> <p>上面的代码等同于下面的代码:</p> <pre><code>grid-template-areas: "one one" "two four" "three four"; grid-template-rows: 200px; grid-template-columns: 1fr 2fr; </code></pre> <p>这个简写创建了三行两列的一个网格,其中有四个命名的网格区域。第一行的显式高度为<code>200px</code>,而第二行和第三行隐式高度为<code>auto</code>。第一列的宽度为<code>1fr</code>,第二列的宽度为<code>2fr</code>。</p> <div style="margin-bottom: 20px;"><iframe id="EEBNrx" src="//codepen.io/airen/embed/EEBNrx?height=400&amp;theme-id=0&amp;slug-hash=EEBNrx&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/1804/css-grid-1.png" alt="" /></p> <blockquote> <p>想知道更多关于显式和隐式网格的区别吗?<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/difference-explicit-implicit-grids.html">看看这篇文章</a>。</p> </blockquote> <p>如果不需要,我们不需要指定区域。我们可以使用<code>grid</code>的简写来定义显式的行和列。下面两个代码片段基本上是在做相同的事情:</p> <pre><code>grid-template-rows: 100px 300px; grid-template-columns: 3fr 1fr; /* 等同于 */ grid: 100px 300px / 3fr 1fr; </code></pre> <h3>处理隐式行和列</h3> <p>可以使用<code>grid</code>简写来指定<code>grid-auto-flow</code>,但是它并不像我们预期的那样工作。我们不只是在声明中添加<code>row</code>或<code>column</code>关键词。相反,我们必须在 <code>/</code>的一侧使用<code>auto-flow</code>关键词。</p> <p>如果<code>auto-flow</code>位于<code>/</code>的左侧,那么<code>grid</code>的简写中<code>grid-auto-flow</code>的值为<code>row</code>和创建显式的列。</p> <pre><code>grid: auto-flow / 200px 1fr; /* 等同于 */ grid-auto-flow: row; grid-template-columns: 200px 1fr; </code></pre> <p>如果<code>auto-flow</code>位于<code>/</code>的右侧,那么<code>grid</code>的简写中<code>grid-auto-flow</code>的值为<code>column</code>和创建显式的行。</p> <pre><code>grid: 100px 300px / auto-flow; /* 等同于 */ grid-template-rows: 100px 300px; grid-auto-flow: column; </code></pre> <p>我们还可以将隐式的网格轨道尺寸大小与<code>auto-flow</code>关键词一起使用,<code>auto-flow</code>分别给<code>grid-auto-rows</code>或<code>grid-auto-columns</code>设置为指定的值。</p> <pre><code>grid: 100px 300px / auto-flow 200px; /* 等同于 */ grid-template-rows: 100px 300px; grid-auto-flow: column; grid-auto-columns: 200px; </code></pre> <div style="margin-bottom: 20px;"><iframe id="YaoNPQ" src="//codepen.io/airen/embed/YaoNPQ?height=400&amp;theme-id=0&amp;slug-hash=YaoNPQ&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/1804/css-grid-2.png" alt="" /></p> <h2>Edge中的查询功能</h2> <p>使用<a href="//hacks.mozilla.org/2016/08/using-feature-queries-in-css/">查询功能</a>检查对CSS Grid的支持是非常有用的,因为所有支持<code>grid</code>的浏览器都能理解。这意味着,我们可以检查浏览器是否支持新的或旧的规范,或者两者都支持。你肯定会问,两个都支持?从Edge 16开始,Edge不仅支持新规范,而且还支持旧规范。</p> <p>所以,如果你想区域是否支持新规范,你可以像下面这样使用查询功能:</p> <pre><code>/* Edge 16 and higher */ @supports (display: -ms-grid) and (display: grid) { div { width: auto; } } /* Edge 15 and lower */ @supports (display: -ms-grid) and (not (display: grid)) { div { margin: 0 } } </code></pre> <p>这里有一个小示例,它显示了在浏览器中打开的查询特性的触发器。</p> <div style="margin-bottom: 20px;"><iframe id="VXJPaW" src="//codepen.io/airen/embed/VXJPaW?height=400&amp;theme-id=0&amp;slug-hash=VXJPaW&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>简单提一下,不要使用浏览器嗅探的特性查询,因为<a href="//css-tricks.com/browser-detection-is-bad/">浏览器检测很糟糕</a>。</p> <h2>指定每列中项目的确切数</h2> <p>网格对于页面布局来说非常有用,但是它在伟德1946手机版级别上也非常有用。我最喜欢的例子之一是能够在多列伟德1946手机版中指定每列的确切的列表数。</p> <p>假设我们有一个11项的列表,我们想在每四个项目之后添加一个新的列。首先要做的事情是在它们的父元素上显式的声明<code>display:grid</code>。默认情况之下,它会依次填充每一行,并在必要时添加新的行。如果我们<code>grid-auto-flow</code>设置为<code>column</code>,网格将依次填充每个列,这正是我们想要的。我们要做的最后一件事是指定每个列的列表项数。这可以通过<code>grid-template-rows</code>属性显式定义尽可能多的行来实现。我们可以通过使用<code>auto</code>关键词来显式地设置每一行的高度,或者让它们与内容一样大。</p> <pre><code>ul { display: grid; grid-template-rows: auto auto auto auto; /* or shorter and easier to read: */ /* grid-template-rows: repeat(4, auto); */ grid-auto-flow: column; } </code></pre> <p>如果我们必须为每列设置五个列表项,我们只需要在网格轨道后添加新的网格轨道,或者使用<a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/CSS/repeat"><code>repeat()</code>函数</a>,只需要将第一个参数更改为你所需的值,比如:<code>grid-template-rows" repeat(5, auto)</code>。</p> <div style="margin-bottom: 20px;"><iframe id="xWogdR" src="//codepen.io/airen/embed/xWogdR?height=400&amp;theme-id=0&amp;slug-hash=xWogdR&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/1804/css-grid-3.png" alt="" /></p> <h2>使用CSS Grid实现Sticky footers效果</h2> <p>在<a href="//css-tricks.com/couple-takes-sticky-footer/">CSS中创建Sticky Footer效果有很多方法</a>。有一些方法很简单,也有一些方法也很复杂。假设我们有一个类名为<code>header</code>、<code>main</code>和<code>footer</code>的页面结构:</p> <pre><code>&lt;body&gt; &lt;header&gt;HEADER&lt;/header&gt; &lt;main&gt;MAIN&lt;/main&gt; &lt;footer&gt;FOOTER&lt;/footer&gt; &lt;/body&gt; </code></pre> <blockquote> <p>如果你重未接触过有关于Sticky Footer相关的知识,建议你<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/136.html">点击这里,阅读相关文章</a>。</p>" </blockquote> <p>首先把<code>html</code>和<code>body</code>的<code>height</code>设置为<code>100%</code>,以确保页面始终使用完整的垂直空间。然后在<code>body</code>中使用<code>grid-template-rows</code>将其拆分为三行。第一个(<code>header</code>)和最后一个(<code>footer</code>)行可以设置任何我们想要的大小。如果我们想让它们总是和它其内容一样大,我们就把<code>height</code>设置为<code>auto</code>。中间的一行(<code>main</code>)应该总是填满剩余的空间。我们不需要计算高度,因为我们可以使用<a href="//css-tricks.com/introduction-fr-css-unit/">分子单位</a>来实现。</p> <pre><code>html { height: 100%; } body { min-height: 100%; display: grid; grid-template-rows: auto 1fr auto; } </code></pre> <div style="margin-bottom: 20px;"><iframe id="OveWva" src="//codepen.io/airen/embed/OveWva?height=400&amp;theme-id=0&amp;slug-hash=OveWva&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>最近,<a href="http://xxysy.com/quot;//twitter.com/Florian_">@Florian</a>在Twitter上说,他想知道<" href="http://xxysy.com/quot;//twitter.com/Florian_/status/974318004598132737">为什么在网格中截断单行文本是如些的复杂</a>。他的例子完美地说明了有关网格项目的一个有趣的事情。</p>" <p><img src="/sites/default/files/blogs/2018/1804/css-grid-4.png" alt="" /></p> <p>有一个三列的网格,每个网格项目中有一个段落。</p> <pre><code>&lt;div class="grid"&gt; &lt;div class="item"&gt; &lt;p&gt;Lorem ipsum ...&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; .grid { display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 20px; } </code></pre> <p>每个段落都应该是单行的,如果段落的长度超过其容器宽度时,则在行的末尾显示一个省略号(<code>...</code>)。@Florian通过将<code>white-space</code>设置为<code>nowrap</code>将其强制为一行,然后<code>overflow:hidden</code>和<code>text-overflow:ellipsis</code>来解决这个问题。</p> <pre><code>p { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } </code></pre> <p>这个方案对于块元素来说是完全可以的,但是在网格示例中,列扩展到单行段的宽度:</p> <div style="margin-bottom: 20px;"><iframe id="WzqRWe" src="//codepen.io/airen/embed/WzqRWe?height=400&amp;theme-id=0&amp;slug-hash=WzqRWe&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>从广义上讲,这种情况会发生,因为网格项不能小于它的子元素。一个网格项目(或一个Flex项目)的默认<code>min-width</code>设置示为<code>auto</code>。具体的可以看规范:</p> <blockquote> <p>...applies an <a href="//www.w3.org/TR/css-flexbox-1/#automatic-minimum-size">automatic minimum size</a> in the specified axis to grid items whose overflow is visible and which span at least one track whose min track sizing function is auto.</p> </blockquote> <p>这使用网格和Flex项目更加灵活,但是有时候,内容能够扩展其父容器的宽度是不可取的。为了避免这种情况,我们可以将网格项目的<code>overflow</code>改叫生设置为<code>visible</code>或者将<code>min-width</code>设置为<code>0</code>。</p> <div style="margin-bottom: 20px;"><iframe id="YaoNbj" src="//codepen.io/airen/embed/YaoNbj?height=400&amp;theme-id=0&amp;slug-hash=YaoNbj&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/1804/css-grid-5.png" alt="" /></p> <p>有关于这方面更多的信息,可以阅读网格规范中的《<a href="//www.w3.org/TR/css3-grid-layout/#min-size-auto">Automatic Minimum Size of Grid Items</a>》部分。</p> <h2>总结</h2> <p>希望这些最新的结论能帮助你在写代码和使用网格时能更爽一些。在这个新规范中有很多细节,但是这些细节变得更为有意思,也更容易帮助我们理解CSS Grid。其实今天整理的是属于第二部分了,早前也整理过一些有关于CSS Grid有趣的东西,感兴趣的可以<a href="//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css3/collection-interesting-facts-css-grid-layout.html">点击这里阅读</a>。</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/css/another-collection-of-interesting-facts-about-css-grid.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/css/another-collection-of-interesting-facts-about-css-grid.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/356.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">CSS3 Grid Layout</a></div><div class="field-item even"><a href="http://xxysy.com/quot;/blog/tags/355.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Grid</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></div> Sat, 14 Apr 2018 14:57:32 +0000 Airen 2385 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-video-player-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.youtube.com/playlist?list=PLZriQCloF6GApxBmkAcCp08VoI6mDFe2G&amp;disable_polymer=true">CSSCon" Australia</a>大会的视频已经放出来了。花了一天的时间看了一下视频,有些话题还是很有意思的。不过咱们今天要聊的不是这个大会中的事情。这不是在学Vue吗?总想给自己找点活干,练习练习Vue。我就在想,是不是可以把该大会的在YouTube上的视频列表效果给模拟出来。</p> <p>既然想了,那就动手试试呗。比如咱们接下来要做的一个效果如下:</p> <p><img src="/sites/default/files/blogs/2018/1804/cssconf-vue-youtube-1.png" alt="" /></p> <blockquote> <p>由于我们的视频和视频的相关信息都来源于YouTube上的<a href="http://xxysy.com/quot;//www.youtube.com/playlist?list=PLZriQCloF6GApxBmkAcCp08VoI6mDFe2G&amp;disable_polymer=true">CSSCon" Australia</a>。如果你看不到视频和缩略图之类的,你需要自备梯子!</p> </blockquote> <h2>构建环境</h2> <p>为了方便,咱们还是一如既往的在Codepen上来完成上图的一个效果。也是基于Vue来完成所要的效果(毕竟是用来练习Vue的嘛)。当然,如果你不想过渡的依赖于Codepen,那么你可以在本地构建该项目。比如直接使用Vue-cli构建工具来创建项目,然后可以<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/seven-ways-to-define-a-component-template-by-vuejs.html">通过多文件伟德1946手机版的方式来做</a>。对于布局效果,咱们就不多说了,而且接下来的内容也不会过多的涉及有关于CSS的事情。简要的提一下,实现上述的布局效果,你可以使用<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/157.html">Flexbox</a>或者<" href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/355.html">CS" Grid布局</a>完成。</p> <blockquote> <p>下面的代码都在Codepen上完成,<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/running-environment.html">如果你是在本地构建,特别是使用多文件组时,略有不同</a>。</p>" </blockquote> <h2>创建视频播放伟德1946手机版</h2> <p>那我们就直接从创建伟德1946手机版开始吧。(如果你是在本地,假设你已构建好了Vue的环境)</p> <p>通过<code>&lt;template&gt;</code>和<code>Vue.component()</code>来创建一个名为<code>video-player</code>的Vue伟德1946手机版。</p> <pre><code>&lt;template id="video-player"&gt; &lt;div class="video-player"&gt;Welcome to CSSConf Australia&lt;/div&gt; &lt;/template&gt; Vue.component('video-player',{ template: '#video-player' }) </code></pre> <p>事实上这个时候,你在浏览器中并看不到什么东西。那是因为,伟德1946手机版仅创建了(伟德1946手机版中只有一个最简单的标签,离目标甚远),并未调用。如果想要看到对应的效果,调用已创建的<code>video-player</code>伟德1946手机版,然后把Vue实例挂载到一个<code>id</code>名为<code>#app</code>的元素中:</p> <pre><code>&lt;div id="app"&gt; &lt;video-player&gt;&lt;/video-player&gt; &lt;/div&gt; let app = new Vue({ el: '#app' }) </code></pre> <p>这时你所看到的效果如下,虽然添加了点样式,但还不能入眼:</p> <p><img src="/sites/default/files/blogs/2018/1804/cssconf-vue-youtube-2.png" alt="" /></p> <h2>创建数据</h2> <p>现在我们已经有了基本的架子,也在浏览器上能看到对应的效果,只不过没有我们所需要的数据。接下来我们创建Vue的 <code>data</code>对象,用来存储CSS Conf视频的相关信息。比如,我们创建一个名为<code>videos</code>的变量。</p> <pre><code>let videos = [] Vue.component('video-player', { template: '#video-player', data () { return videos } }) </code></pre> <p>现在<code>videos</code>还是一个空的数组。我们需要人肉的从YouTube上把有关于CSS Conf相关的8个视频的数据撸下来。在我们的示例中,每个<code>video</code>包含下面的几个字段:</p> <ul> <li><code>id</code>:视频序列号</li> <li><code>title</code>:视频标题</li> <li><code>thumbnail</code>:视频缩略图地址</li> <li><code>youtubeURL</code>:视频地址</li> <li><code>speaker</code>:视频演讲者</li> <li><code>likes</code>:点赞数</li> <li><code>views</code>:浏览数</li> <li><code>describe</code>:视频摘要</li> </ul> <p>比如:</p> <pre><code>let videos = [ { id: 1, title: 'Behind the Illusions: Impossibly high-performance layout animations', thumbnail: '...', speaker: 'David Khourshid', likes: 0, views: 0, describe: "..." }, ..., { id: 8, title: 'Journeys: What makes a developer, really?', thumbnail: '...', speaker: 'Ivana McConnell', likes: 0, views: 0, describe: "..." } ] </code></pre> <h2>遍历数据</h2> <p>现在数据有了,接下来需要对数据进行循环,逐条显示出<code>videos</code>中每个视频。在Vue中,可以使用<code>v-for</code>来对<code>videos</code>进行遍历。</p> <pre><code>&lt;template id="video-player"&gt; &lt;div class="video-player"&gt; &lt;div class="video-list"&gt; &lt;div v-for="video in videos" :key="video.id" class="thumbnail"&gt; &lt;div class="thumbnail-img"&gt; &lt;img :src="video.thumbnail" :alt="video.title" /&gt; &lt;/div&gt; &lt;div class="thumbnail-info"&gt; &lt;h4&gt;{{ video.title }}&lt;/h4&gt; &lt;div class="thumbnail-views"&gt; &lt;span&gt;{{ video.speaker }}&lt;/span&gt; &lt;span&gt;{{video.views}} Views&lt;/span&gt; &lt;/div&gt; &lt;div class="thumbnail-describe"&gt;{{ video.describe }}&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>通过<code>v-for = "video in videos"</code>对<code>videos</code>数组进行遍历,所以我们可以访问<code>videos</code>对象中的每个字段。在我们的视频列表中,我们希望显示出视频的缩略图(<code>thumbnail</code>)、视频标题(<code>title</code>)、视频演讲者(<code>speaker</code>)、视频阅读数(<code>views</code>)和视频的摘要(<code>describe</code>)。然后在模板中通过<code>{{}}</code>方式来访问<code>videos</code>中的每个字段,比如视频的标题,我们可以在模板中这样使用<code>{{ video.title }}</code>。</p> <p>值得一提的是,因为视频缩略图需要显示为图像,需要将字段绑定到<code>&lt;img&gt;</code>元素的<code>src</code>属性上。我们需要使用<code>v-bind</code>的方式,比如<code>:src</code>来绑定图片的URL。</p> <p>这个时候,你刷新浏览器,你会看到类似下面这样的效果:</p> <p><img src="/sites/default/files/blogs/2018/1804/cssconf-vue-youtube-3.png" alt="" /></p> <h2>设置一个当前视频</h2> <p>视频缩略图列表已经处理好了,现在我们可以显示视频了。当应用程序开始加载时,显示数组中的第一个视频。要做到这一点,可以在<code>data</code>数据对象中创建一个名为<code>activeVideo</code>字段,这个主要是用来显示用户选择的视频内容。页面加载时要显示第一个视频,那需要将<code>activeVideo</code>设置为<code>videos[0]</code>。</p> <pre><code>Vue.component('video-player', { template: '#video-player', data () { return { videos, activeVideo: videos[0] } } } </code></pre> <p>当前视频现在是数据对象的一部分,这意味着我们可以模板中通过引用<code>this.activeVideo</code>来访问这个视频。为了显示视频,需要将<code>videos</code>对象中的<code>youtubeURL</code>字段绑定到<code>iframe</code>中。</p> <pre><code>&lt;template id="video-player"&gt; &lt;div class="video-player"&gt; &lt;div class="video-container"&gt; &lt;div class="video-wrap"&gt; &lt;iframe :src="this.activeVideo.youtubeURL" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class="video-list"&gt; &lt;div v-for="video in videos" :key="video.id" class="thumbnail"&gt; &lt;div class="thumbnail-img"&gt; &lt;img :src="video.thumbnail" :alt="video.title" /&gt; &lt;/div&gt; &lt;div class="thumbnail-info"&gt; &lt;h4&gt;{{ video.title }}&lt;/h4&gt; &lt;div class="thumbnail-views"&gt; &lt;span&gt;{{ video.speaker }}&lt;/span&gt; &lt;span&gt;{{ video.views }} Views&lt;/span&gt; &lt;/div&gt; &lt;div class="thumbnail-describe"&gt;{{ video.describe }}&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>这个时候你可以看到效果像下面了:</p> <p><img src="/sites/default/files/blogs/2018/1804/cssconf-vue-youtube-4.png" alt="" /></p> <p>接下来把当前视频的其他信息在模板中输出,以达到我们想要的显示结果:</p> <pre><code>&lt;template id="video-player"&gt; &lt;div class="video-player"&gt; &lt;div class="video-container"&gt; &lt;div class="video-wrap"&gt; &lt;iframe :src="this.activeVideo.youtubeURL" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;h3&gt;{{ this.activeVideo.title }}&lt;/h3&gt; &lt;div class="row"&gt; &lt;p&gt;{{ this.activeVideo.views }} views&lt;/p&gt; &lt;p&gt;{{ this.activeVideo.likes }} &lt;button&gt;Like&lt;/button&gt;&lt;/p&gt; &lt;/div&gt; &lt;div class="describe"&gt;{{ this.activeVideo.describe }}&lt;/div&gt; &lt;/div&gt; &lt;div class="video-list"&gt; &lt;div v-for="video in videos" :key="video.id" class="thumbnail"&gt; &lt;div class="thumbnail-img"&gt; &lt;img :src="video.thumbnail" :alt="video.title" /&gt; &lt;/div&gt; &lt;div class="thumbnail-info"&gt; &lt;h4&gt;{{ video.title }}&lt;/h4&gt; &lt;div class="thumbnail-views"&gt; &lt;span&gt;{{ video.speaker }}&lt;/span&gt; &lt;span&gt;{{ video.views }} Views&lt;/span&gt; &lt;/div&gt; &lt;div class="thumbnail-describe"&gt;{{ video.describe }}&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/template&gt; </code></pre> <p>再次刷新你的浏览器,可以看到如下的效果:</p> <p><img src="/sites/default/files/blogs/2018/1804/cssconf-vue-youtube-1.png" alt="" /></p> <p>从视觉效果上,看上去已达到我们想要的效果了。但为了这个视频播放伟德1946手机版功能性更完善,还需要做一些事情。</p> <h2>添加功能</h2> <p>如果你在浏览器中点击视频列表,你可以发现无法选择想要看到的视频。为此,我们需要在<code>methods</code>中添加一个<code>chooseVideo()</code>的方法:</p> <pre><code>Vue.component('video-player', { template: '#video-player', data () { return { videos, activeVideo: videos[0] } }, methods: { chooseVideo: function (video) { // 设置视频为当前视频 this.activeVideo = video // 视频点浏览器 video.views += 1 } } }) </code></pre> <p>创建了一个<code>chooseVideo()</code>方法,并且给他传了一个<code>video</code>参数。当用户点击视频列表中的列表项时,将调用此方法。另外当一个视频被选中时,将<code>activeVideo</code>设置为被点击视频,也会将视频的浏览数(<code>views</code>)增加<code>1</code>。</p> <pre><code>&lt;div @click="chooseVideo(video)" :key="video.id" v-for="video in videos" class="thumbnail"&gt; </code></pre> <p>试着点击视频列表中的视频。如果一切顺利的话,你应该可以看到你点击的视频。</p> <p><img src="/sites/default/files/blogs/2018/1804/cssconf-vue-youtube-5.gif" alt="" /></p> <h2>设置Like按钮</h2> <p>接下来还需要给Like按钮添加点击功能。需要在<code>methods</code>中创建另一个方法,当单击按钮时,该方法将会累计<code>like</code>的数。</p> <pre><code>Vue.component('video-player', { template: '#video-player', data () { return { videos, activeVideo: videos[0] } }, methods: { chooseVideo: function (video) { this.activeVideo = video video.views += 1 }, addLike: function () { this.activeVideo.likes += 1 } } }) </code></pre> <p>然后将<code>addLike()</code>方法绑定到模板中的Like按钮上。</p> <pre><code>&lt;button @click="addLike"&gt;Like&lt;/button&gt; </code></pre> <p>刷新你的浏览器,试着点击Like按钮!你应该看到计数器在上升。</p> <div style="margin-bottom: 20px;"><iframe id="qozOJV" src="//codepen.io/airen/embed/qozOJV?height=400&amp;theme-id=0&amp;slug-hash=qozOJV&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>伟德1946手机版数据传递</h2> <p>虽然上面的示例已经实现了我们想要的效果。但一眼可以看出,<code>videos</code>数据和<code>video-player</code>伟德1946手机版绑定在一起。这样就有失创建伟德1946手机版的意义。为了提高伟德1946手机版高复用性,我们需要把伟德1946手机版中的数据剥离出来,通过在父伟德1946手机版中(在本例中,也就是<code>#app</code>实例)把数据传递给<code>video-player</code>伟德1946手机版。</p> <p><a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/component-data-and-props-part1.html">根据前面的学习</a>,可以知道,咱们可以使用<code>props</code>属性来实现伟德1946手机版数据传递:<strong>父伟德1946手机版数据通过<code>props</code>向下传递给子伟德1946手机版</strong>:</p>" <p><img src="/sites/default/files/blogs/2018/1802/vue-component-8.png" alt="" /></p> <p>这样的来,我们需要在伟德1946手机版中添加<code>props</code>属性,并且给其设置两个值:<code>videos</code>和<code>activeVideo</code>。用来接父伟德1946手机版传过来的数据。</p> <pre><code>Vue.component('video-player', { template: '#video-player', props: ['videos','activeVideo'], methods: { chooseVideo: function (video) { this.activeVideo = video video.views += 1 }, addLike: function () { this.activeVideo.likes += 1 } } }) let app = new Vue({ el: '#app', data () { return { videos, activeVideo: videos[0] } } }) </code></pre> <p>在调用<code>video-player</code>伟德1946手机版时,需要通过<code>v-bind</code>的方式将数据绑定到<code>props</code>的值上:</p> <pre><code>&lt;child :子伟德1946手机版的prop="父伟德1946手机版数据属性"&gt;&lt;/child&gt; </code></pre> <p>套用到我们的示例中,应该就是这样使用:</p> <pre><code>&lt;div id="app"&gt; &lt;video-player :videos="videos" :active-video="activeVideo"&gt;&lt;/video-player&gt; &lt;/div&gt; </code></pre> <p>最终的效果,<a href="http://xxysy.com/quot;//codepen.io/airen/full/NYZNJB/">可以点击这里查看</a>。</p>" <h2>总结</h2> <p>文章通过Vue模仿了YouTube上的视频列表的一个伟德1946手机版效果。通过一步一步的方式,介绍了<code>video-player</code>伟德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/create-video-player-component.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/create-video-player-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></div> Sat, 14 Apr 2018 09:39:59 +0000 Airen 2384 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/svg/svg-animation.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.zhihu.com/people/tian-huai-ren-57">@villainHR</a>的《<" href="http://xxysy.com/quot;//zhuanlan.zhihu.com/p/35412932">SV" 让 UI 工程师早点回家陪媳妇</a>》一文。</p> </blockquote> <p>本文主要是讲解关于 SVG 的一些高级动画特效,比如 SVG 动画标签,图形渐变,路径动画,线条动画,SVG 裁剪等。</p> <p>例如:路径动画</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-6.gif" alt="" /></p> <p>图形渐变:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-2.gif" alt="" /></p> <p>线条动画:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-3.gif" alt="" /></p> <p>以及,相关的动画的矩阵知识,这个也是现在 CSS 动画里面最重要,同时也是最为欠缺的知识点:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-4.jpg" alt="" /></p> <p>文章会先从基本语法入手,然后,慢慢深入。介绍一些动画基本原理和对应的数学原理知识点。并且文章后面,还附有相关语法的介绍,当你在遇到不熟悉语法的时候可以参考参考。</p> <p><a href="http://xxysy.com/quot;//www.villainhr.com/page/2017/04/17/SVG%2520%25E5%25BF%25AB%25E9%2580%259F%25E5%2585%25A5%25E9%2597%25A8">前面一篇文章</a>,主要介绍了一" SVG 的基本概念和基本图形。接下来我们需要了解一下,SVG 处理矢量这个特性之外,还有啥内容吸引我们,能让 SVG 现在普及度这么高?</p> <blockquote> <p>有关于SVG的基本概念和基本图形更多的信息,可以阅读《<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/blog/tags/647.html">SVG之旅</a>》系列笔记。</p>" </blockquote> <h2>SVG Animation</h2> <p>在 SVG 中,如果我们想实现一个动画效果,可以使用 CSS,JS,或者直接使用 SVG 中自带的 <code>animate</code> 元素添加动画。</p> <p>使用 CSS 的话,有两种选择一种是通过 <code>style</code> 直接内联在里面,另外是直接使用相关的动画属性 —— <code>transform</code>。</p> <pre><code>&lt;use id="star" class="starStyle" xlink:href="http://xxysy.com/quot;#starDef"" transform="translate(100, 100)" style="fill: #008000; stroke: #008000"/&gt; </code></pre> <p>而使用 SVG 中自定的 <code>animate</code> 主要还是 SVG 自己的东西,比较好用。如果想用 CSS 的动画,这都无所谓。</p> <p>先看一个 SVG <code>animate</code> DEMO:</p> <pre><code>&lt;rect x="10" y="10" width="200" height="20" stroke="black" fill="none"&gt; &lt;animate attributeName="width" attributeType="XML" from="200" to="20" begin="0s" dur="5s" fill="freeze" /&gt; &lt;/rect&gt; </code></pre> <p>通过将 <code>animate</code> 标签嵌套在指定的图形里面,即可实现变换的效果。另外,还有 <code>animateTransform</code>,它主要是用来做变形动画的。</p> <pre><code>&lt;rect x="-10" y="-10" width="20" height="20" style="fill: #ff9; stroke: black;"&gt; &lt;animateTransform attributeType="XML" attributeName="transform" type="scale" from="1" to="4 2" begin="0s" dur="4s" fill="freeze"/&gt; &lt;/rect&gt; </code></pre> <p>简单来说:</p> <ul> <li><code>animate</code>: 相当于 CSS 中的 <code>transition</code></li> <li><code>animateTransform</code>: 相当于 CSS 中的 <code>transform</code></li> </ul> <p>里面一些技术细节我们这里就不过多讲解了。这里,主要想介绍一下 <code>animate</code> 中的 morph 的效果。</p> <h3>animate morph</h3> <p>该效果主要做的就是图形内部的渐变。如图:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-5.gif" alt="" /></p> <p>这种动画是怎么实现呢?</p> <p>直接看代码吧:</p> <pre><code>&lt;path fill="#1EB287"&gt; &lt;animate attributeName="d" dur="1440ms" repeatCount="indefinite" keyTimes="0; .0625; .208333333; .3125; .395833333; .645833333; .833333333; 1" calcMode="spline" keySplines="0,0,1,1; .42,0,.58,1; .42,0,1,1; 0,0,.58,1; .42,0,.58,1; .42,0,.58,1; .42,0,.58,1" values="M 0,0 C 50,0 50,0 100,0 100,50 100,50 100,100 50,100 50,100 0,100 0,50 0,50 0,0 Z; M 0,0 C 50,0 50,0 100,0 100,50 100,50 100,100 50,100 50,100 0,100 0,50 0,50 0,0 Z; M 50,0 C 75,25 75,25 100,50 75,75 75,75 50,100 25,75 25,75 0,50 25,25 25,25 50,0 Z; M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z; M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z; M 50,0 C 77.6,0 100,22.4 100,50 100,77.6 77.6,100 50,100 22.4,100, 0,77.6, 0,50 0,22.4, 22.4,0, 50,0 Z; M 50,0 C 77.6,0 100,22.4 100,50 100,77.6 77.6,100 50,100 22.4,100, 0,77.6, 0,50 0,22.4, 22.4,0, 50,0 Z; M 100,0 C 100,50 100,50 100,100 50,100 50,100 0,100 0,50 0,50 0,0 50,0 50,0 100,0 Z; "/&gt; &lt;/path&gt; </code></pre> <p>这么多,是不是感觉有点懵逼。不过,我们细分来看一下其实很简单。里面主要是利用 <code>animate</code> 中的 <code>keyTimes</code>,<code>calcMode</code>,<code>keySplines</code>,以及 <code>values</code> 这几个属性。不急,我们一个一个来解释一下。</p> <ul> <li><code>keyTimes</code>: 这其实和 CSS 中定义的 <code>@keyframes</code> 一样。通过 <code>0-1</code> 之间的值,定义每段动画完成的时间。格式为:<code>value;value...</code>。例如 <code>0;.0625;.208333333;.3125;.395833333;.645833333;.833333333;1</code>。从第一个动画,到第二个动画经历的时间比例为 <code>6.25%</code>。并且,<code>keyTimes</code> 需要和 <code>values</code> 里面定义的帧数一致。</li> <li><code>calcMode</code>: 用来定义动画具体的插值模型。取值有: <code>discrete | linear[default] | paced | spline</code>。具体可以参考 <a href="http://xxysy.com/quot;//developer.mozilla.org/en-US/docs/Web/SVG/Attribute/calcMode">MDN</a>。这里我们主要介绍一" <code>spline</code>。该值表示每个动画间使用自定的贝塞尔变换曲线。如果没有特殊要求,使用 <code>linear</code> 其实已经足够了,这样就不用麻烦去定义下面的 <code>keySplines</code> 属性。</li> <li><code>keySplines</code>:该值用来具体定义动画执行时的 贝塞尔曲线。使用格式是通过 ; 来分隔每一个值。即,<code>cubic-bezier(.31,.57,.93,.46)</code> 为一组。使用 <code>keySplines</code> 表达,则为:<code>keySplines = ".31,.57,.93,.46;"</code>。当然,里面的贝塞尔曲线组数为 整个动画帧数 <code>-1</code>。</li> </ul> <p>而 <code>values</code> 就很简单了。它是直接结合 <code>attributeName</code> 属性,来设置具体的值,每个值之间使用; 进行分隔。</p> <p>像上面那样,可以在指定元素里面嵌套多个 <code>animate</code>,既实现了形状的改变,又实现了颜色的改变。Morph 比较常用于数字的更迭,比如,<a href="http://xxysy.com/quot;//codepen.io/felixhornoiu/pen/dovub">倒" <code>10s</code> 的相关动画</a>。到这里,Morpah 相关的知识点就结束了。</p> <p>接着,让我们来看一下 SVG 中,另外一非常重要的标签 —— <code>animateMotion</code>。</p> <p>该标签可以让指定的元素,绕着指定的路径进行运动。所以这对于复杂的路径来说非常有用,因为我们很难使用 <code>transform</code> 去模拟复杂的变换路径。看一个 DEMO</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-6.gif" alt="" /></p> <h3>animateMotion</h3> <p><code>animateMotion</code> 大致的属性和 <code>animate</code> 差不多,不过,它还拥有自己特有的属性,比如 <code>keyPoints</code>、<code>rotate</code>、<code>path</code> 等。不过,<code>calcMode</code> 在 AM(<code>animateMotion</code>) 中的默认属性由,<code>linear</code> 变为 <code>paced</code>。</p> <p>这些属性,我们慢慢介绍,先从最简单的开始吧。首先,我们来看一个 DEMO:</p> <pre><code>&lt;g&gt; &lt;rect x="0" y="0" width="30" height="30" style="fill: #ccc;"/&gt; &lt;circle cx="30" cy="30" r="15" style="fill: #cfc; stroke: green;"/&gt; &lt;animateMotion from="0,0" to="60,30" dur="4s" fill="freeze"/&gt; &lt;/g&gt; </code></pre> <ul> <li><code>from</code>,<code>to</code>:指定两点的位置,位置参数是以元素的坐标为原点的。</li> <li><code>dur</code>:执行渲染时间</li> <li><code>fill</code>:指定动画结束后停留的装填。有 <code>freeze</code> 和 <code>remove</code> 效果。<code>remove</code> 表示回到动画开始的位置,<code>freeze</code> 表示停留在动画结束的位置。</li> </ul> <p>如果,你想要更复杂的路径,可以直接使用 <code>path</code> 属性来指定路径。用法和 <code>path</code> 标签中 <code>d</code> 属性是一样的。</p> <pre><code>&lt;rect x="0" y="0" width="30" height="30" style="fill: #ccc;"&gt; &lt;animateMotion path="M50,125 C 100,25 150,225, 200, 125" dur="6s" fill="freeze"/&gt; &lt;/rect&gt; </code></pre> <p>或者使用 <code>mpath</code> 标签,引用外部的 <code>path</code>。</p> <pre><code>&lt;path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110" stroke="lightgrey" stroke-width="2" fill="none" id="theMotionPath"/&gt; &lt;circle cx="10" cy="110" r="3" fill="lightgrey" /&gt; &lt;circle cx="110" cy="10" r="3" fill="lightgrey" /&gt; &lt;!-- Red circle which will be moved along the motion path. --&gt; &lt;circle cx="" cy="" r="5" fill="red"&gt; &lt;!-- Define the motion path animation --&gt; &lt;animateMotion dur="6s" repeatCount="indefinite"&gt; &lt;mpath xlink:href="http://xxysy.com/quot;#theMotionPath"/&gt" &lt;/animateMotion&gt; &lt;/circle&gt; </code></pre> <p>动画效果为:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-7.gif" alt="" /></p> <p>所以,一般而言我们在定义 AM 的路径的时候,只用一种方式定义即可,否则会发生相应的覆盖:<code>mpath&gt;path&gt;values&gt;from/to</code>。</p> <p>在 AM 运动中,还有一个很重要的概念就是旋转角。默认情况下,运动物体的角度是按照它和坐标轴的初始角度确定的。例如:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-8.gif" alt="" /></p> <p>这样看起来确实有些别扭,那能不能让物体垂直于路径进行运动呢?</p> <p>有的,根据 <code>rotate</code> 属性值,一共有 3 个值可供选择。</p> <p><code>auto</code>:让物体垂直于路径的切线方向运动。不过,如果你的路径是闭合曲线的话,需要注意起始点的位置。例如:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-9.gif" alt="" /></p> <p><code>auto-reverse</code>:让物体垂直于路径的切线方向并 <code>+180°</code>。也就是和 <code>auto</code> 运动关于切线对称。</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-10.gif" alt="" /></p> <p><code>Number</code>:让物体以固定的旋转角度运动。这个就相当于使用 <code>transform:rotate(deg)</code> 进行控制。</p> <p>在动画设置标签中,还有一个更简单的 —— <code>set</code>。</p> <h4>set</h4> <p>该标签也是用来模拟 <code>transition</code> 效果的。它和 <code>animate</code> 的主要区别是,它仅仅需要 <code>to</code> 的指定属性,而不需要其他的参考属性,比如 <code>from</code>,<code>by</code> 等。那它有啥特别的存在意义吗?</p> <p>有的,因为 <code>set</code> 针对于所有属性,甚至包括 <code>style</code> 里面的相关 CSS 属性。所以,可以靠它来很好描述一些非 <code>number</code> 的属性值。</p> <pre><code>&lt;text text-anchor="middle" x="60" y="60" style="visibility: hidden;"&gt; &lt;set attributeName="visibility" attributeType="CSS" to="visible" begin="4.5s" dur="1s" fill="freeze"/&gt; All gone! &lt;/text&gt; </code></pre> <h3>矩阵动画</h3> <p>上面差不多简单阐述了关于 SVG 一些比较有特点的动画。当然,还有比较重要的线条动画,这个我们放到后面进行讲解。这里先来看一下所有动画中,非常重要的矩阵原理。线性代数应该是大学里面来说,最容易学的一门科目,MD。。。还记得,大学线代期末考试的时候,100分的同学应该说是如韭菜地般,一抓一大片(对不起,我没能和他们同流合污。)</p> <p>那矩阵是如何在动画中使用的呢?</p> <p>简单的说,矩阵中的每个元素其实可以等价代换为每个因式里面的系数:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-11.jpg" alt="" /></p> <p>上面也叫作 三维矩阵。即,它涉及到 <code>x</code>,<code>y</code>,<code>z</code> 轴的计算。那对于我们平面 2D 变换来说,那么此时矩阵又是哪种形式呢?</p> <p>很简单,只要将 <code>z</code> 轴永远置为一个常数就 OK。这里,惯例上是直接取 <code>0 0 1</code> 来设置。</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-12.jpg" alt="" /></p> <p>不信的话,大家只要代进去乘以乘,应该就可以得到结果了。所以,在二维中,具体变换方式为:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-13.jpg" alt="" /></p> <p>后面,我们也会依据这个公式进行相关的变形操作。那矩阵变换是怎么运用到 CSS/SVG 当中呢?</p> <p>在 CSS 中,是直接使用 <code>transform</code> 中的属性:</p> <pre><code>transform: matrix(a,b,c,d,e,f); </code></pre> <p>当然,在 SVG 中也是一样的:</p> <pre><code>&lt;g transform="matrix(1,2,3,4,5,6)"&gt; &lt;line x1="10" y1="20" x2="30" y2="40" style="stroke-width: 10px; stroke: blue;"/&gt; &lt;/g&gt; </code></pre> <p>所以,我们主要的重点就是讲解一下 <code>matrix</code> 这个属性。它的格式为:</p> <pre><code>matrix(a,b,c,d,e,f); </code></pre> <p>对应于我们上面的公式有:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-14.jpg" alt="" /></p> <p>在接触 <code>transform</code> 的时候,大家应该了解到 <code>transform</code> 里面有很多固定的动画属性:</p> <ul> <li><code>translate()</code></li> <li><code>rotate()</code></li> <li><code>scale()</code></li> <li><code>skew()</code></li> </ul> <p>实际上,在底层还是使用 <code>matrix</code> 实现的变换。就拿 <code>translate</code> 举个例子吧。</p> <p><code>translate</code> 的格式为:</p> <pre><code>translate(dx,dy) </code></pre> <p>相当于参考当前原点,在 <code>x/y</code> 轴上移动 <code>dx/dy</code> 的距离。那么映射到矩阵,应该如何表示呢?</p> <p>很简单,它等同于:</p> <pre><code>matrix(1 0 0 1 dx dy); </code></pre> <p>使用代数证明一下:</p> <p>假设有 <code>matrix(1 0 0 1 20 30)</code>,变为矩阵为:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-15.jpg" alt="" /></p> <p>根据,上面的表达式有:</p> <pre><code>X = x'*1 + y'*0 + 20 = x' + 20 Y = x'*0 + y'*1 + 30 = y' + 30 </code></pre> <p>所以,就是 <code>X</code> 在原有 <code>X</code> 轴坐标上向右移动 <code>20</code> 的距离,<code>Y</code> 相对于原有移动 <code>30</code> 的距离。</p> <p>那么其他几个属性呢?也是怎么变化的吗?</p> <p>恩,类似。只是里面取值不一样:</p> <ul> <li><code>scale(x,y)</code>: 放大 <code>X/Y</code> 轴,矩阵的表达为 <code>matrix(x 0 0 y 0 0)</code>。</li> <li><code>rotate(θ)</code>: 坐标旋转,矩阵的表达为 <code>matrix(cosθ sinθ -sinθ cosθ 0 0)</code>。</li> <li><code>skew(θx,θy)</code>: <code>X/Y</code> 轴拉伸,矩阵的表达为 <code>matrix(1 tanθx tanθy 1 0 0)</code>。</li> </ul> <p>注意,上面三个都会改变原有物体的坐标系!!! 这点很重要,换句话说,后面每次变换都是基于前面一个的变换结果的。</p> <p>详情看图:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-16.jpg" alt="" /></p> <p>详情可以参考:<a href="http://xxysy.com/quot;//developer.mozilla.org/en/docs/Web/SVG/Attribute/transform">MD" matrix</a></p> <p>不过,这并不是我们使用 <code>matrix</code> 的重点,也不是它的优势。它的优势在于可计算,即,能够将复杂的动画集合到一个表达式中,并且,后续的变换可以直接基于当前的 <code>matrix</code>。</p> <p>我们先来了解一下,如果多个变换动画一起使用,<code>matrix</code> 应该如何表达呢?</p> <p>只需要找到我们变换动画对应的矩阵,然后相乘即可。例如,先旋转 <code>45°</code>,然后放大 <code>1.5</code> 倍,则有变换动画为:</p> <pre><code>transform: rotate(45deg) scale(1.5,1.5); </code></pre> <p>注意,虽然,你定义动画是分开的,但此时的动画是同时进行的。为啥?因为,这两个动画实际上可以整合成为一个变换矩阵:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-17.jpg" alt="" /></p> <p>并且,位置是不可以调换的。比如,<code>transform: scale(2,2) translate(20px,30px)</code>。即,你先放大两倍,然后移动 <code>20,30</code> 的距离。注意,这里移动的 <code>20,30</code> 相对的是已经放大过后的坐标,相对于原坐标而言就是 <code>40,60</code> 了。 如果,你调换位置,即 <code>transform: translate(20px,30px) scale(2,2)</code>。就变成现在原坐标移动 <code>20,30</code>,然后再放大两倍。</p> <p>而上面强调的顺序关系,实际上就可以理解为矩阵不满足交换律的原则。因为一旦交换,结果很可能不一样。</p> <h4>矩阵高级用法</h4> <p>上面的内容只是简单的描述了关于矩阵的概念。在实际中,矩阵可以说是真正利器。</p> <p>假设现在有一个动画,要求你将一个物体从一个点通过抛物线的方式移动到另外一个点,那么此时要求 JS/CSS 随你挑。此时,你会不会感觉,呼吸急促,头脑发热呢?</p> <p>恩,<code>matrix</code> 可以治,而且包治百病。不过,<code>matrix</code> 有一个限制点,它只能用于一次线性动画表达式。即,针对于抛物线,椭圆曲线这类复杂曲线来说,不太合适。那么有什么办法吗?</p> <p>有的,微分思想。每一段动画其实都可以通过一定范围内的直线拼接而成,那么这样,我们就可以将一段抛物线拆分为由几段线段构成的曲线。当然,如果你分的越细,拟合度就越高。这里我们不打算过度你和,我们简单的将一段抛物线分为 5段。</p> <p>如图:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-18.jpg" alt="" /></p> <p>那么接下来就是抠细节。这里,依次取倾角为 <code>45°</code>,<code>30°</code>,<code>0°</code>,<code>-45°</code>,<code>-30°</code> 这 5 段直线。每段分配的时间比例为 <code>20%</code>、<code>25%</code>、<code>10%</code>、<code>25%</code>、<code>20%</code> 这主要是用于 <code>keyframe</code> 的设定。现在,用数学来分析一下,这个动画到底该怎么弄。</p> <p>现在,已知两点之间的距离为 <code>100px</code>。那么我们同样根据上述比例分,则有 <code>20px</code>, <code>25px</code>, <code>10px</code>, <code>25px</code>, <code>20px</code>。</p> <p>这里我们以 <code>45°</code> 倾角为参考点,则终点坐标为 <code>(20,20)</code>; 。那么,该段的矩阵为:</p> <pre><code>// 注意 Y 轴需要取负值! 1 0 20 0 1 -20 0 0 1 </code></pre> <p>CSS 中的变形动画为:</p> <pre><code>transform: matrix(1,0,0,1,20,-20); </code></pre> <p>然后,第二段就为:</p> <pre><code>1 0 25 0 1 -14.4 0 0 1 </code></pre> <p>使用矩阵的乘法法,则有:</p> <pre><code>1 0 45 0 1 -34.4 0 0 1 </code></pre> <p>变形动画为:</p> <pre><code>transform: matrix(1,0,0,1,45,-34.4); </code></pre> <p>剩余几段也是这样的做法。最终,整个 <code>keyframe</code> 就应该表示为:</p> <pre><code>@keyframe Parabola{ 20%{ transform: matrix(1,0,0,1,20,-20); } 45%{ transform: matrix(1,0,0,1,45,-34.4); } ... } </code></pre> <p>整个动画过程差不多都是这样。当然,矩阵也不仅仅局限于这几个动画,凭借着高度定制化和灵活性的特点,这它还常常用于进行回弹,弹跳等动画中。如果大家有兴趣,后期也可以对这类动画进行简单的讲解。</p> <p>后面,我们最后来了解一下 SVG 中很重要的线条动画。</p> <h3>线条动画</h3> <p>SVG 中的线条动画常常用作过渡屏(splash screen)中。例如:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-19.gif" alt="" /></p> <p>或者,一些比较炫酷的 LOGO 中,比如 AllowTeam 的:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-20.gif" alt="" /></p> <p>看到这些炫酷的效果,大家有没有动心想学一学,看看自己到底能否做的这么好呢?</p> <p>OK,我们现在来正式介绍一下线条动画。在 SVG 中,最长用到的线条标签就是 <code>Path</code>。这里我前面一篇文章已经做了介绍,我这里就不赘述了。</p> <p>而在具体变化当中用到的是关于 <code>stroke</code> 的相关属性:(下面的属性都可以直接用在 CSS 当中!)</p> <ul> <li><code>stroke</code>:定义笔触的颜色。例如:<code>stroke="green"</code></li> <li><code>stroke-dasharray</code>:定义 <code>dash</code> 和 <code>gap</code> 的长度。它主要是通过使用 , 来分隔 实线 和 间隔 的值。例如:<code>stroke-dasharray="5, 5"</code> 表示,按照 实线为 <code>5</code>,间隔为 <code>5</code> 的排布重复下去。如下图:</li> </ul> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-21.jpg" alt="" /></p> <p>放大看有:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-22.jpg" alt="" /></p> <p>另外,<code>stroke-dasharray</code> 并不局限于只能设置两个值,要知道,它本身的含义是设置最小重复单元,即,<code>dash,gap,dash,gap...</code>。比如,我定义 <code>stroke-dasharray="15, 10, 5"</code> 则相当于,<code>[15,10,5]</code> 为一段。则有:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-23.jpg" alt="" /></p> <p>放大看则有:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-24.jpg" alt="" /></p> <ul> <li><code>stroke-dashoffset</code>: 用来设置 <code>dasharray</code> 定义其实 <code>dash</code> 线条开始的位置。值可以为 <code>number || percentage</code>。百分数是相对于 SVG 的 <code>viewport</code>。通常结合 <code>dasharray</code> 可以实现线条的运动。</li> <li><code>stroke-linecap</code>: 线条的端点样式。 </li> <li><code>stroke-linejoin</code>: 线条连接的样式</li> <li><code>stroke-miterlimit</code>: 一个比较复杂的概念,如果我们只是画一些一般的线段,使用上面 <code>linejoin</code> 即可。如果涉及对边角要求比较高的,则可以使用该属性进行定义。它的值,其实就是角长度比上线宽:</li> </ul> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-25.jpg" alt="" /></p> <p>而实际理解的话,就是假设当 <code>width</code> 为 <code>1</code>。此时比例为 <code>2</code>。那么 <code>miter = 2</code>。那么超过 <code>2</code> 的 <code>miter</code> 部分则会被 <code>cut</code>掉。可以参照:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-26.jpg" alt="" /></p> <p>他主要是配合 <code>linejoin</code> 一起使用。因为 <code>linejoin</code> 默认取值就是 <code>miter</code>。所以,默认情况下就可以使用该标签属性。它默认值为 <code>4</code>。其余的大家下去实践一下即可。详细可以参考: <code>miter</code></p> <ul> <li><code>stroke-opacity</code>:线段的透明度</li> <li><code>stroke-width</code>:线的粗细</li> </ul> <p>OK,介绍完关于 <code>path</code> 的所有 <code>stroke</code> 属性之后,我们就要开始动手写一下让线条动起来的代码。简单来说,就是通过 <code>stroke-dashoffset</code> 和 <code>stroke-dasharray</code> 来做。整个动画可以分为两个过程:</p> <ul> <li>通过 <code>dasharray</code> 将实线部分隐藏,空余为全线段长。然后,将实线部分增加至全长。比如:<code>dasharray: 0,1000</code> 变为 <code>dasharray: 1000,1000</code>。</li> <li>同时,通过 <code>dashoffset</code> 来移动新增的实线部分,造成线段移动的效果。有: <code>dashoffset:0</code>,变为 <code>dashoffset:1000</code>。</li> </ul> <p>不过,这里我们不打算使用 <code>Path</code> 来做啥复杂的动画,这主要考虑到手头没有一些 SVG 生成工具。所以,这里我们就以 <code>Text</code> 来做吧(因为做起来真的简单)。</p> <p>这里,先以 IV-WEB 这段文字来做动画。</p> <p>先给大家看一下最终结果:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-27.gif" alt="" /></p> <p>那么这种动画是怎么做的呢?</p> <p>这里,我主要介绍一下关于 CSS 相关,SVG 就一个 <code>Text</code> 我直接贴代码了:</p> <pre><code>&lt;svg viewBox="0 0 1320 300"&gt; &lt;!-- Symbol --&gt; &lt;symbol id="s-text"&gt; &lt;text text-anchor="middle" x="50%" y="50%" dy=".35em"&gt;IV-WEB&lt;/text&gt; &lt;/symbol&gt; &lt;!-- Duplicate symbols --&gt; &lt;use xlink:href="http://xxysy.com/quot;#s-text"" class="text"&gt;&lt;/use&gt; &lt;use xlink:href="http://xxysy.com/quot;#s-text"" class="text"&gt;&lt;/use&gt; &lt;use xlink:href="http://xxysy.com/quot;#s-text"" class="text"&gt;&lt;/use&gt; &lt;/svg&gt; </code></pre> <p>上面是通过创建一个居中定位的字体,然后使用 <code>3</code> 个 <code>text</code> 重叠。具体 CSS 我们下面来说一下。首先,我们营造的效果是从无到有,就需要使用 <code>dasharray</code> 将 <code>gap</code> 设置的足够大。这里我取 <code>300</code> 即可。</p> <pre><code>stroke-dasharray: 0 300; </code></pre> <p>然后,通过 <code>nth-child</code> 选择器,给每一个文字使用不同的颜色值:</p> <pre><code>.text:nth-child(3n + 1) { stroke: #F60A0A; } .text:nth-child(3n + 2) { stroke: #F2FF14; } .text:nth-child(3n + 3) { stroke: #FB9505; } </code></pre> <p>下面才是重点内容。此时,这 <code>3</code> 个 <code>text</code> 的起始点重合。我现在既要他们在运行时不完全重合,又要他们的线条能进行滚动。不啰嗦了,直接看代码吧:</p> <pre><code>@keyframes stroke { 100% { stroke-dashoffset: 1000; stroke-dasharray: 80 160; } } @keyframes stroke1 { 100% { stroke-dashoffset: 1080; stroke-dasharray: 80 160; } } @keyframes stroke2 { 100% { stroke-dashoffset: 1160; stroke-dasharray: 80 160; } } </code></pre> <p>这就是上面 <code>3</code> 个不同的 <code>text</code> 运用的动画。<code>dashoffet</code> 由 <code>0</code> 到 <code>1000</code>。这完成了滚动的目的。同时,为了让字体不重合,我还需要在对应字体的 <code>dashoffset</code> 上,加上不同的间隔距离。比如,第一个字体 <code>offset</code> 为 <code>1000</code>。那么第二个字体,我需要加上前一个字体 <code>dash</code> 的长度,即,<code>80</code>。所以,第二个字体就变为 <code>1080</code>。那么第三个就是加上前两个的 <code>dash</code> 长度,即 <code>1160</code>。</p> <p>大致过程就是这样,详情可以查看: <a href="http://xxysy.com/quot;//codepen.io/JimmyVV/pen/oWWzdB">IVWE" 线条动画</a>。</p> <blockquote> <p>这里再给大家布置一个练习作业,如何实现无线连续的分段动画呢?</p> </blockquote> <p>具体效果如图:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-28.gif" alt="" /></p> <p>给点提示:</p> <blockquote> <p>将多个文字重叠,取不同的 <code>offset</code> 和 <code>array</code> 即可。动画的终止位置一般取一个 <code>gap + dash</code> 的周期长即可。</p> </blockquote> <p>后面看看这篇文章反响如何,到时候再决定是否再写一篇续集,介绍该作业的原理。</p> <h2>SVG 中使用 transition</h2> <p>在 SVG 中,本来就存在相关的动画 Tag,不过里面用起来比较复杂,最常用的还是直接利用 CSS 里面相关的属性标签来做。其中,<code>transition</code> 是最常用的。比如,想做一下颜色的渐变等等。这里可以直接利用 <code>transition</code> 修饰指定的 SVG 属性即可。</p> <pre><code># fill 是 SVG 中,专有属性 path { transition: fill .4s ease; } #europe path { fill: red; } #europe:hover path { fill: white; } </code></pre> <h2>SVG 文字</h2> <p>在 SVG 中定义文字直接使用 <code>text</code> 标签即可。关于文字来说,一般而言需要注意的点就那么即可,文字的排列,间距等等。这些都可以直接使用 CSS 进行控制。不过,有几个属性比较特殊,这里需要额外提一下。</p> <h3>text-anchor</h3> <p>用来定义参考点和实际字符之间的定位关系。格式为:</p> <pre><code>text-anchor: start | middle | end | inherit </code></pre> <p>直接看代码解释吧:</p> <pre><code>&lt;!-- Anchors in action --&gt; &lt;text text-anchor="start" x="60" y="40"&gt;A&lt;/text&gt; &lt;text text-anchor="middle" x="60" y="75"&gt;A&lt;/text&gt; &lt;text text-anchor="end" x="60" y="110"&gt;A&lt;/text&gt; </code></pre> <p>第一个 <code>A</code>,参考的是 <code>(60,40)</code> 的点,定义为 <code>start</code> ,那么参考点应该在字符的前面。</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-29.jpg" alt="" /></p> <p>而剩下两个也是同样的道理:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-30.jpg" alt="" /></p> <h3>tspan</h3> <p>现在,假如我们想在 <code>text</code> 里面添加一些特殊的字符效果,比如斜体,加粗等。由于,<code>text</code> 标签不能实现嵌套,所以,为了解决这个痛点,提出了 <code>tspan</code> 的标签。它其实就是一个可以嵌套的 <code>text</code> 标签。</p> <pre><code>&lt;text x="10" y="30" style="font-size:12pt;"&gt; Switch among &lt;tspan style="font-style:italic"&gt;italic&lt;/tspan&gt;, normal, and &lt;tspan style="font-weight:bold"&gt;bold&lt;/tspan&gt; text. &lt;/text&gt; </code></pre> <p><code>tspan</code> 里面同样可以自定义相关的自身属性。详细的可以参考 <code>tspan</code> 我这里就不详述了。</p> <h3>在 Path 展示 text</h3> <p><code>Text</code> 一般可以横放,竖放。那有没有啥办法让文字可以按照一定的路径任意排放呢?</p> <p>有的,这里可以使用 <code>textPath</code> 标签,来定义具体参考路径。</p> <pre><code>&lt;path id="sharp-corner" d="M 30 110 100 110 100 160" style="stroke: gray; fill: none;"/&gt; &lt;text&gt; &lt;textPath xlink:href="http://xxysy.com/quot;#sharp-corner"&gt" Making a quick turn &lt;/textPath&gt; &lt;/text&gt; </code></pre> <p>如图:</p> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-31.jpg" alt="" /></p> <p>具体细节我这里就不多说了。</p> <h2>Clip</h2> <p>在 DOM 中如果想展示一个图片的部分,或者以某种形状展示图片的部分,一般是通过一个 <code>cover div</code> 来实现的。不过,如果涉及到不规则图形的话,那么 DOM 就有天生缺陷了(当然使用 CSS 里的 <code>clip-path</code> 可以完成,不过兼容性不太好)。而在 SVG 中,提供了 <code>clipPath</code> 标签,能够让我们自定义裁剪图片的范围和形状。</p> <p><code>clipPath</code> 里面可以接任何图形,比如,<code>path</code>,<code>rect</code> 甚至是 <code>text</code>。使用的时候,直接在 <code>style</code> 中,指定 <code>clip-path</code> 即可,或者直接使用 <code>clip-path</code> 属性指定。</p> <pre><code>&lt;defs&gt; &lt;clipPath id="textClip"&gt; &lt;text id="text1" x="20" y="20" transform="rotate(60)" style="font-family: 'Liberation Sans'; font-size: 48pt; stroke: black; fill: none;"&gt;CLIP&lt;/text&gt; &lt;/clipPath&gt; &lt;/defs&gt; &lt;use transform="translate(100, 0)" xlink:href="http://xxysy.com/quot;#shapes"" style="clip-path: url(#textClip);"/&gt; &lt;use transform="translate(100, 0)" xlink:href="http://xxysy.com/quot;#shapes"" clip-path="url(#textClip);"/&gt; </code></pre> <p><img src="/sites/default/files/blogs/2018/1804/svg-animation-32.jpg" alt="" /></p> <p>或者说,如果我们想画一个圆的裁剪区域的话:</p> <pre><code>&lt;defs&gt; &lt;clipPath id="circularPath" clipPathUnits="objectBoundingBox"&gt; &lt;circle cx="0.5" cy="0.5" r="0.5"/&gt; &lt;/clipPath&gt; &lt;/defs&gt; &lt;use xlink:href="http://xxysy.com/quot;#shapes"" style="clip-path: url(#circularPath);" /&gt; </code></pre> <h2>Appendix 参考标签</h2> <h3>g</h3> <p>分组标签应该毫无意外排第一,因为其实作为绘制图形中最常和最基本的标签。前面一篇文章也主要介绍过了,这里做点补充。</p> <p>每一个分组标签都带有 <code>id</code> 属性,唯一标识该分组,为什么呢?</p> <p>因为,后面我们可以使用该 <code>id</code> 标签添加动画,重用该分组等。</p> <pre><code>&lt;g id="demo" stroke="green" fill="white" stroke-width="5"&gt; &lt;circle cx="25" cy="25" r="15"/&gt; &lt;circle cx="40" cy="25" r="15"/&gt; &lt;circle cx="55" cy="25" r="15"/&gt; &lt;circle cx="70" cy="25" r="15"/&gt; &lt;/g&gt; </code></pre> <p>每个分组里面可以含有一些描述标签,比如 <code>desc</code>。 这些描述内容是不会被渲染的。</p> <pre><code>&lt;g id="demo" stroke="green" fill="white" stroke-width="5"&gt; &lt;desc&gt;Just Demo&lt;/desc&gt; &lt;circle cx="25" cy="25" r="15"/&gt; &lt;/g&gt; </code></pre> <h3>use</h3> <p>该标签就是结合 <code>g</code> 标签一起使用,作用是可以复用 <code>g</code> 分组的样式。</p> <pre><code>&lt;g id="Port"&gt; &lt;circle style="fill: inherit;" r="10"/&gt; &lt;/g&gt; &lt;use x="50" y="30" xlink:href="http://xxysy.com/quot;#Port"" class="classA"/&gt; </code></pre> <p>里面使用 <code>xlink:href</code> 加上指定 <code>group</code> 的 <code>id</code>,然后通过 <code>x</code>,<code>y</code> 属性指定副本放置的位置。不过,有一个限制,<code>use</code> 标签的 <code>style</code> 属性,并不能覆盖点原始的 <code>group style</code> 样式。而且,有时候,我们只是想使用一些模板,即,图形并未被解析,只有代码存在。这时候,就需要使用 <code>defs</code> 来包裹了。</p> <h3>defs</h3> <p>用来保存一些代码,使其不会被浏览器解析。并且里面的分组可以被 <code>use</code> 属性的 <code>style</code> 样式所覆盖。</p> <pre><code>&lt;defs&gt; &lt;g id="Port"&gt; &lt;circle style="fill: inherit;" r="10"/&gt; &lt;/g&gt; &lt;/defs&gt; &lt;use x="50" y="50" xlink:href="http://xxysy.com/quot;#Port"" style="fill: blue;"/&gt; </code></pre> <h3>symbol</h3> <p>该标签和 <code>g</code> 标签类似,也是用来进行分组。不过,它有个特点,即,不会被浏览器所渲染。那它不和 <code>defs</code> 差不多吗?</p> <p>恩,确实。不过,<code>defs</code> 是官方推荐,用来包裹一些模板 <code>svg</code> 代码而创造出来,用来增加可读性的标签。而 <code>symbol</code> 是存粹的作为一个模板。它可以独立于 <code>svg</code> 的 <code>viewbox</code> 来自定义子 <code>viewbox</code> 和 <code>preserveAspectRatio</code>。</p> <pre><code>&lt;symbol id="sym01" viewBox="0 0 150 110"&gt; &lt;circle cx="50" cy="50" r="40" stroke-width="8" stroke="red" fill="red"/&gt; &lt;circle cx="90" cy="60" r="40" stroke-width="8" stroke="green" fill="white"/&gt; &lt;/symbol&gt; &lt;use href="http://xxysy.com/quot;#sym01"" x="0" y="0" width="100" height="50"/&gt; </code></pre> <p>同样使用该模板,也是使用 <code>use</code> 标签来完成。</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/svg-animation.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/svg/svg-animation.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/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;/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 class="field-item odd"><a href="http://xxysy.com/quot;/blog/tags/29.html"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Animation</a></div></div></div> Fri, 13 Apr 2018 13:36:49 +0000 Airen 2383 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com w3cplus_引领web前沿,打造前端精品教程 - 伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】 https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/list-rendering-and-vues-v-for-directive.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="//css-tricks.com/author/hassandjirdeh/">@HASSAN DJIRDEH</a>的《<a href="//css-tricks.com/list-rendering-and-vues-v-for-directive/">List Rendering and Vue’s v-for Directive</a>》一文。</p> </blockquote> <p>Web渲染是Web开发中最常用的实战之一。动态列表渲染通常用于简洁友好的格式向用户渲染一系列相似的分组信息。在我们使用的每个Web应用程序中,都可以看到很多内容列表被用于Web应用程序当中。</p> <p>在这篇文章中,我们将收集有关于Vue中的<code>v-for</code>指令生生动态列表的理解,并通过一些示例来说明为什么在这样做的时候应该使用<code>key</code>属性。</p> <p>由于我们将在开始编写代码时全面地解释一些事情,本文假设你对Vue或其他JavaScript框架有一定的了解。</p> <h2>案例:Twitter</h2> <p>我将使用 <a href="http://xxysy.com/quot;//twitter.com/">Twitter</a>作为本文的案例。</p>" <p>当登录了Twitter并进入其首页时,我们会看到一个类似下图的一个效果:</p> <p><img src="/sites/default/files/blogs/2018/1804/v-for_1.png" alt="" /></p> <p>在首页上,我们可以看到面页包括了你的趋势,信息列表,推荐关注等模块。在这些列表显示的内容取决于多种因素:Twitter历史,你的关注者和你喜欢的推荐等。因此,我们可以说所有这些数据都是动态的。</p> <p>尽管这些数据都是动态获取的,但数据显示的方式相同的。这样就可以使用可重用的Web伟德1946手机版。</p> <p>例如,我们可以将Tweets列表看作是单个的<code>tweet-component</code>伟德1946手机版列表。我们可以把<code>tweet-componet</code>看作是一个Shell,它可以接收各种数据,比如用户外、句柄、tweet和用户头像,以及其他一些片段,它们只是以一致的标记显示这些片段。</p> <p><img src="/sites/default/files/blogs/2018/1804/v_for_2.png" alt="" /></p> <p>假设我们希望从服务器上获取一个大数据用于渲染一个伟德1946手机版列表(例如,<code>tweet-component</code>伟德1946手机版中的列表)。在Vue中,应该首先想到的是<code>v-for</code>指令。</p> <h2><code>v-for</code>指令</h2> <p><code>v-for</code>指令用于基于数据源来渲染项目列表。该指令可以在模板元素上使用,并且需要一个特定的语法:</p> <p><img src="/sites/default/files/blogs/2018/1804/v-for_3.png" alt="" /></p> <blockquote> <p>有关于<code>v-for</code>更多的介绍可以<a href="http://xxysy.com/quot;//www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/v-for.html">点击这里</a>进行了解。</p>" </blockquote> <p>我们来看看这个例子。首先,我们假设我们已经获得一个<code>tweets</code>数据的集合:</p> <pre><code>const tweets = [ { id: 1, name: 'James', handle: '@jokerjames', img: 'https://semantic-ui.com/images/avatar2/large/matthew.png', tweet: "If you don't succeed, dust yourself off and try again.", likes: 10, }, { id: 2, name: 'Fatima', handle: '@fantasticfatima', img: 'https://semantic-ui.com/images/avatar2/large/molly.png', tweet: 'Better late than never but never late is better.', likes: 12, }, { id: 3, name: 'Xin', handle: '@xeroxin', img: 'https://semantic-ui.com/images/avatar2/large/elyse.png', tweet: 'Beauty in the struggle, ugliness in the success.', likes: 18, } ] </code></pre> <p><code>tweets</code>是<code>tweet</code>对象集合,每个<code>tweet</code>包含特定的推特详细信息 —— 一个唯一的标识符、账户名、推特消息等。现在我们尝试使用<code>v-for</code>指令基于这些数据来渲染<code>tweet-component</code>列表。</p> <p>首先,先创建Vue实例——Vue应用程序的核心。我们把实例挂载到<code>id</code>为<code>app</code>的DOM元素上,并将<code>tweets</code>设置为实例数据对象的一部分。</p> <pre><code>new Vue({ el: '#app', data: { tweets } }); </code></pre> <p>现在,我们将创建一个<code>tweet-component</code>伟德1946手机版,通过<code>v-for</code>指令来渲染<code>tweet</code>列表。使用全局的<code>Vue.component</code>构造函数来创建一个名为<code>tweet-component</code>的伟德1946手机版:</p> <pre><code>Vue.component('tweet-component', { template: ` &lt;div class="tweet"&gt; &lt;div class="box"&gt; &lt;article class="media"&gt; &lt;div class="media-left"&gt; &lt;figure class="image is-64x64"&gt; &lt;img :src="tweet.img" alt="Image"&gt; &lt;/figure&gt; &lt;/div&gt; &lt;div class="media-content"&gt; &lt;div class="content"&gt; &lt;p&gt; &lt;strong&gt;{{tweet.name}}&lt;/strong&gt; &lt;small&gt;{{tweet.handle}}&lt;/small&gt; &lt;br&gt; {{tweet.tweet}} &lt;/p&gt; &lt;/div&gt; &lt;div class="level-left"&gt; &lt;a class="level-item"&gt; &lt;span class="icon is-small"&gt;&lt;i class="fas fa-heart"&gt;&lt;/i&gt;&lt;/span&gt; &lt;span class="likes"&gt;{{tweet.likes}}&lt;/span&gt; &lt;/a&gt; &lt;/div&gt; &lt;/div&gt; &lt;/article&gt; &lt;/div&gt; &lt;/div&gt; `, props: { tweet: Object } }); </code></pre> <p>这里有一些有趣的东西值得我们去关注。</p> <ul> <li><code>tweet-component</code>期望<code>props</code>中的<code>tweet</code>是一个对象(<code>props:{tweet:Object}</code>)。如果伟德1946手机版中<code>props</code>的<code>tweet</code>不是一个对象,那么Vue将会发出一个警告信息</li> <li>使用Mustache语法<code>{{}}</code>,将<code>props</code>的<code>tweet</code>对象绑定到伟德1946手机版模板</li> <li>伟德1946手机版标记与<a href="http://xxysy.com/quot;//bulma.io/documentation/elements/box/">Bulma的Box元素</a>相似,因为它与<code>tweet</code>很类似</li>" </ul> <p>在HTML模板中,我们需要把创建的模板挂载到Vue的应用程序中(即,<code>id</code>为<code>app</code>的元素中)。在这个标记中,我们将使用<code>v-for</code>指令来呈现一个<code>tweet</code>列表。因为<code>tweets</code>是我们将要迭代的数据集合,所以<code>tweet</code>将是在指令中使用合适的别名。在每个呈现的<code>tweet-component</code>伟德1946手机版中,将对伟德1946手机版中<code>props</code>的<code>tweet</code>对象做遍历。</p> <pre><code>&lt;div id="app" class="columns"&gt; &lt;div class="column"&gt; &lt;tweet-component v-for="tweet in tweets" :tweet="tweet"/&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <p>不管<code>tweet</code>对象中有多少条信息,我们的设置将总是以我们所期望的相同标记渲染<code>tweet</code>中的每条信息。</p> <p>添加一些CSS样式之后,我们看到的效果将是这样的:</p> <div style="margin-bottom: 20px;"><iframe id="wmZEvM" src="//codepen.io/airen/embed/wmZEvM?height=400&amp;theme-id=0&amp;slug-hash=wmZEvM&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>尽管正如我们所预期的一样,程序能正常运行,但在浏览器的控制台中出现一个Vue的提示信息:</p> <pre><code>[Vue tip]: &lt;tweet-component v-for="tweet in tweets"&gt;: component lists rendered with v-for should have explicit keys... </code></pre> <blockquote> <p>注意,如果你在Codepen环境下运行此代码,你可能无法看到浏览器控制台报上面的提示信息。</p> </blockquote> <p>为什么可以按我们预期的运行,但Vue会提示要显式的指定<code>key</code>的值呢?</p> <h2>Key</h2> <p>在使用<code>v-for</code>指令渲染列表,为每个迭代的元素指定一个<code>key</code>属性是很常见的做法。这是因为Vue使用<code>key</code>属性为每个节点的标识创建唯一的绑定。</p> <p>解释一下。如果我们的列表中有任何动态的UI更改(比如列表项的顺序被打乱),Vue将选择在每个元素中更改数据,而不是相应的移动DOM元素。这在大多数情况下都不是问题。然而,在某此情况下,<code>v-for</code>渲染出来的列表项依赖于DOM状态和(或)子伟德1946手机版状态,这可能会导致一些非预期的渲染效果。</p> <p>让我们来看一个例子。如果<code>tweet-component</code>伟德1946手机版包含了一个允许用户直接输入消息的字段呢?我们将忽略如何提交信息的响应,并简单地处理新输入字段本身:</p> <p><img src="/sites/default/files/blogs/2018/1804/v_for_4.png" alt="" /></p> <p>把这个新的输入字段添加到<code>tweet-component</code>伟德1946手机版中:</p> <pre><code>Vue.component('tweet-component', { template: ` &lt;div class="tweet"&gt; &lt;div class="box"&gt; // ... &lt;/div&gt; &lt;div class="control has-icons-left has-icons-right"&gt; &lt;input class="input is-small" placeholder="Tweet your reply..." /&gt; &lt;span class="icon is-small is-left"&gt; &lt;i class="fas fa-envelope"&gt;&lt;/i&gt; &lt;/span&gt; &lt;/div&gt; &lt;/div&gt; `, props: { tweet: Object } }); </code></pre> <p>假设我们想要在我们的应用程序中引入另一个新功能,这个功能包括允许用户随机打乱消息列表。</p> <p>要做到这一点,我们需要先在HTML模板中添加一个"Shuffle!"按钮。</p> <pre><code>&lt;div id="app" class="columns"&gt; &lt;div class="column"&gt; &lt;button class="is-primary button" @click="shuffle"&gt;Shuffle!&lt;/button&gt; &lt;tweet-component v-for="tweet in tweets" :tweet="tweet"/&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <p>我们在按钮元素上添加一个单击事件监听器,在触发时将调用<code>shuffle</code>方法。在我们的Vue实例中需要创建一个<code>shuffle</code>方法,负责在实例中随机打乱<code>tweets</code>集合。我们将使用<a href="http://xxysy.com/quot;//lodash.com/docs/4.17.5#shuffle"><code>lodash</code>的<code>_shuffle</code>方法</a>来实现这个功能。</p>" <pre><code>new Vue({ el: '#app', data: { tweets }, methods: { shuffle() { this.tweets = _.shuffle(this.tweets) } } }); </code></pre> <p>来试试效果。如果我们点击<code>shuffle</code>按钮几次,我们会注意到,我们的<code>tweet</code>列表元素会被随机分类。</p> <div style="margin-bottom: 20px;"><iframe id="oqOaoY" src="//codepen.io/airen/embed/oqOaoY?height=400&amp;theme-id=0&amp;slug-hash=oqOaoY&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>但是,如果我们在每个伟德1946手机版的<code>input</code>中输入一些信息,然后单击<code>shuffle</code>按钮,我们会发现有一些奇怪的事情发生,而且超出我们所预期的效果:</p> <p><img src="/sites/default/files/blogs/2018/1804/v_for_random_.gif" alt="" /></p> <p>由于我们没有使用<code>key</code>属性,Vue并没有为每个<code>tweet</code>节点创建唯一的绑定。因此,当我们打算重新对<code>tweets</code>排序时,Vue采用了更有效的,更节省的方法简单地在对每个元素进行数据的更改(或修补)。由于临时的DOM状态(即输入的文本)仍然存在,造成给我们的体验就如上图所示的效果一样,文本出现意外的匹配。</p> <p>下图向我们展示了对每个元素和仍然存在的DOM状态进行数据的修补:</p> <p><img src="/sites/default/files/blogs/2018/1804/v-for_6.png" alt="" /></p> <p>为了避免这种情况,我们不得不为<code>tweet-component</code>伟德1946手机版渲染出来的每个列表项绑定一个<code>key</code>值。我们将使用<code>tweet</code>的<code>id</code>作为唯一标识符,更安全地说,<code>tweet</code>的<code>id</code>不应该等于另一个<code>tweet</code>的<code>id</code>。因为我们使用的是动态值,所以我们将使用<code>v-bind</code>指令将我们的<code>key</code>绑定到<code>tweet.id</code>:</p> <pre><code>&lt;div id="app" class="columns"&gt; &lt;div class="column"&gt; &lt;button class="is-primary button" @click="shuffle"&gt;Shuffle!&lt;/button&gt; &lt;tweet-component v-for="tweet in tweets" :tweet="tweet" :key="tweet.id" /&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <p>现在,Vue识别每个<code>tweet</code>的节点的标识。因此,当我们打算对列表进行调整时,将重新对伟德1946手机版进行排序。</p> <p><img src="/sites/default/files/blogs/2018/1804/v_for_random_2.gif" alt="" /></p> <p>由于<code>tweet-component</code>伟德1946手机版中的每个列表项都被相应的移动,因此我们可以使用Vue的<code>transition-group</code>功能把元素重新排序的效果做得更好一些。</p> <p>为此,我们将把<a href="http://xxysy.com/quot;//vuejs.org/v2/guide/transitions.html#List-Transitions"><code>transition-group</code></a>元素添加到<code>v-for</code>列表中。我们将指定<code>tweets</code>的过渡名称,并声明<code>transition-group</code>应该做为<code>div</code>元素渲染。</p>" <pre><code>&lt;div id="app" class="columns"&gt; &lt;div class="column"&gt; &lt;button class="is-primary button" @click="shuffle"&gt;Shuffle!&lt;/button&gt; &lt;transition-group name="tweets" tag="div"&gt; &lt;tweet-component v-for="tweet in tweets" :tweet="tweet" :key="tweet.id" /&gt; &lt;/transition-group&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <p>基于过渡的名称,Vue将自动识别是否已指定了CSS的<code>transition</code>或<code>animation</code>。因为我们的目标是调用列表中项目移动时,Vue将会指定CSS的过渡名称<code>tweets-move</code>(给<code>transition-group</code>的名称<code>tweets</code>)。因此,我们将手动引入一个具有指定类型和过渡时间的<code>.tweets-move</code>的类:</p> <pre><code>#app .tweets-move { transition: transform 1s; } </code></pre> <blockquote> <p>这只是一个简单的效果。如果要应用更复杂的列表过渡效果,就需要阅读<a href="http://xxysy.com/quot;//vuejs.org/v2/guide/transitions.html">Vue的文档</a>,这样可以了解关于Vue中所有不同类型过渡的详细信息。</p>" </blockquote> <p>当调用<code>shuffle</code>时,<code>tweet-component</code>元素将在位置移动时有一个过渡的效果。试一试!在<code>input</code>中输入一些信息,然后多单击几次"Shuffle!"按钮几次。</p> <div style="margin-bottom: 20px;"><iframe id="xWeQjp" src="//codepen.io/airen/embed/xWeQjp?height=400&amp;theme-id=0&amp;slug-hash=xWeQjp&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>key</code>属性,就不能使用<code>transition-group</code>元素来创建列表过渡效果,因为元素是修补的而不是被重新排序的。</p> <p>是否应该始终使用<code>key</code>属性呢?并非如此,<a href="http://xxysy.com/quot;//vuejs.org/v2/guide/list.html#key">Vue文档</a>推荐只有满足下面的情况之下才使用<code>key</code>属性:</p>" <ul> <li>出于性能考虑,我们有意地希望使用默认的修补元素的方式</li> <li>DOM内容非常简单</li> </ul> <h2>总结</h2> <p>希望文章描述了<code>v-for</code>指令是如何有用的,并提供了更多的上下文来解释为什么在<code>v-for</code>指令中要使用<code>key</code>属性。如果你有任何想法,或者上文中有任何不对之处,欢迎在下面的评论中指出来。</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/list-rendering-and-vues-v-for-directive.html">https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com/vue/list-rendering-and-vues-v-for-directive.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/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/vue"" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Vue</a></div></div></div> Thu, 12 Apr 2018 13:40:15 +0000 Airen 2382 at https://www.伟德19463331|伟德1946手机版|伟德1946网页版【官方首页】.com