【中后台应用】从表单抽象到表单中台 相信前端开发的同学,对表单其实并不陌生,而且时至今日,表单应用的编写因为React、Vue等框架的出现,也变得更加地便捷了。在前端工作中,有着很多中后台应用-表单的开发工作量,笔者自己深陷其中,所以为了让头发别掉得太快,重新去理解了表单这个东西,从而重新去思考和设计表单的开发模式,提升效率。 理解表单 其实大家都知道表单是什么,但大多数人对它应该没有一个明确地认识,至少我之前是没有的。 基础表单 <!-- https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Your_first_HTML_form --> <form action="/my-handling-form-page" method="post"> <div> <label for="name">Name:</label> <input type="text" id="name" /> </div> <div> <label for="mail">E-mail:</label> <input type="email" id="mail" /> </div> <div> <label for="msg">Message:</label> <textarea id="msg"></textarea> </div> <div class="button"> <button type="submit">Send your message</button> </div> </form> 复制代码 这段代码完成了一个最为基础的表单,我们来分析下,它都有什么? 提交地址、提交方法 提示信息 输入框 提交按钮 然后今时今日,这样简单的表单其实并不再能满足越发复杂的应用需求了。 更丰富的表单 在有了JQ、React、Vue等等之后,网络和社区上有了更为丰富的表单组件,比如日期选择、时间选择器、图片裁剪上传等等。 //https://codepen.io/pen/?&editable=true&editors=001 const { TimePicker } = antd; function onChange(time, timeString) { console.log(time, timeString); } ReactDOM.render( <TimePicker onChange={onChange} defaultOpenValue={moment('00:00:00', 'HH:mm:ss')} />, mountNode ); 复制代码 定义表单 可不管怎么变化,提交地址和提交方法、提示信息、用户输入、提交按钮,这些都是不可或缺的,于是我尝试用简练的语言来抽象一下表单是个什么东西: 表单是一个将人机交互行为转换为数据后提交给服务器的可视化前端应用。 想要理解表单,这两个词就尤为关键: 人机交互行为 转换为数据 人机交互行为 为什么不是表单组件(输入框、上传文件、选择框等等),而是人机交互行为?因为在表单应用的开发中,会有更多地用户对数据进行输入场景,而基本的表单组件只是其中的一类行为而已,如果哪天我们的表单里面,需要用户在一个画板上画圈圈呢? 转换为数据 我们最终与服务器进行传递的东西,不过是一份数据而已,而表单很重要的一个作用,就是将人机交互的行为转换为计算机能够存储的数据,然后与接收方进行通信。 所以,表单是这样的: " alt="image" data-src="https://user-gold-cdn.xitu.io/2019/4/22/16a459a19aa533d6?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1248" data-height="150" /> 高级模式-动态表单 聪明的开发者当然不想每天都重复地写着类似的代码,动态表单也是因此而生的吧。 动态表单,说白了就是只需要通过一份配置,就能生产一个表单应用。它能够极大地提升我们的效率,组件的复用率等等。开发者从写代码到了写配置。 就算没有对表单进行明确的理解,动态表单的组件、框架或者库类,其实都已经存在着很长的一段时间了。但它却还是存在着一些问题: 有学习配置的成本 有开发和维护配置的成本 为了解决它的问题,于是笔者基于动态表单,设计了一个表单中台。 表单中台的设计 表单中台是通过对表单进行了抽象,然后单独针对网站应用上的所有表单而设计的。 它是对一个网站应用上面的所有表单,从前端开发者对表单相关的开发维护到用户提交数据到服务端,这样一个完整链路的抽象封装。 表单配置的可视化生产 " alt="image" data-src="https://user-gold-cdn.xitu.io/2019/4/22/16a459a19abdf01d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1278" data-height="156" /> 表单的配置化其实就是将表单开发的逻辑,转化成为了一种结构,在前端看来,它是个JSON或者是个对象。手动编写表单配置是可以被可视化的工具所替代的,这样,表单的开发和维护就变得更加清晰、简便了,效率也会得到提升。 一份配置对应着一个表单的时候,但我们在一个网站应用(业务)上有多种场景需要多个表单,这时候就会有多份配置,多份配置会就需要对齐进行管理,甚至需要动态化异步加载配置。我把配置相关的事情,也一并列入表单中台的设计之中,让链路更加地完整。 说到这里,有些人可能会联想到一些问卷调查的网站、应用。本质上,它们是一样的,但问卷调查应用与大家复杂地表单开发工作还是会有很大的不一样,所以当有表单需求来了的时候,你不会告诉你的业务方说,"你去建立个问卷调查就好啦”。而它与问卷调查系统不一样的地方就是一个商业系统与中台系统的区别。所谓的中台,就是用来驱动和支撑商业系统的。 而实现可视化的手段,就是通过表单来生产配置,然后渲染表单。 " alt="image" data-src="https://user-gold-cdn.xitu.io/2019/4/22/16a459a19bade4c1?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="964" data-height="205" /> 可视化生产表单配置的页面: " alt="表单" data-src="https://user-gold-cdn.xitu.io/2019/4/22/16a45a3b796d52be?imageslim" data-width="1191" data-height="399" /> 表单中台 表单中台是一个可以完全由前端驱动的产品,因为表单里面跟数据存储查询是可以相对对立的部分,不管数据跟哪个服务器进行通信,都是不需要关心的,标准应该有前端进行制定。这样,它就是一个去中心化的产品,同时也具备成为一个中台的可能。因为它是一个中台,所以它也是能够支撑和驱动各种N个中后台和业务发展的。 架构设计 " alt="image" data-src="https://user-gold-cdn.xitu.io/2019/4/22/16a459a19d712d6b?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="768" data-height="624" /> 通信层与服务器 通信层磨平了与服务器进行通信的过程,这其中包括了配置的增删改查,表单数据的读写。接口标准由前端进行了定义。 例如配置查询的接口: 参数 类型 是否必须 说明 formId Long 否 表单ID type Long 是 0表示根据userId获取用户的配置列表,1表示根据formId获取某个具体配置 核心层 通过之前对表单的抽象,就可以抽象出一些表单相关的JS类,这个些类本质上其实都是一些数据相关的操作,包括但不限于: 表单数据验证 表单数据读写 表单元素的基本属性 /** * 定义一些标准的表单属性 * - defaultValue * - key * - dataSource * - disabled * - size * - title/labelText * * 方法: * - onChange * - setValue * - verify * @param {Object} config */ initFormProps(config){ let onChange = (value) => { this.setValue(value) this.formChange(this.key,value) } let getValue = () => { return this._value } return { onChange, getValue, size: this.config.size, dataSource: this.dataSource || [], disabled: this.disabled, defaultValue: this.defaultValue, value: this._value, key: this.key, block: this.config.block === "true" || this.config.block === "true", title: this.title || this.labelText, labelText: this.labelText || this.title, _type: this.type } } 复制代码 有了这些,就可以在渲染层,进行多框架的渲染对接了。 业务层 在输出的时候,同时输出了渲染组件和配置生产组件,配置的生产组件可以不进行上线,只要对接业务接口即可;渲染组件自动拉取对应场景的表单配置进行渲染。 所以,只要一次的接入,后续的表单开发工作,就是三步: 1.(未有满足的表单组件时)开发自定义的表单组件 2. 在配置生产组件创建表单 3. 在对应场景接入渲染组件 总结 开发者的开发历程通常会有四个阶段:写代码、写配置、可视化生产、中台。特别是在中后台的应用场景中,这样路径似乎都是有效的。 本文只是说到了表单的中台,其实,这个思路是可以被复制的, 从表单页面的可视化,到表格页面的可视化,再到中后台站点的可视化,路径与架构设计都会大致相同。因此,为了解放生产力,还有很长的路要走。
Javascript AST 编译器的研究学习 Source: 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _IceInteractContainer = require('./InteractContainer'); var _IceInteractContainer2 = _interopRequireDefault(_IceInteractContainer); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } if (location.host === "xxxx.taobao.com") { lib.x = 10 } exports.default = _IceInteractContainer2.default; module.exports = exports['default']; AST: https://astexplorer.net/ 初步观察 可以得到一些明确的信息: 整个文件代表着一个Program JavaScript的代码过程被解释成一个数组,并在body属性里 还有很多未知的信息,我就暂时先忽略了。 Body 可以看到,里面有ExpressionStatement、VariableDeclaration、FunctionDeclaration、IfStatement这些实例。我们对应到源代码中就不难理解了: 'use strict'; // AST:ExpressionStatement Object.defineProperty(exports, "__esModule", { // AST:ExpressionStatement value: true }); var _IceInteractContainer = require('./InteractContainer'); // AST:VariableDeclaration var _IceInteractContainer2 = _interopRequireDefault(_IceInteractContainer); // AST:VariableDeclaration // AST:FunctionDeclaration function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } if (location.host === "xxxx.taobao.com") { // AST:IfStatement lib.x = 10 } exports.default = _IceInteractContainer2.default; // AST:ExpressionStatement module.exports = exports['default']; // AST:ExpressionStatement 看来是将JS进行了归纳,表达式语句、变量描述、函数描述、IF语句等等。因此不难想象,应该还有While语句等等吧。 ExpressionStatement Literal 'use strict'; // AST:ExpressionStatement 展开ExpressionStatement,可以看到有个 expression 属性,这条表达式主要的信息也就在这了。值得注意的是 expression 指向的是一个 Literal 实例. type:文本 value:值"use strict" rawValue 和 raw 又有啥区别?(未知) 看起来很简单,一个字符串文本执行语句。那再继续看看一个函数调用的语句将会变成什么样。 CallExpression Object.defineProperty(exports, "__esModule", { // AST:ExpressionStatement value: true }); 可以看到 CallExpression 实例有个 callee 和 arguments,代表着谁在调用和参数是谁。 callee : MemberExpression 这时候我们看到一个新的实例 MemberExpression这是一个成员表达式,其实我们经历过这些探究之后,不难想象有什么能够编译成 MemberExpression,比如:alert(hello.word)里面的hello.word就会编译成MemberExpression MemberExpression 从上图可以看到,MemberExpression实例有两个关键的属性,Object和property,粗暴的理解就是,'.'(点)前面的就是Object,后面的就是property 。 但是,hello[0] 的情况呢?这个就有点意思了,hello[0] 也会被编译成 MemberExpression的实例。这个可以自己尝试和对比下看看。 然后可以看到Object.defineProperty被拆分成了两个Identifier实例,分别放在了Object和property属性里面. Identifier 这个标识符类,关键的就是 name属性了。总结下其实可以得出什么东西会别编译成Identifier, 例如: var a = new Function; a; // AST:Identifier arguments 值得一说的是,我这里说的是参数是谁,也就意味着,他可以是任何其他的实例,可能是一个 Literal,可能还是一个CallExpression,又或者是MemberExpression, 但它应该不会是 VariableDeclaration ,IfStatement,嗯,从语法层面来看,是不可能的。 嗯。现在看来这个 ObjectExpression是没有出现和分析过的,不过按照笔者的思路,也可以尝试去看看这个实例了。~
运用CSS变量 使用方法 正确的用法 :root{ --JC_Color:"#ff0"; /*定义变量*/ --JC_Img_Url:"url(https://taobao.com/jc.png)";/*定义背景图片*/ --JC_TRS:"translateY(0)";/*定义CSS3变换*/ } .elmA{ color:var(--JC_Color);/*使用变量*/ background-image:var(--JC_Img_Url)/*使用背景图片变量*/ transform:var(--JC_TRS);/*使用CSS3变换*/ } 容易犯错的用法 下面的用法是错误的: :root{ --JC_Img_Url:"https://taobao.com/jc.png";/*定义背景图片*/ --JC_TRS:"0";/*定义CSS3变换*/ } .elmA{ background-image:url(var(--JC_Img_Url))/*使用背景图片变量*/ transform:translateY(var(--JC_TRS));/*使用CSS3变换*/ } Javascript设置CSS变量 documentElement.style.setProperty("--JC_Img_Url","url(https://taobao.com/jc.png)") Javascript获取CSS变量 documentElement.style.getPropertyValue("--JC_Img_Url") 其他 作用域 CSS变量是存在作用域的,作用域是针对DOM节点的。 .elmA{ --JC_Color:"#f11"; } /*在非 .elmA 节点下的DOM节点无法获取变量 --JC_Color 的值*/ :root{ color:var(--JC_Color); /*失效值*/ } 可覆盖 :root{ --JC_Color:"#f00"; } body .elmA{ --JC_Color:"#f11"; } /* * JS 设置 */ document.querySellector('.elmA').style.setProperty("--JC_Color","#f12") /* *会使用在当前节点定义的值 *这意味着可以设置默认值了 **/ .elmA{ color:var(--JC_Color); /*#f12*/ } 一个简单的封装 class CssVar { /** * 针对某一Dom节点的某个CSS变量值进行设置 * 包括了获取、设置和删除 * next:可以针对背景图片、transform等进行再次封装,让其使用更简单 * @param {Object} param0 配置 */ constructor({elm,name,value}){ this.name = `--${name}` this._elm = elm || document.documentElement this.set(value) } set(value){ this._elm.style.setProperty(this.name,value) } get(){ return this._elm.style.getPropertyValue(this.name).trim() } selfDestruction(){ this._elm.style.removeProperty(this.name) } } 一个简单的Demo list 插入动画。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> :root{ --Item_TranslateY: translateY(100%); --Item_Opacity: 0; } .list{ overflow:hidden; position: relative; } .item{ transition: all 1s; opacity: var(--Item_Opacity); transform: var(--Item_TranslateY) } </style> </head> <body style="height:100%;padding:0;margin:0"> <button id="inputLi">插入</button> <ul class="list" id="list"></ul> <script> class CssVar { /** * 针对某一Dom节点的某个CSS变量值进行设置 * 包括了获取、设置和删除 * next:可以针对背景图片、transform等进行再次封装,让其使用更简单 * @param {Object} param0 配置 */ constructor({elm,name,value}){ this.name = `--${name}` this._elm = elm || document.documentElement this.set(value) } set(value){ this._elm.style.setProperty(this.name,value) } get(){ return this._elm.style.getPropertyValue(this.name).trim() } selfDestruction(){ this._elm.style.removeProperty(this.name) } } </script> <script> function insertItem(){ let id = (+new Date).toString(32) let html = `<li class="item" id="${id}">我是列表</li>` window.list.innerHTML += html let item = window[id] // 触发动画 setTimeout(v=>{ new CssVar({ elm:item, name:"Item_TranslateY", value:0 }) new CssVar({ elm:item, name:"Item_Opacity", value:1 }) },50) } window.inputLi.onclick = function(){ insertItem() } </script> </body> </html>
按照日程进行了一些总结,由于时间因素和英语水平的限制,没有弄得特别好,只是大概写了下自己注意到和理解到的一些东西。 Day 1 强调了要打造更为亲近的产品,Facebook是为了更好的连接人的关系。 针对之前的隐身泄露事件,扎克还强调了隐身安全,也会有一些相应的功能提现。keep people safe。 透露了下现如今有20亿的用户 重新开放应用审核。 扎克演讲部分 第一天主要阐述了产品上的更新迭代: Fighting Fake News,讲述了Facebook通过与虚假、垃圾、恶意等不健康的信息进行对抗的过程与期待。 Data Privacy,特地强调了用户数据的安全,隐私保护等,因此有了下面的一个功能。 Clear History,一键清除用户数据历史记录的功能和不被Facebook记录数据的开关。 Meaningful Relationships,扎克希望在Facebook里面人与人的关系是有意义的(而非像微博那样一堆僵尸粉举例),提升用户的幸福指数。 Groups Tab,群组功能,将会更容易加入群组,加强泛社交关系,并会告诉你,下图是扎克在国会的直播,在groups里面,就像开party一样?。 Dating,约会功能强势推出,很多媒体也关注了,这是基于Facebook的人物关系图谱(AI+算法生成)的应用。 人们可以创建一个与他们的Facebook个人资料分开的约会资料,Facebook可根据用户的约会偏好、共同点、共同朋友推荐潜在的匹配。用户可以选择通过Groups 或者Events来发现兴趣爱好相似的人。并且,出于用户隐私考虑,约会功能不会向朋友展示,Facebook将会在今年晚些时候开始测试 Camera Video Chat、AR Camera Effects、Group Video Calling,推出视频聊天功能(支持多人),带有VR特效的视频聊天技术。 Message simplifying messenger business & bots VR/AR in the Future Oculus Go 199$ (在场每个人都有,是一款3DoF的VR一体机,还有Oculus TV、Oculus Rooms、Oculus venues,将与小米合作,详细的产品信息看这:《关于Oculus Go一体机的一切》 通过与硬件厂商小米的合作,Oculus Go搭载了能够提升性能表现和以全新方式表达应用程序的规格。 结合高通骁龙821芯片和自动动态节流功能,Oculus Go提供了更佳的能效,可以实现更流畅的帧率和整体更优的体验。 清晰的光学元件和优秀的透镜可以降低纱窗效应的影响,并且提供了比Oculus Rift更高的分辨率。Oculus Go搭载了一个全新的显示面板,538ppi,5.5英寸,2560×1440 WQHD分辨率标准的快速切换LCD屏幕,而这能够大大提升视觉清晰度。 尽管续航能力取决于具体运行的内容,但内置的锂电池应该可以支持约2小时的游戏娱乐,而流媒体和视频则是2.5小时。 作者:刘卫华 链接:https://www.zhihu.com/question/275577870/answer/381247334 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 虚拟现实空间记录,基于Oculus,上面链接有讲~ 3D photos,这个比较有意思,day2有详细介绍。 规划图: 详细介绍部分 大概列举一些认为比较有意思的点,有些是展开扎克之前所说的东西: Marketing,重新开放应用审核,更强大的开放应用市场,将服务更多的开发者,并对开发者提供更多的支持。 灾害相应与无偿献血,帮助大家更好的知道灾情,和更好地去救助他人。同样用到了关系图谱的技术,去发动群众。 简单、可靠、安全和私密的免费网络连接。 Day2 技术相关的一天,整体来看,Facebook的AI和算法运营可谓是无所不在了,而且也有些比较惊艳的产出或者想法,主要分了三大块技术,AI、AR/VR、网络连接。 AI Pythorch https://pytorch.org/about/ ,基于Python的AI 深度学习框架1.0版本发布,后续将开源。 该框架作为AI技术底层,支撑着几乎所有Facebook 与AI有关的业务或者技术。 DensePose,人姿识别(我给的名字),不仅仅能够对图片里的人物姿态进行识别,还能对视频进行识别。 附上一篇相关文章:这个 AI 不仅能看懂你摆的 Pose,还能完美重现出来 Vision ,视觉场景的数据运用 Facebook 数据量情况: 通过海量的数据进行训练之后的AI图片识别能力前后对比, 其他 language peasoning elf、深度学习(打星际、下围棋) VR/AR VR技术所包括的一些技术分支,但今天主要讲的是下图的四个。 光学&展示(现实感的场景还原) 音频技术 系统设计 交互设计 AI 计算机视觉 用户体验 虚拟人像 今天VR的能力与人本身的能力的对比。 场景还原后,镜子里的成像也跟现实世界是一样的! Facebook 在VR场景中的虚拟人手和虚拟人像。 最困难的地方在于卡通人物的真实感,也就是人们在说话的时候,嘴型的模拟,但Facebook克服了这个困难。 最终将成像: 图片技术、AR相关 图片内容识别,通过机器学习、训练,图片的内容含义也能被识别出来。 conecting网络连接 亮点:通过AI进行网络拓扑设计。 具体项目的详细介绍(跟第一天一样) AI图片识别技术、自然语言处理技术,对视频内容的理解、对图片内容的理解和对语言文本内容的理解。 AI与道德的讨论,在某些场景下面将会用卡人人物(基于真实照片)来代替真人照片。 3D图像重建技术,场景建模(VR技术中,计算机视觉的部分)。 先对单张图片进行3D图像重建,(这也是Day1中,扎克提到的3D photos产品的技术核心),然后对改场景中其他角度、位置的图片进行3D图像重建,再通过AI算法识别,将场景进行还原。 此技术就是支撑Oculus Gallery 的核心技术。 传统的3D图像重建,与完整的3D图像重建: End. 中文相关资料: https://www.admin5.com/article/20180502/849969.shtml https://www.zhihu.com/question/275577870 https://yivian.com/news/44704.html https://zhuanlan.zhihu.com/p/35637223 视频地址: https://www.bilibili.com/video/av22908946/?p=2 相关资料 https://developers.facebook.com/blog/post/2018/05/02/day-two-f8-developer-news-roundup https://facebook.ai/developers https://developers.facebook.com
题图、摄影:锦此 当我们在讨论CSS选择器优先级的时候,我们再讨论什么? 其实很多人都对此有点模糊,那我换个方式问: 一个CSS属性的最终值是怎么来? 回答 : CSS属性的最终值是通过层叠计算得来的。 那什么是层叠计算呢? 我通俗的理解,其实就是先计算再重叠。 层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在CSS处于核心地位,CSS的全称层叠样式表正是强调了这一点。 计算过程 计算的过程指的是用户代理(浏览器只是用户代理的一种“实例”)在渲染HTML的时候,对CSS进行层叠计算的过程(这里不讨论浏览器的渲染、重绘等触发时机)。 为了方便理解,这里只针对一个属性值(padding)进行讨论,其他的属性值都是一样的过程。 demo: <div class="taobao_com" id="taobao_com" data-show="true"> <div class="taobao"></div> <p>taobao.com</p> </div> div{ padding:1px; } .taobao_com{ padding:12px; } div .taobao{ padding:123px; } .taobao_com .taobao{ padding:1234px; } 在属性的计算之前,会对每个文档元素的每个属性上的值进行排序 (W3C文档地址): 1. Transition declarations [CSS3-TRANSITIONS] 2. Important user agent declarations 3. Important user declarations 4. Important override declarations [DOM-LEVEL-2-STYLE] 5. Important author declarations 6. Animation declarations [CSS3-ANIMATIONS] 7. Normal override declarations [DOM-LEVEL-2-STYLE] 8. Normal author declarations 9. Normal user declarations 10.Normal user agent declarations 排序靠前的会比靠后的优先级要高。 1 > 2 > 3 > 4 > 5 ... 假设我们的用户代理是Chrome浏览器,那么当我们要计算A:<div class="taobao"></div>的padding值的时候,会有这么一个排序过程(简化): 1. 浏览器中对该标签的``padding``默认值 2. 开发者对该标签的 ``padding`` 进行声明的值 3. 浏览器中对该标签的 ``padding`` 进行 **!important** 声明的值 4. 开发者对该标签的 ``padding`` 进行 **!important** 声明的值 5. 开发者在CSS动画中对 ``padding`` 进行声明的值 那么在demo里面,分别写了4条padding的的声明,但都会被排在层叠顺序的开发者对该标签的 padding 进行声明的值之中,而且padding没有更高权重的声明了,那么就会对这个声明队列里的声明进行优先级的计算。 所以,!important 跟选择器优先级是什么关系? 优先级的计算 优先级的计算首先是 选择器权重 的优先级计算,然后是 声明先后顺序 的优先级计算。 选择器优先级的权重计算 这时候,才到了选择器优先级登场。 选择器的权重并不是网上很多人说的 选择器通过权重值 1(div等)、10(class)、100(id) 这样的方式进行的,这只是别人理解出来的东西,真正的计算原理可以翻阅W3C文档选择器权重的计算。 文档指出: A selector’s specificity is calculated for a given element as follows: 1.count the number of ID selectors in the selector (= A)2.count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= B)3.count the number of type selectors and pseudo-elements in the selector (= C)4.ignore the universal selector 文档也给了例子: Examples: * /* a=0 b=0 c=0 */ LI /* a=0 b=0 c=1 */ UL LI /* a=0 b=0 c=2 */ UL OL+LI /* a=0 b=0 c=3 */ H1 + *[REL=up] /* a=0 b=1 c=1 */ UL OL LI.red /* a=0 b=1 c=3 */ LI.red.level /* a=0 b=2 c=1 */ #x34y /* a=1 b=0 c=0 */ #s12:not(FOO) /* a=1 b=0 c=1 */ .foo :matches(.bar, #baz) /* Either a=1 b=1 c=0 or a=0 b=2 c=0, depending on the element being matched. */ 大致意思就是,把权重分为了 A,B,C 三个级别,A > B > C , A,B,C 直接各自计算。也就是会优先计算 A 的权重,如果相等会计算 B 的权重,以此类推。 所以才有了100、10、1的说法,但很不准确。 结合之前的demo,我们来计算下权重值: 1: div { /*type selectors*/ padding:1px; /*a = 0 , b = 0 , c = 1*/ } 2: .taobao_com{ /*class selectors*/ padding:12px; /*a = 0 , b = 1 , c = 0*/ } 3: div .taobao{ /*type selectors + class selectors*/ padding:123px; /*a = 0 , b = 1 , c = 1*/ } 4: .taobao_com .taobao{ /* class selectors + class selectors */ padding:1234px; /*a = 0 , b = 2 , c = 0*/ } 由于A位相等,B位差异的最大值在 4 ,得到的结果将会是 第4条声明 胜出: 1. a = 0 , **b = 0** , c = 1 2. a = 0 , **b = 1** , c = 0 3. a = 0 , **b = 1** , c = 1 4. a = 0 , **b = 2** , c = 0 声明先后顺序 当 A 、B 、C 所计算的权重都相等时(ABC三个值相等)相等时,后面声明的值将会是最终的计算值。 最终得到了CSS属性的值。 值得注意的是,MDN这个关于层叠的文档的中文版是错的。 另外,(MDN文档)写到: 同时仍应注意用@keyframes @规则定义的值会覆盖全部普通值,但会被!important的值覆盖。 W3C文档也有指出: Important user declarations Animation declarations [CSS3-ANIMATIONS] 但在Chrome浏览器(65.0.3325.162(正式版本) (64 位))测试的时候, .img { animation: doudong 1.5s linear infinite alternate; transform: rotate(100deg) !important; } transfrom 的 !important 并没有覆盖 animation 的。 https://codepen.io/jincdream/pen/xWPQZM 难道我理解错了?
用Alfred3+有道快速翻译中英文 好好学习,天天向上。 1.安装Alfred3 https://www.alfredapp.com/ 支持正版,购买激活码可以购买家庭版,支持多人,最多没说多少,5~6人应该是没问题的。 下载安装这个省略了。 使用workflow 打开Alfred3后,Mac的工具栏会多一个帽子:就代表你的Alfred已经运行了。 下载一个workflow文件:有道翻译 提取码/密码: 1XgC(如果失效可以联系 @锦此 或者网上直接下载) 下载后直接双击文件:点击 Import ,插件就安装成功了。!注意, 这个插件里面写的是楼主自己有道API的key,如果有能力的还望替换成自己的,最后会写下相关教程。 开始翻译 首先来看看如何激活 Alfred , 激活Alfred后: 然后在里面输入yd+空格键+你想要翻译的词或者句子: 键盘敲入Enter键,就会自动复制结果。 开始你愉快的翻译旅程吧!