- 总结JavaScript的检测方式
我们都知道,JavaScript是有5种原始类型的: number string boolean undefined null 检测原始类型 检测原始类型的最佳选择是使用[代码]typeof[代码] [代码]typeof 'abc' // string typeof 123 // number typeof true // boolean typeof undefined // undefined [代码] [代码]typeof[代码]有一个好处就是:未声明的变量也不会报错 [代码]typeof someVariable //此时someVariable是未定义,返回undefined [代码] 检测复合类型 复合类型内置有(不只以下几种,只是举例说明): Object Array Date Error 当我们使用[代码]typeof[代码]检测的时候,就会看到都是返回object [代码]typeof {} //object typeof [] //object typeof new Date() //object typeof new Error() //object [代码] 此时的最佳选择是使用[代码]instanceof[代码] [代码]var today = new Date() today instanceof Date // true [代码] 到这里好像全部的检测类型都搞定了。 但是,检测类型并不能这么简单地分为原始类型和复合类型,因为复合类型会涉及到构造函数的问题。 检测函数 当我们的页面内嵌了其他的frame时,问题就来了。因为不同的frame的构造函数是独立的,即会发生以下问题: [代码]// 在frame A定义的函数test function test(){} // 在frame B检测 test instanceof Function //false // 而使用typeof则可以正确返回 typeof test // function [代码] 故检测函数的时候,最佳选择是使用[代码]typeof[代码] 检测数组 数组的问题和函数是一样的,因为不同的构造函数。而此时[代码]typeof[代码]也不灵了,因为只返回object。 Douglas Crockford则提供了一种叫duck typing(鸭式辩型)的方式: [代码]function isArray(value){ return typeof value.sort === 'function'; } [代码] 其实,这种方式是默认的认为只有数组才有sort方法。其实传入任何有sort方法的对象也是返回true的。因此这个方法并不完美。 最终的解决方案也是ECMAScript 5的实现方案,就是来自Kangax大神的方法: [代码]function isArray(value){ return Object.prototype.toString.call(value) === '[object Array]' ; } [代码] 这个方法能完美地辨别是否为数组。 在ECMAScript 5则可以使用Array的内置方法: [代码]Array.isArray([]) // true [代码] 检测属性 我发现,在检测一个对象的属性是否存在的时候,常常是这样的: [代码]if(object.someProps){ //一些逻辑 } [代码] 或者是这样的: [代码]if(object.someProps != null){ //一些逻辑 } [代码] 或者是这样的: [代码]if(object.someProps != undefined){ //一些逻辑 } [代码] 其实以上都是有问题的!因为以上方式都忽略了object可能存在假值的情况(即是属性存在,但是等于null或者undefined或者0或者false或者空字符串等等)。因此最佳的方式是使用[代码]in[代码]运算符: [代码]if(someProps in object){ //一些逻辑 } [代码] 以上检测数据类型的所有方式。 参考: 《Maintainable JavaScript》
2020-03-11 - 正确使用JavaScript数组
首先,我们可以简单地认为缩进就是代码复杂性的指标(尽管很粗略)。因为缩进越多代表我们的嵌套越多,因此代码就越复杂。今天就拿数组来做具体的例子,来展示以下如何抛弃循环,减少缩进,正确地使用JavaScript数组。 “…a loop is an imperative control structure that’s hard to reuse and difficult to plug in to other operations. In addition, it implies code that’s constantly changing or mutating in response to new iterations.” -Luis Atencio 循环 我们都知道,循环结构就是会无形地提高代码的复杂性。那我们现在看看在JavaScript上的循环是如何工作的。 在JavaScript上至少有四五种循环的方式,其中最基础的就是[代码]while[代码]循环了。讲例子前,先设定一个函数和数组: [代码]// oodlify :: String -> String function oodlify(s) { return s.replace(/[aeiou]/g, 'oodle'); } const input = [ 'John', 'Paul', 'George', 'Ringo', ]; [代码] 那么,如果我们现在要使用[代码]oodlify[代码]函数操作一下数组里每个元素的话,如果我们使用[代码]while[代码]循环的话,是这样子的: [代码]let i = 0; const len = input.length; let output = []; while (i < len) { let item = input[i]; let newItem = oodlify(item); output.push(newItem); i = i + 1; } [代码] 这里就有许多无谓的,但是又不得不做的工作。比如用[代码]i[代码]这个计数器来记住当前循环的位置,而且需要把[代码]i[代码]初始化成0,每次循环还要加一;比如要拿[代码]i[代码]和数组的长度[代码]len[代码]对比,这样才知道循环到什么时候停止。 这时为了让清晰一点,我们可以使用JavaScript为我们提供的[代码]for[代码]循环: [代码]const len = input.length; let output = []; for (let i = 0; i < len; i = i + 1) { let item = input[i]; let newItem = oodlify(item); output.push(newItem); } [代码] [代码]for[代码]循环的好处就是把与业务代码无关的计数逻辑放在了括号里面了。 对比起[代码]while[代码]循环虽有一定改进,但是也会发生类似忘记给计数器[代码]i[代码]加一而导致死循环的情况。 现在回想一下我们的最初目的:就只是给数组的每一个元素执行一下[代码]oodlify[代码]函数而已。其实我们真的不想关什么计数器。 因此,[代码]ES2015[代码]就为我们提供了一个全新的可以让我们忽略计数器的循环结构- [代码]for...of[代码]循环 : [代码]let output = []; for (let item of input) { let newItem = oodlify(item); output.push(newItem); } [代码] 这个方式是不是简单多了!我们可以注意到,计数器和对比语句都没了。 如果我们这就满足的话,我们的目标也算完成了,代码的确是简洁了不少。 但是其实,我们可以对JavaScript的数组再深入挖掘一下,更上一层楼。 Mapping [代码]for...of[代码]循环的确比[代码]for[代码]循环简洁不少,但是我们仍然写了一些不必要的初始化代码,比如[代码]output[代码]数组,以及把每个操作过后的值push进去。 其实我们有办法写得更简单明了一点的。不过,现在我们来放大一下这个问题先: 如果我们有两个数组需要使用[代码]oodlify[代码]函数操作的话呢? [代码]const fellowship = [ 'frodo', 'sam', 'gandalf', 'aragorn', 'boromir', 'legolas', 'gimli', ]; const band = [ 'John', 'Paul', 'George', 'Ringo', ]; [代码] 很明显,我们就要这样循环两个数组: [代码]let bandoodle = []; for (let item of band) { let newItem = oodlify(item); bandoodle.push(newItem); } let floodleship = []; for (let item of fellowship) { let newItem = oodlify(item); floodleship.push(newItem); } [代码] 这的确可以完成我们的目标,但是这样写得有点累赘。我们可以重构一下以减少重复的代码。因此我们可以创建一个函数: [代码]function oodlifyArray(input) { let output = []; for (let item of input) { let newItem = oodlify(item); output.push(newItem); } return output; } let bandoodle = oodlifyArray(band); let floodleship = oodlifyArray(fellowship); [代码] 这样是不是好看多了。但是问题来了,如果我们要使用其他函数来操作这个数组的话呢? [代码]function izzlify(s) { return s.replace(/[aeiou]+/g, 'izzle'); } [代码] 这时,我们前面创建的[代码]oodlifyArray[代码]函数帮不了我们了。不过如果我们这时创建[代码]izzlifyArray[代码]函数的话,代码不就又有许多重复的部分了吗? [代码]function oodlifyArray(input) { let output = []; for (let item of input) { let newItem = oodlify(item); output.push(newItem); } return output; } function izzlifyArray(input) { let output = []; for (let item of input) { let newItem = izzlify(item); output.push(newItem); } return output; } [代码] 这两个函数是不是及其相似呢。 如果此时我们将其抽象成一个模式的话呢:我们希望传入一个数组和一个函数,然后映射每个数组元素,最后输出一个数组。这个模式就称为[代码]mapping[代码]: [代码]function map(f, a) { let output = []; for (let item of a) { output.push(f(item)); } return output; } [代码] 其实我们并不需要自己手动写[代码]mapping[代码]函数,因为JavaScript提供了内置的[代码]map[代码]函数给我们使用,此时我们的代码是这样的: [代码]let bandoodle = band.map(oodlify); let floodleship = fellowship.map(oodlify); let bandizzle = band.map(izzlify); let fellowshizzle = fellowship.map(izzlify); [代码] Reducing 此时[代码]map[代码]是很方便了,但是并不能覆盖我们所有的循环需要。 如果此时我们需要累计数组中的所有数组呢。我们假设有一个这样的数组: [代码]const heroes = [ {name: 'Hulk', strength: 90000}, {name: 'Spider-Man', strength: 25000}, {name: 'Hawk Eye', strength: 136}, {name: 'Thor', strength: 100000}, {name: 'Black Widow', strength: 136}, {name: 'Vision', strength: 5000}, {name: 'Scarlet Witch', strength: 60}, {name: 'Mystique', strength: 120}, {name: 'Namora', strength: 75000}, ]; [代码] 如果我们要找到[代码]strength[代码]最大的那个的元素的话,使用[代码]for...of[代码]循环是这样的: [代码]let strongest = {strength: 0}; for (hero of heroes) { if (hero.strength > strongest.strength) { strongest = hero; } } [代码] 如果此时我们想累计一下所有的[代码]strength[代码]的话,循环里面就是这样的了: [代码]let combinedStrength = 0; for (hero of heroes) { combinedStrength += hero.strength; } [代码] 这两个例子我们都需要初始化一个变量来配合我们的操作。合并两个例子的话就是这样的: [代码]function greaterStrength(champion, contender) { return (contender.strength > champion.strength) ? contender : champion; } function addStrength(tally, hero) { return tally + hero.strength; } // 例子 1 const initialStrongest = {strength: 0}; let working = initialStrongest; for (hero of heroes) { working = greaterStrength(working, hero); } const strongest = working; // 例子 2 const initialCombinedStrength = 0; working = initialCombinedStrength; for (hero of heroes) { working = addStrength(working, hero); } const combinedStrength = working; [代码] 此时我们可以抽象成这样一个函数: [代码]function reduce(f, initialVal, a) { let working = initialVal; for (item of a) { working = f(working, item); } return working; } [代码] 其实这个方法JavaScript也提供了内置函数,就是[代码]reduce[代码]函数。这时代码是这样的: [代码]const strongestHero = heroes.reduce(greaterStrength, {strength: 0}); const combinedStrength = heroes.reduce(addStrength, 0); [代码] Filtering 前面的[代码]map[代码]函数是将数组的全部元素执行同个操作之后输出一个同样大小的数组; [代码]reduce[代码]则是将数组的全部值执行操作之后,最终输出一个值。 如果此时我们只是需要提取几个元素到一个数组内呢?为了更好得解释,我们来扩充一下之前的例子: [代码]const heroes = [ {name: 'Hulk', strength: 90000, sex: 'm'}, {name: 'Spider-Man', strength: 25000, sex: 'm'}, {name: 'Hawk Eye', strength: 136, sex: 'm'}, {name: 'Thor', strength: 100000, sex: 'm'}, {name: 'Black Widow', strength: 136, sex: 'f'}, {name: 'Vision', strength: 5000, sex: 'm'}, {name: 'Scarlet Witch', strength: 60, sex: 'f'}, {name: 'Mystique', strength: 120, sex: 'f'}, {name: 'Namora', strength: 75000, sex: 'f'}, ]; [代码] 现在假设我们要做的两件事: 找到[代码]sex = f[代码]的元素 找到[代码]strength > 500[代码]的元素 如果使用[代码]for...of[代码]循环的话,是这样的: [代码]let femaleHeroes = []; for (let hero of heroes) { if (hero.sex === 'f') { femaleHeroes.push(hero); } } let superhumans = []; for (let hero of heroes) { if (hero.strength >= 500) { superhumans.push(hero); } } [代码] 由于有重复的地方,那么我们就把不同的地方抽取出来: [代码]function isFemaleHero(hero) { return (hero.sex === 'f'); } function isSuperhuman(hero) { return (hero.strength >= 500); } let femaleHeroes = []; for (let hero of heroes) { if (isFemaleHero(hero)) { femaleHeroes.push(hero); } } let superhumans = []; for (let hero of heroes) { if (isSuperhuman(hero)) { superhumans.push(hero); } } [代码] 此时就可以抽象成JavaScript内置的[代码]filter[代码]函数: [代码]function filter(predicate, arr) { let working = []; for (let item of arr) { if (predicate(item)) { working = working.concat(item); } } } const femaleHeroes = filter(isFemaleHero, heroes); const superhumans = filter(isSuperhuman, heroes); [代码] Finding [代码]filter[代码]搞定了,那么如果我们只要找到一个元素呢。 的确,我们同样可以使用[代码]filter[代码]函数完成这个目标,比如: [代码]function isBlackWidow(hero) { return (hero.name === 'Black Widow'); } const blackWidow = heroes.filter(isBlackWidow)[0]; [代码] 当然我们也同样会发现,这样的效率并不高。因为[代码]filter[代码]函数会过滤所有的元素,尽管在前面已经找到了应该要找到的元素。因此我们可以写一个这样的查找函数: [代码]function find(predicate, arr) { for (let item of arr) { if (predicate(item)) { return item; } } } const blackWidow = find(isBlackWidow, heroes); [代码] 正如大家所预期那样,JavaScript也同样提供了内置方法[代码]find[代码]给我们,因此我们最终的代码是这样的: [代码]const blackWidow = heroes.find(isBlackWidow); [代码] 总结 这些JavaScript内置的数组函数就是很好的例子,让我们学会了如何去抽象提取共同部分,以创造一个可以复用的函数。 现在我们可以用内置函数完成几乎所有的数组操作。分析一下,我们可以看出每个函数都有以下特点: 摒弃了循环的控制结构,使代码更容易阅读。 通过使用适当的方法名称描述我们正在使用的方法。 减少了处理整个数组的问题,只需要关注我们的业务代码。 在每种情况下,JavaScript的内置函数都已经将问题分解为使用小的纯函数的解决方案。通过学习这几种内置函数能让我们消除几乎所有的循环结构,这是因为我们写的几乎所有循环都是在处理数组或者构建数组或者两者都有。因此使用内置函数不仅让我们在消除循环的同时,也为我们的代码增加了不少地可维护性。 本文翻译自:JavaScript Without Loops
2020-03-11 - 小程序同层渲染原理剖析
众所周知,小程序当中有一类特殊的内置组件——原生组件,这类组件有别于 WebView 渲染的内置组件,他们是交由原生客户端渲染的。原生组件作为 Webview 的补充,为小程序带来了更丰富的特性和更高的性能,但同时由于脱离 Webview 渲染也给开发者带来了不小的困扰。在小程序引入「同层渲染」之前,原生组件的层级总是最高,不受 [代码]z-index[代码] 属性的控制,无法与 [代码]view[代码]、[代码]image[代码] 等内置组件相互覆盖, [代码]cover-view[代码] 和 [代码]cover-image[代码] 组件的出现一定程度上缓解了覆盖的问题,同时为了让原生组件能被嵌套在 [代码]swiper[代码]、[代码]scroll-view[代码] 等容器内,小程序在过去也推出了一些临时的解决方案。但随着小程序生态的发展,开发者对原生组件的使用场景不断扩大,原生组件的这些问题也日趋显现,为了彻底解决原生组件带来的种种限制,我们对小程序原生组件进行了一次重构,引入了「同层渲染」。 相信已经有不少开发者已经在日常的小程序开发中使用了「同层渲染」的原生组件,那么究竟什么是「同层渲染」?它背后的实现原理是怎样的?它是解决原生组件限制的银弹吗?本文将会为你一一解答这些问题。 什么是「同层渲染」? 首先我们先来了解一下小程序原生组件的渲染原理。我们知道,小程序的内容大多是渲染在 WebView 上的,如果把 WebView 看成单独的一层,那么由系统自带的这些原生组件则位于另一个更高的层级。两个层级是完全独立的,因此无法简单地通过使用 [代码]z-index[代码] 控制原生组件和非原生组件之间的相对层级。正如下图所示,非原生组件位于 WebView 层,而原生组件及 [代码]cover-view[代码] 与 [代码]cover-image[代码] 则位于另一个较高的层级: [图片] 那么「同层渲染」顾名思义则是指通过一定的技术手段把原生组件直接渲染到 WebView 层级上,此时「原生组件层」已经不存在,原生组件此时已被直接挂载到 WebView 节点上。你几乎可以像使用非原生组件一样去使用「同层渲染」的原生组件,比如使用 [代码]view[代码]、[代码]image[代码] 覆盖原生组件、使用 [代码]z-index[代码] 指定原生组件的层级、把原生组件放置在 [代码]scroll-view[代码]、[代码]swiper[代码]、[代码]movable-view[代码] 等容器内,通过 [代码]WXSS[代码] 设置原生组件的样式等等。启用「同层渲染」之后的界面层级如下图所示: [图片] 「同层渲染」原理 你一定也想知道「同层渲染」背后究竟采用了什么技术。只有真正理解了「同层渲染」背后的机制,才能更高效地使用好这项能力。实际上,小程序的同层渲染在 iOS 和 Android 平台下的实现不同,因此下面分成两部分来分别介绍两个平台的实现方案。 iOS 端 小程序在 iOS 端使用 WKWebView 进行渲染的,WKWebView 在内部采用的是分层的方式进行渲染,它会将 WebKit 内核生成的 Compositing Layer(合成层)渲染成 iOS 上的一个 WKCompositingView,这是一个客户端原生的 View,不过可惜的是,内核一般会将多个 DOM 节点渲染到一个 Compositing Layer 上,因此合成层与 DOM 节点之间不存在一对一的映射关系。不过我们发现,当把一个 DOM 节点的 CSS 属性设置为 [代码]overflow: scroll[代码] (低版本需同时设置 [代码]-webkit-overflow-scrolling: touch[代码])之后,WKWebView 会为其生成一个 [代码]WKChildScrollView[代码],与 DOM 节点存在映射关系,这是一个原生的 [代码]UIScrollView[代码] 的子类,也就是说 WebView 里的滚动实际上是由真正的原生滚动组件来承载的。WKWebView 这么做是为了可以让 iOS 上的 WebView 滚动有更流畅的体验。虽说 [代码]WKChildScrollView[代码] 也是原生组件,但 WebKit 内核已经处理了它与其他 DOM 节点之间的层级关系,因此你可以直接使用 WXSS 控制层级而不必担心遮挡的问题。 小程序 iOS 端的「同层渲染」也正是基于 [代码]WKChildScrollView[代码] 实现的,原生组件在 attached 之后会直接挂载到预先创建好的 [代码]WKChildScrollView[代码] 容器下,大致的流程如下: 创建一个 DOM 节点并设置其 CSS 属性为 [代码]overflow: scroll[代码] 且 [代码]-webkit-overflow-scrolling: touch[代码]; 通知客户端查找到该 DOM 节点对应的原生 [代码]WKChildScrollView[代码] 组件; 将原生组件挂载到该 [代码]WKChildScrollView[代码] 节点上作为其子 View。 [图片] 通过上述流程,小程序的原生组件就被插入到 [代码]WKChildScrollView[代码] 了,也即是在 [代码]步骤1[代码] 创建的那个 DOM 节点对应的原生 ScrollView 的子节点。此时,修改这个 DOM 节点的样式属性同样也会应用到原生组件上。因此,「同层渲染」的原生组件与普通的内置组件表现并无二致。 Android 端 小程序在 Android 端采用 chromium 作为 WebView 渲染层,与 iOS 不同的是,Android 端的 WebView 是单独进行渲染而不会在客户端生成类似 iOS 那样的 Compositing View (合成层),经渲染后的 WebView 是一个完整的视图,因此需要采用其他的方案来实现「同层渲染」。经过我们的调研发现,chromium 支持 WebPlugin 机制,WebPlugin 是浏览器内核的一个插件机制,主要用来解析和描述embed 标签。Android 端的同层渲染就是基于 [代码]embed[代码] 标签结合 chromium 内核扩展来实现的。 [图片] Android 端「同层渲染」的大致流程如下: WebView 侧创建一个 [代码]embed[代码] DOM 节点并指定组件类型; chromium 内核会创建一个 [代码]WebPlugin[代码] 实例,并生成一个 [代码]RenderLayer[代码]; Android 客户端初始化一个对应的原生组件; Android 客户端将原生组件的画面绘制到步骤2创建的 [代码]RenderLayer[代码] 所绑定的 [代码]SurfaceTexture[代码] 上; 通知 chromium 内核渲染该 [代码]RenderLayer[代码]; chromium 渲染该 [代码]embed[代码] 节点并上屏。 [图片] 这样就实现了把一个原生组件渲染到 WebView 上,这个流程相当于给 WebView 添加了一个外置的插件,如果你有留意 Chrome 浏览器上的 pdf 预览,会发现实际上它也是基于 [代码]<embed />[代码] 标签实现的。 这种方式可以用于 map、video、canvas、camera 等原生组件的渲染,对于 input 和 textarea,采用的方案是直接对 chromium 的组件进行扩展,来支持一些 WebView 本身不具备的能力。 对比 iOS 端的实现,Android 端的「同层渲染」真正将原生组件视图加到了 WebView 的渲染流程中且 embed 节点是真正的 DOM 节点,理论上可以将任意 WXSS 属性作用在该节点上。Android 端相对来说是更加彻底的「同层渲染」,但相应的重构成本也会更高一些。 「同层渲染」 Tips 通过上文我们已经了解了「同层渲染」在 iOS 和 Android 端的实现原理。Android 端的「同层渲染」是基于 chromium 内核开发的扩展,可以看成是 webview 的一项能力,而 iOS 端则需要在使用过程中稍加注意。以下列出了若干注意事项,可以帮助你避免踩坑: Tips 1. 不是所有情况均会启用「同层渲染」 需要注意的是,原生组件的「同层渲染」能力可能会在特定情况下失效,一方面你需要在开发时稍加注意,另一方面同层渲染失败会触发 [代码]bindrendererror[代码] 事件,可在必要时根据该回调做好 UI 的 fallback。根据我们的统计,目前同层失败率很低,也不需要太过于担心。 对 Android 端来说,如果用户的设备没有微信自研的 [代码]chromium[代码] 内核,则会无法切换至「同层渲染」,此时会在组件初始化阶段触发 [代码]bindrendererror[代码]。而 iOS 端的情况会稍复杂一些:如果在基础库创建同层节点时,节点发生了 WXSS 变化从而引起 WebKit 内核重排,此时可能会出现同层失败的现象。解决方法:应尽量避免在原生组件上频繁修改节点的 WXSS 属性,尤其要尽量避免修改节点的 [代码]position[代码] 属性。如需对原生组件进行变换,强烈推荐使用 [代码]transform[代码] 而非修改节点的 [代码]position[代码] 属性。 Tips 2. iOS 「同层渲染」与 WebView 渲染稍有区别 上文我们已经了解了 iOS 端同层渲染的原理,实际上,WebKit 内核并不感知原生组件的存在,因此并非所有的 WXSS 属性都可以在原生组件上生效。一般来说,定位 (position / margin / padding) 、尺寸 (width / height) 、transform (scale / rotate / translate) 以及层级 (z-index) 相关的属性均可生效,在原生组件外部的属性 (如 shadow、border) 一般也会生效。但如需对组件做裁剪则可能会失败,例如:[代码]border-radius[代码] 属性应用在父节点不会产生圆角效果。 Tips 3. 「同层渲染」的事件机制 启用了「同层渲染」之后的原生组件相比于之前的区别是原生组件上的事件也会冒泡,意味着,一个原生组件或原生组件的子节点上的事件也会冒泡到其父节点上并触发父节点的事件监听,通常可以使用 [代码]catch[代码] 来阻止原生组件的事件冒泡。 Tips 4. 只有子节点才会进入全屏 有别于非同层渲染的原生组件,像 [代码]video[代码] 和 [代码]live-player[代码] 这类组件进入全屏时,只有其子节点会被显示。 [图片] 总结 阅读本文之后,相信你已经对小程序原生组件的「同层渲染」有了更深入的理解。同层渲染不仅解决了原生组件的层级问题,同时也让原生组件有了更丰富的展示和交互的能力。下表列出的原生组件都已经支持了「同层渲染」,其他组件( textarea、camera、webgl 及 input)也会在近期逐步上线。现在你就可以试试用「同层渲染」来优化你的小程序了。 支持同层渲染的原生组件 最低版本 video v2.4.0 map v2.7.0 canvas 2d(新接口) v2.9.0 live-player v2.9.1 live-pusher v2.9.1
2019-11-21 - 写一个能自定义尺寸、样式的switch
小程序原生的switch不能灵活的修改宽高、样式,很不方便,我这边参考WeUI的开关,写了一个可以自定义尺寸样式的switch组件。 直接上代码:https://gitee.com/piscdong/wechat-switch 效果如下图,可以自定义宽高,可以做成方角的 [图片] 代码分析 这个switch主要的难点就是点击后背景颜色变换的动画,这里用到了css的transition、transform两个属性来实现动画,以及::before和::after两个伪元素。 wxml基本结构为: [代码]<view class="switch"> <view></view> </view> [代码] 父级view是整个switch容器,会用到::before做背景色切换动画,::after做禁用时的灰色遮罩。内部的一个view是来回切换的白点。未选中时默认class是switch,选中时增加一个class:switch_checked。 选中状态到未选中状态背景有一个从中间变大到全部的白色动画,所以需要给父级view设置一个颜色作为背景色。 [代码].switch { ... background: #00c000; position: relative; } [代码] 未选中时::before覆盖整个容器,选中时::before设置[代码]transform: scale(0);[代码],这样选中时白色区域就会缩放到最小,再加上transition实现动画效果。 [代码].switch::before { display: block; content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 9999rpx; background: #fff; transition: all 0.35s cubic-bezier(0.45, 1, 0.4, 1); } .switch_checked::before { transform: scale(0); } [代码] 来回移动的白点,未选中时通过[代码]left: 0;[代码]定位到左侧,选中时将left设置为100%定位到右侧,但是这样白点会完全移出容器范围,所以还需要加上[代码]transform: translateX(-100%);[代码]将白点向左再一定自身宽度的100%,同样加上transition实现动画效果。 [代码].switch view { position: absolute; top: 0; left: 0; width: 60rpx; height: 60rpx; border-radius: 50%; background: #fff; box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.4); transition: all 0.35s cubic-bezier(0.45, 1, 0.4, 1); } .switch_checked view { left: 100%; transform: translateX(-100%); } [代码] 关于“::” 最后搭车说一下“:”和“::”,“:”是伪类,“::”是伪元素。按照我的理解:伪类不会在dom中增加节点,只不过是css选择器的一种特殊效果;伪元素会增加节点,flex布局中会影响到其他元素。 为了保证兼容性,css3是允许伪元素使用单个冒号。
2019-12-24 - 小程序与小游戏获取用户信息接口调整,请开发者注意升级。
为优化用户体验,使用 wx.getUserInfo 接口直接弹出授权框的开发方式将逐步不再支持。从2018年4月30日开始,小程序与小游戏的体验版、开发版调用 wx.getUserInfo 接口,将无法弹出授权询问框,默认调用失败。正式版暂不受影响。开发者可使用以下方式获取或展示用户信息: 一、小程序: 1、使用 button 组件,并将 open-type 指定为 getUserInfo 类型,获取用户基本信息。 详情参考文档: https://developers.weixin.qq.com/miniprogram/dev/component/button.html 2、使用 open-data 展示用户基本信息。 详情参考文档: https://developers.weixin.qq.com/miniprogram/dev/component/open-data.html 二、小游戏: 1、使用用户信息按钮 UserInfoButton。 详情参考文档: https://developers.weixin.qq.com/minigame/dev/document/open-api/user-info/wx.createUserInfoButton.html 2、开放数据域下的展示用户信息。 详细参考文档: https://developers.weixin.qq.com/minigame/dev/document/open-api/data/wx.getUserInfo.html 请各位开发者注意及时调整接口。
2018-04-16