邮件和推送通知的思考

邮件、推送?拜托,都不是!

本文是翻译,版权归原作者所有



考虑移动通知的框架

今年,平均每天收发 2120 亿封邮件,大多数都是机器生成的。在数十年里,邮件一直都是滥用最严重的渠道。尽管有了复杂的反垃圾邮件工具、和诸如 unroll.me 的服务,我们的收件箱还是充满了不想看到的邮件。

尽管推送通知没有那么糟糕,它们就一直存在着。受增长的参与度和留存率的诱惑,开发人员正在推送通知方面发力,平均每月发送 50 多条通知。

大部分通知来自于富有激情的市场人员,他们采用广撒网的营销策略,但是很多家用产品正在产生更多不必要的通知。

比如,当有人在 Twitter 上关注你时,你会得到一条推送通知和一封邮件。多一名关注者,比较有意思,但是,这需要发送推送和邮件吗?我不这样认为。当一条推送足够时,清除两条通知就显得多此一举了。

不只是 Twitter,LinkedIn、Facebook、Quora 等网站,都为大部分事件发送推送和邮件——新联系、标签、消息、收入等。当然,它们都允许你调整通知设置,但是默认通知设置得不友好、并期望用户修改,是不足取的。

也不是有意为之。鉴于邮件和推送退出率的差异,与其挑选哪些地方发送,不如在所有渠道发送通知。我还没有看到人们抱怨过这种行为,或许是因为长期暴露在邮件 spam 中,已经见怪不怪了。

但是这不代表你可以用不必要的通知轰炸用户,尤其是你能够做到分组通知、追踪订阅和参与度。

总之,存在三种通知:

  • 交易性通知:用户触发的事件,比如购买、订购、售卖东西、修改密码、进账出账等。
  • 告知性通知:非交易类的、用户触发的事件,比如社交互动(关注用户、给照片打标签、点赞)、消息等。
  • 促销通知:由服务触发的、以营销为驱动的事件,比如交易、促销、年终销售等。

关于哪种渠道(邮件或推送)最适合哪种通知类型,在我们弄清楚之前,先看看这两种渠道之间有哪些不同:

  • 邮件是富内容、持久和嘈杂的。你能够发送带有 html、图片和视频的、良好界面和丰富格式的邮件。如果你删除,它们就躺在收件箱里,等待被查收。打开率取决于邮件内容,但是通常情况下,这属于一种嘈杂的渠道。
  • 推送和上下文情景紧密相关、非持久、以及相对较少的干扰。你能够发送一条信息,带上简单的 CTA注1。当用户清除它们后,就会隐藏起来,再也看不到了。这是一种干扰较少的渠道——虽然这两种渠道都是即时传递信息,但是推送有着更多的「实时」吸引力,因为它比邮件有着更少的干扰。
  • 短信是持久的、感觉更加实时,相对干扰也少。90% 的短信在 3 分钟内被查收。但是,成本转嫁给了用户,也是所有渠道中最敏感的——垃圾信息的容忍程度不高。

最大区别在于情景感知。你可以基于用户位置、活动、附近等因素发送推送通知。邮件不具备丰富的情景。推送交互比邮件交互更加轻量级。

那么,对于这些通知,什么才是最好的渠道呢?这取决于你的产品和用户(需要 A/B 测试),但是还要考虑:

交易性通知

像订单确认、购买清单、订购信息、密码修改等「存档」种类的事件,最适合邮件了,也足够好了。用户或许想留作记录,供后来参考。

对于「状态更新」相关的交易,比如送货状态、延迟等,邮件和推送都可使用。它们属于你不想让用户错过的通知——错过一条通知的成本要高于一条多余通知的成本。

对于「具有时效性的更新」,尤其和按需服务相关的通知,即在数分钟内送达服务,此时短信最有效。短信的打开率最高。

今天,很多按需服务在所有渠道上发送通知,即使不太必要,也会发送。「仅供参考」的通知使用短信,「存档」的通知使用邮件,或许已经足够好了。短信是最敏感的渠道——容忍度也是最低的。

告知性通知

对于大多数事件,推送足够好了。用户没有回头参考这些事件的需求。同时发送两种渠道的通常争议在于,邮件相对于推送而言,是一种更加丰富的扩展——比如,对于经典的「新关注者」的案例,推送能够告诉我,现在有了一名新关注者,而邮件可以呈现他们的具体情况。但是,我觉得,用户为了浏览推送过来的具体情况而多出来一次额外点击的成本,是应该省掉的。

促销通知

促销可以根据用户的情景做出相应改变,比如位置、活动、附近等,因此,推送就足够好了。举个例子:当我在星巴克附近时,就发送一张当地星巴克的优惠券;在年终销售期间,发送某些商品的打折信息等。

由于促销不需要用户当前的情景,邮件更加适合且足够胜任。例子:每周优惠券、通常的年终交易等。我知道,电子商务营销人员将会讨厌我说破这一点,但是请看看推送选择进入零售 app 的下降趋势吧。

关于赢取信誉的更多思考:

主动退订邮件

假定某种邮件的打开率非常低,可以考虑自动帮助用户退订,并发送一封邮件,内容为「我们注意到,您不会查收这些邮件,因此我们自动为您退订了。还您一个干净的收件箱!」。你的用户将因此爱上你(我也会的),或许还会对他们的朋友说!只是不要告诉你的营销部门。

谨慎处理声音

最近,我看到很多 app 不允许关闭声音——太糟糕了。如果你默认打开了声音,记得提供关闭的方式。使用柔和的声音——声音越大,不代表越好。找到在夜晚关闭声音的方式。使用当地时区。

同步 web 和手机

如果用户在手机 app 打开了一条通知,标记成了已读,那么当他们访问网站时,他们就不应该再看到这条通知。Twitter 就存在这个问题——Twitter app 和网站没有同步通知。

同步邮件和推送

如果你不得不同时发送邮件和推送,那么找到发送它们的顺序。比如,先发送推送,如果用户在 X 分钟内阅读了,就不要再发送邮件了。

学习用户习惯

测量并理解用户处理通知的方式、兴趣点,并作出相应调整。及时发送相关通知。

精心设计过的通知,会大致了解用户关心什么、用户和产品交互的方式与位置、用户是怎样处理通知的,用合适的渠道、在合适的时间、以合适的通知取悦用户。处置得当,就能为你的参与度和留存率带来奇迹。


雇佣朋友不会破坏你的业务,除非有糟糕的沟通

本文是翻译,版权归原作者所有



雇佣朋友不会破坏你的业务,除非有糟糕的沟通

「和同事保持亲密的、私人关系,没有什么好处。绝对没有。」

几星期前的一天,我正在清理抽屉,无意中发现我的一本旧日记,在第一页摘抄了一句话。当时的日期是 2012-3-20,我能够回忆起来写过这句话——喝酒喝到深夜,歪歪扭扭写下的——但是真想不起来,我为什么觉得有必要在纸上写下这句话来提醒未来的自己。

当时,我已经在 MetaLab 供职 4 年了,公司更像是日益扩大的朋友圈,而非一家成功的设计公司。我们所有人在工作之外呆在一起的时间更长,吃饭、喝酒、看电影、或者来一次说走就走的旅行。我们甚至还建立了 Facebook 群组,在里面发布某个晚上要做什么的消息,方便其他人参与进来。

当公司男女员工都是 20 多岁一起工作时,就出现了不得不面对的事情:每个人都在成长。他们意识到,世界比他们最初设想的要大一些,他们的技能能够支持他们在职业生涯中走得更远。如果不是更好的工作机会,至少要到更好的城市、或者获取某种新的经验,或其它考虑。基本上,我们开始怀有一种可怕的可能性:或许我不会永远在这里工作

做为企业主,我们幸而有一些忠诚的员工和我们呆在一起,因为,他们年龄的渐长、堆积起来的债务、陷入爱情无法自拔、组建家庭以及打算买房。但是有一些人没有走寻常路——这些朋友偏离了轨道。他们属于工作中的朋友,你需要对他们吼叫,但是你却不能那样做;你需要开除他,但是对他寄予了厚望。

我乐于想象突然冒出来的火花:它开始变得更加清晰,是的,我们可能不会永远是朋友。或许关乎钱的争议、或许关乎被夸大的管理方面的争议,因为我们刚好是朋友。但是在将来,这个人或许会离开,而我或许会受到伤害。

做为一家初创公司,我们常常感到没有犯错的自由,我们离破产永远只差一步糟糕的举措。我们在每个节点感到被迫良莠不分,我们常常做出彻底的、武断的声明,让我们具有内心的平静,并继续前行。我们不得不火冒三丈,把这种沮丧转化为得来不易的 Startup Lesson™,使我们朝着创业涅槃挪动一步。

当我们发现自己处于上述处境时,我们会说「不要和朋友共事,不值得」,当下一代创始人来寻求建议(大约 6 个月后)时,我们就把这种认识传递给了下一代。它恰恰变成了一种普遍看法。

它的作用反而阻止我们进一步思考,或许我们需要做出一些改变,某些地方还比较重要。

和朋友共事是最棒的。不,不是这样的,我是认真的。

老实讲,当我听到人们说「不要和朋友共事」时,我感到惊奇,我更加惊奇的是,我会想到,即使和朋友共事很不错,那也不长久。

和朋友共事是世界上最美妙的过程。我曾拥有的所有独特的工作,都是和我在意的人一起完成——他们把我看做活生生的人,而非资产。毫不夸张地说,和优秀的人共事,能让工作更出彩,能让伟大的想法更加完美,能让你在早上就迫不及待地去工作。

雇佣朋友、并和朋友共事不太容易,但不是没有可能。这的确需要一种优化的沟通策略和完全的意愿。

我们最终和朋友共事常常出于完全的便利,对于开展艰苦的工作,这不是理想的方式。尽管如此,却是导致我最终退出 MetaLab 的原因,很可能也是你将来终止和某个 24 岁 VP 共事的原因,后者从未在其它地方工作过。

为什么我们要雇佣受信任的贡献者

这个故事通常比较可信:一切进入正轨,貌似我们不费多大功夫就搞定了所有事情,我们认为,或许我们具有了魔法。我们是特例,不需要什么过程——迈开步伐的部分乐趣,在于没有冗长规则的旅行。

因此,无需展开广泛的职位搜索,我们在身边寻找即可。我们遵循 David Frankel 的呼吁「可信任的贡献者」——和我们关系紧密的、且至少具备我们要求的某些技能的人。

在很多情况下,我们相当肯定,那些人不会对我们摆架子,但是和我们感到距离最近的也是他们。正如 Frankel 所指出的,「……如果你刚毕业就做了 CEO,那么,他们为什么就不能成长到 VP 要职呢?」

毕竟,有种潜在意识,是新兴业务行家的组成部分,即我们或许还不够完美——如果混进了某些人,他们真地确切知道他们在干什么,那么他们会被吓住。任何人——我是说任何人——只要他们运作过一项业务,就会感到「冒名顶替综合症(Impostor Syndrome)注1」,就会再引入一些熟悉的面孔(你觉得真正了解真实的你的那些人),从而带来了某些必不可少的草率,对你而言,脆弱的纸牌屋可能快要倒塌了。

我们信任的贡献者们,也是我们寄予厚望的人:对我们而言,这些朋友和准专家们一定具有巨大的、未开发的潜能。我们对自己说,他们就差一个机会了;如果我们能够找到点燃引线的方法,他们就能一飞冲天。在我们人生当中,第一次可以用有意义的方式帮助别人——比如,喝醉酒的时候,对貌似有创新能力的朋友说,他们是多么出色的设计师。

或许你预料到了,丰满的理想无法掩饰骨感的现实,难以圆满。

事情恶化之前,我们该怎么办

当一项业务开始启动后,出错的地方有很多。每一天都让你感到真的难以为继——漩涡使得全部混乱敲打在山上,的确如此。在某种程度上,我们被某种类型的兴奋伤害到了。比如,你决定放弃针对客户项目的普通合同,客户反而拒绝付款。这是一个容易学到的教训——下一次,一定让他们签署一份可恶的合同。

但是,「软性」管理的突然而至,打破了节奏,尤其在人们分析的阶段,甚至在我们没有招聘朋友之前,我们渴望数据、性格测试和其它启发方法,来为我们做决定。正如 Daniel Goleman 在他的佳作《如何培养一名又聪明又笨蛋的主管》中详细指出,效率低下的员工即使处于最好的环境下,也难以回头,且费时费力——但是,也不是没有可能。

问题在于,当我们看到招过来的朋友正在挣扎、或者呈现出完全不胜任时,我们该做出怎样的反应。我们只好处理某个问题,他和我们或许有着千丝万缕的关系;我们竟然莫名地变成了他们的监护人,我们开始妥协。在处理既是朋友又是员工的人时,往往不知所措:既不能改善他们的表现,也无法采取必要的手段让他们离职。

我们不能在从一个带有相反两种极端的、较宽范围审视员工并作出客观决定,而是倾向于把他们推向中间的情形,这是不幸的现实。

eShares 的 CEO Henry Ward 在《如何招人》一文详细解答了这种错觉

Henry Ward 在《如何招人》一文详细解答了这种错觉:

Ward 正在讨论通常意义上的低效员工,但是我认为,上图非常完美地解释了我要对自己说的关于朋友的讨论,他可能不再是理想的人选、或他需要受到严厉的惩戒,即:我们没有直面需要作出的艰难决定,而是对自己撒了个谎,让整个痛苦再往前推进一点点,以说服自己事情貌似没有想象中那么糟糕(或特别之处)。Ward 列出了某些谎言,我敢肯定,你以前听到过某些谎言:

  • 他装着很努力。
  • 她应该得到另一次机会。
  • 人们真的喜欢她。
  • 我觉得他表现很差。
  • 他擅长其它工作。
  • 他工作之外的事情很多。
  • 她不称职。

请思考一下:如果我们拙于惩罚正常情境下的低效员工,那么当我们需要惩罚一个朋友时,会经历怎样的情感压力呢?这个朋友可能是我们真地在意的人、我们把他的成功看成了一项个人投资。事实上,他们是我们的朋友,恰恰成为我们无视其不佳表现的另一个借口。

即使我们置身事外,我们也知道,存在着一个问题,对于既是朋友、又是同事的员工,他们的特征非常明显:他们之所以能被招过来、还留在公司,是因为有着非常特殊、非常优先的环境。

我们审视一下他们的胡言乱语吧,比如「如果我们不是朋友就太好处理了」,但是我们真的在寻找解决办法:做好复杂工作的最简单方式就是良好的、诚实的沟通。我们说雇佣朋友不太好使,但是,我们恰恰在艰难地穿过让人沮丧的、貌似不可逾越的泥潭的时刻,往往陈述了 Startup Lesson™。在这些时刻,我们不会寻求真理;我们太急于得到结果而无法观察。

雇佣朋友,会有立竿见影的效果

考虑一下,当你雇佣一个你认为是朋友的人,你会得到什么。很快,你身边有个可以坦然面对的人:做为朋友,你会对他们说,他们是否伤害了你。你愿意告诉他们你是否正在度过低迷期(如果你不愿意,或许你们没有想象中那么亲密)。

尽管这种类型的关系不一定轻松变换到工作环境中,但是它使有信任感的团队陷入极度困难的境地——这种关系的缺乏影响到了如此多的初创公司。

一些年前,Lynda Gratton 和 Tamara J. Erickson 研究了 55 个大型团队的协作行为,并在《哈佛商业评论》上公布了他们的发现。其中一项发现是他们称之为「遗产关系(heritage relationshipos)」的重要性——团队成员彼此熟悉,要么是朋友、要么曾经合作过。

值得一提的是,他们发现,如果某个团队刚开始由一群不熟悉的人组成,那么,他们被迫需要在工作中投入大量时间:比如建立信任,如果你雇佣的是一个朋友,它就无需建立。

然而,我们和朋友共事时,不是在一切进展顺利的时候就遇到了问题,而是在度过了极度诚实和信任的蜜月期之后。然后,我们需要抛出问题,并制定规范。

最重要的任务以及貌似容易出错的地方,就变成了和你的朋友维持最初的信任和紧急沟通,并将其延伸到关系的各个方面,确保过去是朋友、现在是员工的家伙能够被尽可能公平地对待。不论怎样,这都要求你除了维护诚实,这是你们成为亲密朋友的基础,还要根据情况采取一些行动。

关心个人发展,直接挑战

在 First Round Capital 的 CEO 峰会上,Kim Scott 在其精彩演讲中提出了一个沟通方面的概念,很快成为创业圈里受欢迎的词语:坦诚相待注2

如下图所示,坦诚相待是其中一个象限,包含了诚实和个人关系,这是沟通所需的两个绝对要素,它们共存于愉悦的和谐之中。

坦诚相待是其中一个象限,包含了诚实和个人关系

和朋友共事存在些微不爽,当你需要给他们建议时却做不到,而要冒着破坏朋友关系的风险,以诚实和同情为基础的沟通策略才是最佳选择。好处在于,在你们开始处事之前,你和朋友就已经体验到了坦诚相待。它是任何良好关系的基石,也是其成为强大到难以置信的管理工具的原因。

坦诚相待之类的要素对于和朋友(也包括任何员工)的工作关系相当重要,为了更好地理解,你所要做的就是观察两个极端的其中一端:破坏的同情心(ruinous empathy)。Scott 指出,它也是大多数管理问题出现的地方。和朋友共事的读者,应该对于 Scott 说的这个掌故不会感到陌生:

「有个家伙为我打工,我们叫他 Bob。我真地喜欢 Bob。可是问题来了,Bob 在工作中的表现非常糟糕,」Scott 说道。每当 Bob 对其表现流露出担忧时,Scott 就会尽量鼓励他。但是大约一年之后,她意识到,Bob 的糟糕表现正在影响整个团队——她最终很有可能失去某些优秀员工。尽量对 Bob 「好」一些,就是对优秀员工的不公平,结局也不利于 Bob。

「在 10 个月里,我从未批评过 Bob,因为我一直顾及着他的感受,而现在,我就坐在 Bob 面前,我要解雇他。非常痛苦。」Scott 说,「当我告诉他时,Bob 把椅子向后推了推,看着我说,‘为什么你不告诉我?为什么没有人告诉我?’」。

当我们为了照顾员工的感受、或顾及关系、而没有诚实对待他们时,结局对谁都不好。Scott 的员工对他运作高效组织的能力失去了信心。当然,Scott 不得不开除掉这个人,尽管她花了数月时间赞美他。可怜的 Bob 做着他不擅长的事情,他浪费了近一年的职业生涯,可能因被开除而蒙羞。

能够回想起来的一个重要现实就是,当你雇佣的朋友欠你一个感恩债时,那么你也欠他们一个。他们把他们的职业、他们的未来、他们的希望和梦想托付给你了。无论发生了什么,他们都不应该因为你的不作为而收获一个停滞不前的职业生涯。比如,「哦,我觉得你不应该雇佣朋友」,个人沉沦是一种完全不可原谅的不负责任。每个人都应具备彻头彻尾的诚实——尤其是你的朋友,如果你还当他们是朋友的话。

Scott 推荐鼓励你的所有员工坦诚相待,因此,即使你不方便给出朋友需要的反馈,团队也有很多人或许不存在不爽快的情况。如果它成为了惯例,你或许会发现,你给了朋友最需要的反馈,甚至你都没有注意到。

真正的问题:和每个人的沟通

思考 2012 年一篇文章时,我忍不住回想起所有的焦虑,它们是每个工作日的家常便饭。有整整几个星期,貌似不可能做好工作;无论我在工作中投入多少努力,都没有多少收效。有时候,有些运作奇好的地方也会出现问题。有时候,胡乱猜测成为最大的靶心。

当我们整装待发时,当我们尽量扩大规模时,我们是如此不顾一切地渴求真理。它们有时候貌似是指引我们向前的唯一指南。但是,给我们愚蠢的、误导规则的,恰恰就发生在这些真理的实践中,比如「不要雇佣朋友」。

千万千万拜托,不要管这些论调。否则,你就是在冒险,而错失开启公司增长的重要机会,Gary Vaynerchuk 在这里写到了

「当我的弟弟 AJ 和我创办 VaynerMedia 时,我们雇佣了 AJ 的八名大学同学,让公司运转起来。此后,我们雇佣了更多的人,分散在组织周围,并继续视情况雇佣新人。最初的八个人添加了一些东西,我认为相当重要:公司文化。如果你不关心公司文化,你就会输。我们知道,这八个人将为公司带来能量和关注,他们将很快建立一种有趣的、优秀的文化。」

如果你遇到了问题,要记得问题不在于只和与你共事的朋友沟通;问题在于要负责任地和每个人沟通。如果我们不能尽早稳固地沟通问题,那么随着时间的发展,我们就会真的搞砸,我们雇佣的人就搞不定我们犯下的错误。


CSS 的 22 个必备技巧

本文是翻译,版权归原作者所有



大家好!今天我们要讨论 CSS 中的一些有用的技巧。开始吧……

混合模式

CSS 混合模式

目前,Firefox 和 Safari 开始支持混合模式,就像 Photoshop 一样。Chrome 和 Opera 也支持,只是有些差异。看例子:

你可以创建不同的样式。下面是示例代码:

.blend {
    background: #fff;
}
.blend img {
    mix-blend-mode: darken; 
}

在线尝试一下 CSS 混合模式和滤镜

渐变边框

CSS 渐变边框

如今,你可以在边框里使用渐变了。非常简单,只要用较小的 z-index 设置伪元素就可以了:

.box {
  margin: 80px 30px;
  width: 200px;
  height: 200px;
  position: relative;
  background: #fff;
  float: left;
}
.box:before {
      content: '';
      z-index: -1;
      position: absolute;
      width: 220px;
      height: 220px;
      top: -10px;
      left: -10px;
      background-image: linear-gradient(90deg, yellow, gold);
}

你可以在这里找到所有例子。使用 background-clip 和 background-origin 也可以做到。在美好未来的某一天,border-image 属性也会被所有浏览器支持,实现方法如下:

.box {
    border-image: linear-gradient(to bottom, #000000 0%, #FFFFFF 100%); 
    border-image-slice: 1; /* set internal offset */
}

z-index 支持过渡

627e4e0da85e419fa53ce6c0552caec5

你可能不知道,但是 z-index 的确支持过渡了!它不会在每一步去改变值,因此你会认为,它不会产生过渡。但是,它真的支持!这里有个不错的例子

currentColor

我们可以用它检测当前颜色值,这样我们就不必多次定义它。

当和 SVG icon 一起使用时最有帮助,它随着父级元素颜色的改变而改变。通常,我们的做法如下:

.button {
  color: black;
}
.button:hover {
  color: red;
}
.button:active {
  color: green;
}

.button svg {
  fill: black;
}
.button:hover svg {
  fill: red;
}
.button:active svg {
  fill: green;
}

不过,我们可以用 currentColor 实现:

svg {  
  fill: currentColor;
}

.button {
  color: black;
  border: 1px solid currentColor;
}
.button:hover {
  color: red;
}
.button:active {
  color: green;
}

关于伪元素的代码:

a {  
  color: #000;
}
a:hover {  
  color: #333;
}
a:active {  
  color: #666;
}

a:after,  
a:hover:after,  
a:active:after {  
  background: currentColor;
  ...
}

object-fit

你还记得有时候你需要为图片设置 background-size 吗,因为它会解决很多问题。现在你可以使用 object-fit,webkit 支持它,很快也会被 Firefox 支持。

.image__contain {
  object-fit: contain; 
} 
.image__fill {
  object-fit: fill; 
}
.image__cover {
  object-fit: cover; 
}
.image__scale-down {
  object-fit: scale-down;
}

CSS object-fit

示例

单选、复选按钮的样式

我们不使用任何图片,来给某个复选按钮设置样式:

 <input type="checkbox" id="check" name="check" />
 <label for="check">Checkbox</label>
input[type=checkbox] {display: none;}

input[type=checkbox] + label:before {  
    content: "";
    border: 1px solid #000;
    font-size: 11px;    
    line-height: 10px;
    margin: 0 5px 0 0;
    height: 10px;
    width: 10px;
    text-align: center;
    vertical-align: middle;
}

input[type=checkbox]:checked + label:before {  
    content: "\2713";
}

CSS 单选、复选按钮的样式

你可以看到,伪元素和伪选择器 :checked(IE9+)表现正常。在上面的示例代码中,我们隐藏了原始的复选按钮,用我们自己的代替。当被勾选时,我们通过 content 显示一个 Unicode 字符。

CSS 和 HTML 用到的 Unicode 字符不同。在 CSS 中,开头是反斜杠,然后跟上十六进制的字符,而在 HTML 中,它是十进制的,形如 &#10003; 。

我们还可以给复选按钮加上动画:

input[type=checkbox] + label:before {  
    content: "\2713";
    color: transparent;
    transition: color ease .3s;
}
input[type=checkbox]:checked + label:before {  
    color: #000;
}

下面是单选按钮的动画:

input[type=radio] + label:before {  
    content: "\26AB";
    border: 1px solid #000;
    border-radius: 50%;
    font-size: 0;    
    transition: font-size ease .3s;
}
input[type=radio]:checked + label:before {  
    font-size: 10px;    
}

CSS 单选、复选按钮的样式-动画

你可以在这里找到完整的 Unicode 清单,试着鼓捣下代码吧。

CSS 中的 counter

不是每个人都知道 counter 可以在 CSS 中设置:

<ol class="list">  
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ol>
.list {
    counter-reset: i; //reset conunter
}
.list > li {
    counter-increment: i; //counter ID
}
.list li:after {
    content: "[" counter(i) "]"; //print the result
}

我们在 counter-reset 属性中定义了一个任意 ID 和初始值(默认为 0)。你可以在 counter-increment 中设置另一个数字,它定义了计数器的步长。

比如,counter-increment: i 2 将只显示偶数。

CSS 高级计数器

你还可以累加被用户选中的复选按钮:

<div class="languages">  
  <input id="c" type="checkbox"><label for="c">C</label>
  <input id="C++" type="checkbox"><label for="C++">C++</label>
  <input id="C#" type="checkbox"><label for="C#">C#</label>
  <input id="Java" type="checkbox"><label for="Java">Java</label>
  <input id="JavaScript" type="checkbox"><label for="JavaScript">JavaScript</label>
  <input id="PHP" type="checkbox"><label for="PHP">PHP</label>
  <input id="Python" type="checkbox"><label for="Python">Python</label>
  <input id="Ruby" type="checkbox"><label for="Ruby">Ruby</label>
</div>  
<p class="total">  
  Total selected:
</p>
.languages {
  counter-reset: characters;
}
input:checked {  
  counter-increment: characters;
}
.total:after {
  content: counter(characters);
}

我们累加 input:checked 的值,并显示出来,参看例子

CSS 高级 counter

你还能开发出一个小型计算器呢:

<div class="numbers">  
  <input id="one" type="checkbox"><label for="one">1</label>
  <input id="two" type="checkbox"><label for="two">2</label>
  <input id="three" type="checkbox"><label for="three">3</label>
  <input id="four" type="checkbox"><label for="four">4</label>
  <input id="five" type="checkbox"><label for="five">5</label>
  <input id="one-hundred" type="checkbox"><label for="one-hundred">100</label>
</div>  
<p class="sum">  
  Sum 
</p>
.numbers {
  counter-reset: sum;
}

#one:checked { counter-increment: sum 1; }
#two:checked { counter-increment: sum 2; }
#three:checked { counter-increment: sum 3; }
#four:checked { counter-increment: sum 4; }
#five:checked { counter-increment: sum 5; }
#one-hundred:checked { counter-increment: sum 100; }

.sum::after {
  content: '= ' counter(sum);
}

运行原理一样。这里有在线 demo文章

css 小型计算器

没有图片的菜单图标

你还记得需要使用「三明治」图标的频率吗?

「三明治」图标

至少有 3 种方法来绘制:

1.shadow

 .shadow-icon {
   position: relative;
   }
   .shadow-icon:after {
     content: "";
     position: absolute;
     left: 0;
     top: -50px;
     height: 100%;
     width: 100%;
     box-shadow: 0 5px 0 #000, 0 15px 0 #fff, 0 25px 0 #000, 0 35px 0 #fff, 0 45px 0 #000;
     }

2.渐变

.gradient-icon {
    background: linear-gradient(to bottom, #000 0%, #000 20%, transparent 20%, transparent 40%, #000 40%, #000 60%, transparent 60%, transparent 80%, #000 80%, #000 100%);
}

3.UTF-8

你可以只粘贴这个标准符号:☰ (Unicode: U+2630, HTML: &#9776;)。你可以调整其颜色或尺寸,因此它没有其它方法灵活。

看例子

你还可以使用带有伪元素的 SVG、图标字体或边框。

@Supports

CSS 有一些称之为 supports 的新表达式。如你所见,它可以检测浏览器是否支持所需选项。不是所有浏览器都支持它,但是你可将其用作简单的检查。

@supports (display: flex) {
    div { display: flex; }
}

/*You can check prefixes*/
@supports (display: -webkit-flex) or (display: -moz-flex) or (display: flex) {
    section {
        display: -webkit-flex;
        display: -moz-flex;
        display: flex;
        float: none;
    }
}

visibility: visible

把 visibility: visible 的区块设置为 visibility: hidden,你对此有何看法?

.hidden {
  visibility: hidden;
}
.hidden .visible {
  visibility: visible;
}

你或许认为所有元素都将被隐藏,实际上,除了子元素显示之外,父元素将隐藏所有元素。请看 demo

position: sticky

CSS position: sticky

我们已经发现了一个新特性,现在你可以创建 “sticky” 的区块了。它们和 fixed 区块表现一样,但是不会隐藏另一个区块。你最好看下这里。目前,只有 Mozilla 和 Safari 支持,但是你可以用如下方式实现:

.sticky {
  position: static;
  position: sticky;
  top: 0px;
}

我们将会在支持的浏览器里得到一个 sticky 区块,而在其它浏览器里得到一个普通区块。特别有利于移动网站,因为你需要创建一个可移动区块且不影响其它元素。

新尺寸

最近,世界上找到了一种新方式,用来描述不同物体的尺寸。比如:

  • vw(视口宽度):视口宽度,单位:1/100。
  • vh(视口高度):视口高度,单位:1/100。
  • vmin 和 vmax:二者都是相对于视口的宽度或高度,但前者总是相对于大的那个,后者总是相对于小的那个。

有意思的是,大部分现代浏览器都对它们支持很好,你可以随意使用。我们为什么需要它们呢?因为它们让所有的尺寸更简单了。你不必定义父级元素尺寸的百分比或其它东东。看个例子:

.some-text {
    font-size: 100vh;
    line-height: 100vh;
}

视口尺寸的单位

或者,你在屏幕中央放置一个美丽的弹窗:

.blackSquare {
    background: black;
    position: fixed;
    height: 50vh;
    width: 50vw;
    left: 25vw;
    top: 25vh;
}

这貌似是很酷的解决方案。请参考来自 Codepen 的例子

在使用这个特性时,存在一些劣势:

  • IE9 应该使用 vm 而不是 vmin。
  • iOS7 上的 vh,存在一些 bug。
  • vmax 还不被完全支持。

文本修饰

我们用数行代码就能改变选中文本的颜色:

*::selection {
    color: #fff;
    background: #000;
}
*::-moz-selection {    
    /*Only Firefox still needs a prefix*/
    color: #fff;
    background: #000;
}

除了定义选中文本的颜色,还能定义阴影和背景。

触摸设备上的块滚动

如果页面存在一些内部滚动的区块,那么除了添加 overflow: scroll / auto,还要添加这行代码:

-webkit-overflow-scrolling: touch;

问题在于,移动设备浏览器对于 overflow: scroll 属性支持不够好,会滚动整个页面而不是期望的区块。-webkit-overflow-scrolling 修复了这个问题。你可以将其添加到你自己的项目中,看看效果。

使用硬件加速

有时候你的动画能够减慢用户电脑。为了阻止这种情况,你可以针对特定区块使用加速:

.block {
    transform: translatez(0);
}

你可能感受不到变化,但是浏览器理解,这个元素应该被看做三维,然后开启加速。如果针对 will-change 属性的具体设计,没有提供正常支持,这种方法就不太建议了。

类命名用 Unicode 字符

你可以在如下代码看到使用 Unicode 字符做类名:

.❤ {
    ...
}
.☢ {
    ...
}
.☭ {
    ...
}
.★ {
    ...
}
.☯ {
    ...
}

只是开个玩笑。尽量不要在大项目中使用,因为不是每一台电脑都一定支持 UTF-8。

百分比表示的垂直边距

事实上,垂直缩进是根据父元素的宽度、而非高度计算出来的。我们创建两个区块:

<div class="parent">  
    <div class="child"></div>
</div>
.parent {
    height: 400px;
    width: 200px;
}
.child {
    height: 50%;
    padding-top: 25%;
    padding-bottom: 25%;
    width: 100%;
}

理论上,应该根据高度来填充父元素的,不过,我们看看结果:

CSS百分比表示的垂直边距

你应该记住,百分比是根据父元素的宽度计算出来的。

Firefox 下的 button 边距

Firefox 还没有自身方法来计算 button 的边距。貌似奇怪,但是你可以手动添加。

Firefox 下的 button 边距

还可以这样修复:

button::-moz-focus-inner,  
input[type="reset"]::-moz-focus-inner,  
input[type="button"]::-moz-focus-inner,  
input[type="submit"]::-moz-focus-inner {  
    border: none;
    padding:0;
}

Color + Border = Border-Color

不是每个人都明白,除了为任何对象定义文本颜色,还可以定义其边框颜色:

input[type="text"] {  
    color: red;
    border: 1px solid;
}

CSS:Color + Border = Border-Color

流金岁月

如果你仍然不得不支持 IE7 等类似情况,那么,你可以用一个笑脸来定义其 hack:

css 的类名

很酷,对吧?


充分发挥 JavaScript 语言的优势

本文是翻译,版权归原作者所有



尽管我在生产环境中使用 JavaScript 长达 8 年之久了,但是,直到最近 2 年,我才开始学习如何正确地编写 JavaScript 代码,根据我对人们的理解,很多开发者都有类似经历。我们有相当一部分人用 PHP、ASP 等语言做网站,貌似把 JavaScript 当成了一种补充。然而,随着 NodeJS 的流行以及浏览器的发展,JavaScript 已经冲到了现代前端 web 开发的最前线。

我想在本文分享一些东西,我觉得它们是当今每个开发者都应该充分发挥 JavaScript 优势的技术点。我不是要列出一份详尽的清单,而是尽量遵循我所认为的重要程度进行论述。

JavaScript 诞生于混乱的年代,因此一些好的和坏的特性就构成了这门语言。当你用 JavaScript 和其它语言开发软件时,你会尝到痛苦的教训,然后不得不回头维护(或扩展)所谓的应用程序。没有这个经历,你将很难理解我要说的内容。下面谈谈我所接受的惨痛教训。

1.改变头等函数

头等函数注1将一门编程语言变成了成年人的乐高积木。简而言之,有了头等函数,意味着 JavaScript 能够把函数做为参数传递,还能做为另一个函数的返回值、并赋给变量。这意味着你用很少的代码就能实现非常强大的功能,尤其在函数式编程中。举个实际例子:

var myArray = [1, 2, 3, 4, 5, 6]
 
var add = function (a, b) {
  return a + b;
};
 
var getTotal = function (arr) {
  return arr.reduce(add, 0);
};
 
getTotal(myArray); // => 21

在第 7 行,我们将一个匿名函数赋给了 getTotal 变量。在第 8 行,我们把 add 函数做为参数传给了 reduce 函数。我们就可以创建更高层次的通用函数,避免了不必要的重复(符合 DRY 原则注2)。我们还能实现复合函数注3,在例子中,我们把 add 函数做为 reduce 的参数,组成了复合函数。

2.学习 Array 的原生方法

我不单单要讨论 Array.prototype.pushArray.prototype.splice。映入脑海的还有一些更有用的函数:

和第一个例子一样,你可以在复合函数中使用它们,并组装出某些更加通用、但功能强大的、更高级的函数。大部分应用程序都要涉及数据集的操作,为了取得这些数据,你需要明白有哪些可用的工具,这是及其宝贵的。

3.使用 ES6(ES2015)

箭头函数、let & const、解构、尾调用优化和模板字符串,都属于 ES6 的一些不错的特性。我个人设法避免使用 class 关键字,因为它鼓励类和构造函数的继承,它们更容易引发误用和副作用。

ES6 特性为编写 JavaScript 引入了一种相当新颖的方式,多研究一些细节、探索这些特性,或许是本文的一个部分,我下周可能还会有后续文章。

在浏览器/NodeJS 环境(当写代码时)使用大部分的 ES6 特性,需要用到转码器,相应工具可以访问 https://babeljs.io/。做为一名 web 开发者,我习惯使用 babel 和 browserify,用一条简单命令将 ES6 风格的代码转换成 ES5 的风格:

npm install -g browserify babelify
browserify -t babelify es6code.js > es5code.js

如果我们用 ES6 重新编写第一个例子中的代码,可能是如下代码:

const myArray = [1, 2, 3, 4, 5, 6]
const add = (a, b) => a + b;
const getTotal = (arr) => arr.reduce(add, 0);
 
getTotal(myArray); // => 21

代码看起来简洁、直观,这应该足以引起你对 ES6 的兴趣了。但要牢记一点,某些和引擎相关的 ES6 特性,可能无法通过转码器。

4.理解绊脚石:作用域、闭包和类型转换

作用域

描述这个问题的最好方式就是借助一段代码,我打算借鉴 Douglass Crockfords 《JavaScript 语言精粹》上的一个类似例子,因为它非常贴切:

var foo = function () {
  var a = 2,
      b = 4;

  var bar = function () {
    var b = 9,
        c = 14;
    // Here: a is 2, b is 9 and c is 14

    a = a + b + c;
    // Here: a is 25, b is still 9 and c is still 14
  };

  // Here: a is 2, b is 4 and c is undefined

  bar();
  // Now: a is 21, b is still 4 and c is still undefined
};

可以看到,你声明变量的位置,和内部函数怎样被定义、以及内部函数怎样引起变量 a 改变其值,存在着关系。对变量声明保持清晰的认识,有助你编写更加可靠的软件。对于函数,要清晰地定义输入和可预期的输出,并尽可能明了。

闭包

闭包是 JavaScript 里保持私有变量的一种绝妙方式,不过,貌似也成了被误解的根源。基本上说,闭包支持你从内部函数去访问外部函数的作用域(又称作用域),类似于上面作用域例子中的 a。就这么简单。有意思的是,当你从外部函数返回一个函数、而内部函数可以访问外部函数的作用域时,内部函数将一直保留外部函数的作用域,哪怕过了整个外部函数的生命期。听起来有些复杂,举个例子:

var outer = function () {
  var a = 1; // variables declared outside of the 'inner' function declaration
  var b = 2;
  
  return function inner() {
    return a; // return 'a' that was defined in the outer function.
    // return b; // We could also access 'b' if we felt like it.
  };
};

var inner = outer(); // the outer function returns the inner function
inner(); // => 1 // the inner functions still remembers that 'a' was defined as '1' from the outer function

类型转换

由于 JavaScript 貌似荒谬的类型转换,而带来了很多笑话。为了避免所有让人讨厌的地方,最好使用可信任的 ===,它会在你所期望的大部分情况下,做严格的对象比较。只有一种情况要不好使,当你比较独立的对象字面量时,即使它们具有完全相同的属性和结构,它们也不是相等的,因为它们具有两个完全独立的对象。对于这种情况,只比较对象的每个属性就可以了,像 Underscorejs 之类的资源库就提供了上述情况的深度比较的方法。

5.避免类继承

这或许存在争议,因为软件行业的现状就用到了类继承。我敢说,你用类继承写过某个不错的应用程序,但是,如果你这样做了,那么你可能没有发挥 JavaScript 的优势。借助更加有用的方法,你就能用 JavaScript 写出更加轻量、可维护的应用程序。应用程序逃不掉修改的命运,如果你使用类继承的模式,那么当你用到服务于另一个需求的某个对象时,你将不得不继承随之而来的所有东东。当你想做单元测试时,就更加恶心了,你最终不得不为基类模拟出一整套需求,而你的目标只是为了测试高于继承链之上的简单属性。最好扔掉设计蓝图,根据数据流和对象、而非种类,开始思考你的程序。用这种方式思考,可以极大改善程序的可测试性、可维护性和简洁性。

6.坚持学习,不要失去信心

很多人抱怨 JavaScript 发展太快,有太多的坑,不过,这只是因为人们没有学习这门语言,没有专注于框架。JavaScript 肯定有一些糟粕,但是它也有一些精华,如果你想拥抱正面的东西,那么你就能充分发挥它的优势。我主要做 C# 开发,但是自从我开始正确地学习 JavaScript 之后,它就变成了我的首选语言,因为它具备高度灵活的特点。如果你打算一试,那么 JavaScript 就是踏入函数式编程的入门语言,尤其在和 ReactJS 一起使用时。老实讲,我强烈推荐使用 JavaScript 进行函数式编程。

我希望本文有助于传播 JavaScript 优秀的福音,鉴于 JavaScript 的成功,我也想说句公道话,希望其它语言能从中寻找灵感、而不只是维持现状。


传送门:阮一峰《ECMAScript 6简介


技术债务和技术投资

本文是翻译,版权归原作者所有


  • 原文地址(original source):http://jamison.dance/12-31-2015/technical-debt-and-technical-investment/
  • 作者(author): lexical NOPE(@jergason

技术债务

技术债务,是软件工程讨论折衷方案时所用到的一种工具。当你遇到技术债务注1时,你就会堆积一些快速、肮脏的代码,它们更难以维护、或拉低了图中的效率曲线。随着时间的推移,和你一开始用正确的方式开发相比,维护快速、肮脏代码或基础架构的成本,要更高一些。你能够感受到此言不虚,因为我画了一张图。

技术债务

一些人把技术债务的说法用作糟糕的代码。这不太正确。技术债务是某种原因导致的糟糕代码。有时候,你用错误的方式,以更快地完成工作;有时候更快速地完成,的确重要。当速度的好处超过维护成本或重构糟糕代码时,你会选择承担债务。

技术投资

技术债务的对立面是技术投资注2。对于技术投资而言,你当下放慢速度,是为了将来能够加快速度。或许你选择编程语言或 web 框架。你的团队花时间学习新工具,但是在某种程度上,如果你选择得当,它们就更有效率。下图是没有标数字的手绘图,它无可置疑地证明了这一点。

技术投资

你需要投资

成功的产品,只会变得更加复杂。如果人们喜欢并使用你的产品,你将会增加更多功能和复杂性。对于人们愿意付费的、或大体上在使用的现有功能,是不可能移除掉的。

在最初原型阶段,工具和技巧是有生产力的,但是,有时候它会与较大型产品的复杂度发生冲突。如果你在处理复杂度方面不能投入,随着复杂度的增加,开发速度就会减缓。随着产品复杂度和团队成员的增加,技术投资能够规模化增加你的团队生产力。

风险

技术债务和技术投资均有风险。如果你低估了维护技术债务、或承担它所带来的好处,你的产品在长期收益方面将放慢脚步。如果你高估了技术投资的价值,你在短期内就会减缓进度,而没有长期加速。

好的技术投资所需条件?

如果你认同了技术投资,接下来的问题就成了「我该如何评判一项优秀的技术投资呢」?其答案可能和普通投资一样——你做不到的。你可以审核当前情况,并尽量预测未来趋势,但是,平心而论,这都是臆测。

技术上很难评判,因为我们经常期望看到立竿见影,如果我们看不到,就会放弃。计算机历史充斥着过早放弃的好思想,但是也长期散落着坏思想。

既然有了这些警告,下面给出一些预示,它们反映出 web 开发团队中属于好的技术投资。我不保证全部都是对的。

Elm、PureScript、或其它语言

静态类型的纯函数式编程是一种老思想了,它从未引起主流编程的注意。我认为,它是未来的一种方式,尤其对于客户端应用程序,更是如此。Elm注3 和 PureScript注4 正在解决文化和教育方面的问题,它们阻碍了类似语言赢得主流的使用,我觉得,在未来几年,它们当中一定会有一种语言赢得广泛的用户基数。

可观察对象

这包括了类似 RxJS注5 的技术、构建于 Cycle.js 之类的可观察对象之上的框架,还包括在 JavaScript 应用程序里使用可观察对象来管理状态和通信的通用实践。我认为,我们在管理客户端 JavaScript 方面还没找到有效的解决方案。可观察对象貌似成了更好方式的良好选择。

雇佣和指导初级开发人员

我可能要多谈一下这个问题,但是重点在于,人才市场的初级开发人员有着很低的薪水,他们常常为了找到工作而不顾一切。如果团队或公司能够和初级开发人员较有效率地协作,并帮助他们成长为高级开发人员,就会在雇佣方面获得巨大优势。

技术债务和技术投资

对于技术债务,你把代码搞得一团糟,随后需要自己擦干净屁股。对于技术投资,你预先理顺一切,以后你就能更有效率地工作。二者都可以被理智地使用,它们对于保持一种健康的工程师组织和文化也是必要的。

你对技术投资有什么看法?你有一些好的技术投资想法吗?


Jamison Dance 是 Kuali Co 的一名软件工程师。他喜欢小猫、让计算机更聪明、以及孩子们开怀大笑的魔力。他在 http://jamisondance.com 撰文,twitter 账号 @jergason,GitHub 账号 jergason。他还是 JavaScript Jabber 网站上的播客。