首页

高手是如何利用认知偏差,打造独特用户体验的?

雪涛

为什么大爷大妈们总对排队情有独钟,为什么平常不想要的东西一打折就买一堆……震惊!原来套路你的竟然都是自己的大脑!

如今想要成为一名合格的用户体验设计师,首要就是对目标用户的理解和把控。而为了实现这一目标,掌握一些心理学知识就显得尤为重要。当我们能在设计中合理利用心理学的时候,就离创造出让用户感慨「深得朕心」的体验不远了。

这次要介绍的是设计心理学中与我们日常生活密切相关的认知偏差 (Cognitive bias) ,了解它的基本定义之后再结合相关案例探讨如何在设计中利用认知偏差来创造更好的用户体验。

什么是认知偏差

网络上的定义是这样的:人们在知觉自身、他人或外部环境时,常因自身或情境的原因使得知觉结果出现失真的现象。

简单来说,就是大脑创造了一些快捷方式,在处理信息时自然地去调用这些快捷方式,只是这种操作在快速的同时,也会对我们的决策过程产生危害,比如我们会选择性忽略一些信息,或是自发地对信息进行脑补。这样的认知模式导致我们产生了非理性的偏差。

△ 图:大脑的操作

认知偏差种类丰富,已经被提出的就有几百种,有很多尚未被完全验证。下图总结了现有的认知偏差,它们大概可以分为四类:信息过载,信息的意义不明确,大脑来不及认真作出反应以及大脑存不下所有的记忆。通过这张图,我们可以更清晰地了解不同认知偏差背后的成因。

△ 图:认知偏差宝典

认知偏差在体验场景中的应用

那我们该怎么在设计中利用认知偏差呢?我根据日常工作中经常接触的消费场景和学习场景总结了以下几个方面:

△ 图:章节概览,也叫懒人速读版

1. 引导用户决策

作为体验设计师,我们需要为用户的决策创造合适的环境,来引导其按照我们设计的方向去达成他们的目标。

锚定效应

锚定效应(Anchoring)是比较常见的一个被利用在消费场景中的认知偏差。它指的是人在做决定的时候,会在很大程度上依赖于其接触到的信息。

比如商场里原价 2000 现价 500 的商品,原价的存在会让人觉得这件商品的价值就是 2000,现在 500 简直是捡了大便宜。

在体验设计中可以借鉴这种做法,通过前后对比放大来优惠感知,进而促进用户做出有利于我们的决策。

△ 图:利用锚定效应推荐会员套餐,对比差价确实诱人

从众效应

从众效应(Bandwagon Effect)是另外一个常见的用于消费场景的认知偏差,指的是人们做决定时通常会和他人保持一致。

下图是售前页一般的设计技巧,通过展示购买人数和滚动播放购买信息来体现出该商品的热门,让正在犹豫的用户「随大流」下单购买。

基于此,我们在设计中可以营造出一种群体选择的效果来吸引更多的用户。

△ 图:这么多人选择,跟随大家不会错,买它

内群体偏差

前面提到的认知偏差之外,内群体偏差 (ingroup bias) 也一般被用来引导用户决策,它是指人们会在认知上倾向于自己所属的群体。

比如 Booking 在查看评论的区域加入了筛选评论语言这一按钮,虽然设计的本意可能是为了方便用户更好地理解评论内容,但是在真实的使用过程中可以发现,用户更加信任自己所选标签内的评论内容,因为同语言往往意味着来自相同的国家或者相近的文化背景,用户通过这种方式找到一个小群体,然后被影响进而做出与群体内人们更为相似的决策。

△ 图:同胞的评价更可靠

因此在体验设计中利用内群体偏差的关键点在于打造群体归属感,借由小群体的力量影响用户的决策。

2. 提高用户粘性

引导用户做出于我们有利的决策之外,同样,我们可以利用认知偏差提高用户粘性,增强用户和产品之间的联系,使他们对产品「爱不释手」。

宜家效应

宜家效应 (The Ikea Effect) 是指消费者对于自己投入劳动、情感而创造的物品的价值产生高估的价值判断偏差的现象,消费者对于一个物品付出的劳动(情感)越多,就越容易高估该物品的价值。利用宜家效应提高用户粘性的核心是创造低投入、高回报、高贡献价值的任务,保证用户能够完成任务的基础上贡献自己的价值。

在学习场景中我们可以利用宜家效应提高用户粘性,将用户留下来坚持学习。

首先需要保证用户能够完成任务。懂你英语 ®A⁺ 产品中,我们会在用户设置目标时描述该目标的表现,用户以此选择自己的目标。这种方式下的用户对于任务结果的预见性提高,能清楚的知道如果完成任务会达到什么效果,更能被促使顺利地完成任务。

△ 图:目标展示与贡献积累

其次,用户对产品粘性增强需要能够感知到自己所做出的投入,学习场景下这种投入感知更多体现在知识的累积上。在懂你英语 ®A⁺ 课程中,我们将用户与目标之间的距离设计为学习路径,用户每完成一个阶段的学习便会在路径上明显前进,日积月累下来用户能看到自己前进的印记,清晰感知到自身知识的累积,也就因此对产品有更高的价值感受。

3. 情感化激励

负向偏见

由于学习的「反人性」,学习场景下的用户在体验流程中产生消极情绪的概率要大于其余场景,比如学习效果不好,难以坚持等。此类消极情绪对于学习产品影响很大,是因为负向偏见 (negativity bias) 的存在,人们对不好的事情的记忆比快乐的记忆更加清晰,更经常回忆。

因此在学习场景下我们需要给用户更多的正面积极的反馈来抵消掉负面体验的影响。在懂你英语 ®A⁺ 课程设计中,我们在学习结果页根据用户不同的学习表现给出不同的反馈,即使是偏低的成绩,也依然会给出一个较为积极的反馈,以期鼓励用户坚持学习。

△ 图:做得不好也不要灰心

除了简单抵消掉用户的负面偏见,我们甚至可以通过设计去完全扭转局面,变困境为有趣的体验。最经典例子便是谷歌断网时的小恐龙游戏,不知道有多少人会故意关掉网络来玩这个游戏。

△ 图:谷歌的断网小恐龙

最后

作为设计师我们可以通过了解和利用认知偏差来创造既让用户满意又平衡商业的双赢体验。但由于设计师本身也是人类,与用户拥有同样的思考机制,因此在日常的调研分析和设计的过程中也要警惕认知偏差的影响,不断深入了解用户以及使用科学的测试方法来完善自己的设计,持续迭代反思,不因为某个方案自己倾注了很多心血,就觉得它是最好的。用户可能并不买账呢。

从四个角度,帮你快速了解瓷片区设计

雪涛

最近设计项目中涉及到「瓷片区」,于是和一些设计伙伴请教了解了一下,在此记录总结一下,也希望可以对大家有些小小的帮助。

什么是瓷片区

听说:美团内部将首页的运营广告位模块称为瓷片位,其可以根据需求变动灵活调整,就像瓷片一样灵活适用,顾名思义,瓷片区也就被叫开来了。

根据下图我们可以了解到,瓷片区在产品中的应用。

瓷片区的功能

瓷片区作为与 Banner、金刚区并行的三大运营板块,都负责着导流的功能。瓷片区较两者更便于在页面中进行布局,可以灵活复用。

在电商产品中,导流指的是通过某种形式,增加对商品/功能的曝光,使自己的用户群去购买或了解感兴趣的商品/功能。导流简化流程:导流入口 → 落地页 → 转化率,设计师需要通过在这个流程中收集的数据,进行复盘反思优化设计。

瓷片区常见类型

瓷片区属于运营区,在页面中通常位于用户容易点击的区域。通常为图文混排,常见的类型有:实物类、插画类。

1. 实物类

应用场景:需要对商品/服务有高曝光度的产品类型,如外卖、电商、旅游类等,通过对自有商品/服务的直观展示,达到吸引用户的目的。

优点:识别度高、适配千人千面、元素更换灵活;

缺点:图片质量要求较高;

2. 插画类

应用场景:常见于金融类、虚拟类产品。

优点:高度概括主题的图形,通过插画增加产品的调性和趣味性;

缺点:针对性比较强,难复用,花费时间;

设计关键点

1. 排版

瓷片区常见排版方式:左右排版、上下排版和对角线排版。设计师可以将三种排版方式组合设计,使页面更具有节奏感;

  • 左右排版:图标/插画左右布局,一行展示 1 块或 2 块,适用各种场景;
  • 上下排版:图文上下结构布局,一行至少展示 3 块瓷片,适用于功能入口;
  • 对角线排版:图文呈对角线布局,一行显示两块瓷片,适用于文字信息较多的需求;

2. 图片

对于电商或商城类产品来说,配图的好坏是影响用户点击率最直观的元素;设计瓷片区时需要考虑全局配图和局部配图的情况:

  • 配图质量:高质量、符合产品调性、背景简洁、抠图边缘一定要干净;
  • 配图规范:统一图片或插图的尺寸和视觉面积;保证图片之间的差异性;提炼关键文案信息。

3. 文字

  • 主副标题:通过大小、粗细和颜色进行区分,颜色不宜过多;
  • 标签文字:结合需求属性和定位,处理不同层面的标签;

4. 背景

瓷片区背景常见类型:白色/浅色背景、渐变色背景;设计师根据产品调性及业务需求对背景进行灵活使用,但要遵守以下原则:

  • 层次清晰,是否对最重要的元素有所突出;
  • 易读,保证文本及商品符合所有设备的易读标准;
  • 视觉表现力,强化品牌风格;

小结

文中从四点对瓷片区进行了一个概括性的了解总结,设计要点的话在排版设计中都是需要注意的基础知识所以未多加赘述,以此为自己在项目中遇到的知识点做一次沉淀。

文章来源:优设    作者:木子的小千世界

你想要的免费商用插图,这个网站全都有!

雪涛

一转眼,夏天就来了,设计中需要清新又抓人眼球的图片,老板又要求使用各年龄段都喜欢的插画风格,应用场景要广,但是预算又没有那么多,该怎么办呢?这也不是熬夜就能熬出来的,不要烦恼,下面这个网站立马解决你上面所有需求的同时,还能帮助提升你的工作效率,延缓你秃头的时间。

这个创造性超强的插画制作网站就是 BLUSH ,你可以根据自己的想法进行元素的替换,也可以直接使用 8 位网站合作设计师的现有作品,超清新的颜色和多样的搭配空间,绝对会让你十分满意的,无论是做 Banner、海报还是用作文章中的插图都十分合适。最重要的是,可以免费商用!接下来就跟着我一起,走进这个网站吧。

网站链接:https://blush.design/

网站内拥有 12 大种类的插画图库,包含人物、城市风景、植物、甜点、涂鸦等等。选择你喜欢或者需要的一类,就可以开始你的创作。操作类似于换装游戏,在各个你需要改变的地方更换你喜欢的元素就可以。如果你要摸清整个网站可以产生多少种搭配的话,可能需要花上你不止一天的时间哦。

从上图可以看出,单在人物上,网站就提供了不同的风格,有像儿童形象的「Friendly ones」,也有极简线条的「Big Shoes」,还有夸张超炫的「Power Moves」等等。接下来,为了更好的理解网站提供的服务和操作的方式,我选取实用多变的「Croods」来演示。

首先在栏目左侧选择「Croods」,就会看到下面这样的界面。最上面是设计师的作品,下面则是可以由你自由创造作品的入口,造型可以选择站着或者坐着,场景可以选择谈话、聚会、公园等等,整体的背景也有五大类供你选择。

这里我选择点击「Peaceful Place」。网站出现的作品很符合现在多数人在家办公的状态,如果你不想自己动手,那直接选择这张图片或者点击「Randomiza」再随机生成一张图片,自己满意之后,点击「Download」便可以获取 PNG 格式的图片,不过,高品质 PNG 和 SVG 格式则需要付费。

当然,你也可以通过改变图片上的各个元素,生成你所需要的场景插画。例如想要一个盘着腿坐在沙发上发消息,和朋友聊天聊的很开心的一个形象,就可以通过下方给予的元素进行个性化定制,把表情改成开心,电脑换成手机,右上方替换成聊天框,最后导出为下图的样子。如果想要多人的场景,在上一步选择多人的场景进入就可以了。

同时,网站也支持下载插件,方便众位设计师在 Figma 中更好的运用。在首页点击右上角的「Get Plugin」进入页面,注册账号就可以顺利安装插件。大家可以根据自己的需要选择下载插件或者直接在网站上在线制作。

最后,不得不提的一点是,网站图库在兼顾多样性和平等性上做了很大的努力,人物的肤色可以随你改变,残疾人也有一席之地,可以选择为人物匹配轮椅,或者给人物戴上假肢,涵盖尽可能多的人物情况和场景。相信这样的网站一定能满足你丰富的设计需求,快去使用吧!

文字来源:优设    作者:山楂

如何做好儿童节设计?

雪涛

在教程开始之前,我先说一下今天的教程我们会讲哪些部分。首先我们会分析儿童风格的特点、然后在这些特点之上给大家演示绘制一些插画小元素。

我们先来看一下这些海报里,是不是很容易就能分辨出,哪一个是关于儿童主题的画面?

可能刚刚的会比较明显,那这组是不是也同样可以分辨出哪一个儿童感比较强的画面?儿童感它既然能够在这些风格里凸显,它一定有一些特点。那么接下来我会通过几个方面去分析这些特点。

物体的选用

我们要选用一些符合儿童风格的元素。植物是儿童画里出现最常用的元素,动物作为自然界的一份子,当然也非常适合。一些儿童属性的物品。比如书本、棒棒糖等。还有天空的云朵、房子这些儿童画画的时候经常出现的元素,都非常适合儿童画面。

但是一些明显不属于儿童用品的元素就不合适了。比如说口红,口红不会让人联想到儿童,更多是成年女性。骷髅头这种恐怖的物体也不能出现。

那我问一下,眼镜适合吗?如果是这种尖锐的眼镜的话可能不太适合。

但是一旦它的外形变得圆润可爱,就很合适。很多物品也像眼镜一样,只要把外形改变得更符合儿童的气质,就不会显得突兀。

假如某一些物体儿童化特征不明显,而又想让它有儿童感,那应该怎么做呢?加入表情,表情简单可爱,就能表现儿童感的情绪。

每个物体加上表情就会变得可爱。但也正因为这些表情加在了本身没有表情的物体上,所以风格里有一种搞怪感。

颜色

我把随机下载的儿童海报拼接在一起,整体分析它们的特点。

儿童插画的海报元素使用的色相比较多,这里随机抽取的海报里,画面都是使用了比较多的颜色,红、黄、蓝、绿基本都用上了。

在饱和度上都偏高,一般保持在 70% 以上。

明度方面,把画面模糊再变黑白,我们可以看到整体画面给我们的感觉是比较亮的,就连中间用了大色块的海报的明度也在 50% 以上。而存在的一些深色也是作为画面的点缀。

那么总结起来,一般是色相选取的颜色比较多、饱和度比较高、明度比较高。我们以这张图为例。

当它的颜色种类变少的时候,画面的活泼程度就会降低。

而饱和度变低后,不仅活泼度降低,这种颜色的风格会趋向于成人化。

画面的颜色变深之后,画面就没有透气感。有一个很合适的成语形容,叫「老气横秋」。就是缺乏朝气的感觉。

形体

那么儿童插画的形体有什么特点?总结起来有两个特点,简单和随意。首先儿童对世界的理解还不够深,能力也不足,往往画岀来的东西都是以概括的形式。

比如儿童画一颗真实的树的时候,去掉很多细节,把一个物体比较有特征的外形表现出来。比如儿童画一颗树可能是这样画的。

其次儿童的能力和经验不够,画画的时候边缘都是比较不平滑的,看起来比较随意。

而且就算本来在他眼中是对称的物体,因为手绘的感觉而看起来歪歪扭扭。

当一个物体简单成平滑的几何形的时候,它是偏向于设计感和现代感的。当它越来越接近于随意感觉的时候,它的儿童特征会更强,同时更亲切。

就像这些作品,虽然是插画师画的,但是却有很强的儿童感,就是这种简单和随意再加上插画师的审美形成的画面。

要注意的是简单和随意的把握,太过随意,商业化的价值就不高了。

画面布局

儿童海报里的插画虽然每个元素比较简单随意,但是要保持画面比较完整,是需要很多元素的拼凑的。

就像这个幼儿园品牌的元素都特别随意,但是最后呈现的效果却也不错,除了在延伸上花了很多心思之外,还因为它的元素比较多,看起来不至于太单薄。

而这些元素的摆放可以不遵循严格的透视和逻辑,这种方式体现了儿童绘画和成年人严格的绘画原理的不同。

案例演示

能满足上面的特点的儿童风格有很多,在这里我打算用一种比较简单的绘制风格,来进行操作的演示。

首先我们画一颗简单的树,来看一下这个风格。外形不会追求非常的对称。

就连树干我也是用「画笔工具」和鼠标画的。营造一种随意感。

在这里调整一下最后整体的外形后,我们会发现它的边缘和线条都太顺了。没有手绘的自然感。

所以我们要用到「变形工具」,对边缘进行推拉,这样它的边缘就不会特别光滑了。

最后调整一下尖锐的锚点,树就这么简单地完成了。

大家可能会觉得这个风格过于简单了,我用画动物,去讲解一下其中的细节。首先还是用「钢笔工具」,把形体轮廓和主要部位画出来。 大家是不是觉得画到这里就完成了?其实这个风格比较简单,如果没有一些装饰感的细节的话就会显得太过随意了。所以在这只鸟里,要去想怎么添加一些细节。

首先可以加个腮红。腮红的颜色不一定是红的。像鸟的身体色块面积太大了,需要有装饰,所以画几条羽毛。羽毛的形状太过规矩了,还是需要转曲用「变形工具」调整。

还有尾巴的部分也是需要用线的形式让整体更有细节感。最后都是要用「变形工具」让它的线条有种随意感。

最后的细节在嘴巴上添加,直接画一条直线。其实添加细节就是不要出现一整大块的纯色色块。

对于物体的简化不用太过于死板,比如我们现在看到的这个植物,它可以直接简化成圆形和密集的点的组合。

一开始我是直接用「剪切蒙板」的,但是这样边缘过于光滑了,而且里面的点也被框住,不够美观。于是就把点的分布超出圆形一点。这样看起来就比较有细节了。

而我们通过改变形体,就可以演变出很多种不一样的形态。比如这个物体,我在做的时候想能不能把中间的圆缩小一点 。黑色的点占比再大一点。这样就产生了另外一种植物。

甚至还可以直接去掉中间的圆,把点再放大作为主体。原本的点放大会变得太过随意。所以只能通过画圆形和「变形工具」调整。

这样又可以得到不一样的一颗植物了。这样的演变可以根据画面的需求选取不一样的植物。

所以在这里我已经画好一套常用元素的素材了。以植物动物为主,基本能满足日常设计中的需求了。这套素材会分享给大家,在我们的订阅号后台回复「儿童节素材」就可以下载。这套素材是允许大家商用的。有了素材后,怎么用呢,接下来我将用这套素材演示它能怎么使用。

这些素材可以在画面作为点缀营造气氛。

直接使用这些素材,堆叠成完整的画面。

也可以和图片中的人物或者产品做穿插。

接下来用一个案例演示一下,我是怎么用素材的。这个是学而思的手机端海报的文案,主要有标题部分、课程内容、和二维码等。

首先建立一个手机端尺寸。设置好版心的大小。在这里我做的是下方卡片式的结构。(白色色块部分是海报)

可以先把文案编排进来。首先安排好大的元素,二维码和活动价格。主讲老师和上课时间的小标题,字号大小要保持一致。卡片里的信息就编排完了。

标题是画面最大的字号放在上方,副标题和补充信息和大标题居中。接下来就是要在画面中添加我们的素材了。那么怎么选择呢?首先要定好你要选择的元素范围,不要同时出现太多种类的元素。以植物为主、再加上几个元素点缀是一般不会出错的选择。

首先添加的是面积比较大、块面化比较明显的元素。这里就加了三棵简单的树。

接下来就是添加一些更有装饰感的花花草草。

在添加元素的时候也要注意画面点、线、面的结合。就比如现在我们添加的元素主要是以面和点为主。

虽然树的树干也有线,但是轮廓的线条太「顺」了。

所以要添加「线」的特征更明显一点的元素。

在画面的上方加上适合存在画面上方的元素、比如长颈鹿因为本身比较高,所以没问题。把标题移到适合的位置,加蒲公英作为点缀。最后只剩下中间的部分怎么做出和主题相关联的画面了。

我们还有一个信息可以放在人物的手上。最后一个元素就从海报的主题出发。既然是学习的内容,可以画一本书,放在背影上。元素的拼凑完成了,最后就是细节的添加。

现在画面下方的信息不够突出,是因为卡片和背景都是白色的,所以下方加上一个深色的色块,让信息更显眼。

突出的信息可以变颜色。

背景变成黄白色,最后加上 logo。现在画面好像完成了,但是我最后还发现了一个问题,画面上的颜色饱和度都很高,各种颜色之间碰撞,看起来有点透不过气的感觉。

是因为画面缺少浅色。所以我选了两个花把它变成白色。

现在这个素材使用的案例就完成了。

最后要说明,这一套素材它只是一种风格,我们教程前面有说到的任何一个因素的改变都可以演变成无数的风格。

比如它的颜色搭配也可以变,变成一种更亮眼的颜色。或者说它变得更随意,有笔触的自然感。甚至还可以是画画技法风格变化。也可以为每一个元素都加上眼睛,一种搞怪感就出现了。所以是有无限的可能性的,最重要的是要学以致用。

做好画面还不够,我们画面选择的字体也是很重要的,那么我觉得这几款字体做得都不错。

文章来源:优设    作者:研习设

实现一个Vue自定义指令懒加载

seo达人

什么是图片懒加载

当我们向下滚动的时候图片资源才被请求到,这也就是我们本次要实现的效果,进入页面的时候,只请求可视区域的图片资源这也就是懒加载。


比如我们加载一个页面,这个页面很长很长,长到我们的浏览器可视区域装不下,那么懒加载就是优先加载可视区域的内容,其他部分等进入了可视区域在加载。


这个功能非常常见,你打开淘宝的首页,向下滚动,就会看到会有图片不断的加载;你在百度中搜索图片,结果肯定成千上万条,不可能所有的都一下子加载出来的,很重要的原因就是会有性能问题。你可以在Network中查看,在页面滚动的时候,会看到图片一张张加载出来。


lazyLoad


为什么要做图片懒加载

懒加载是一种网页性能优化的方式,它能极大的提升用户体验。就比如说图片,图片一直是影响网页性能的主要元凶,现在一张图片超过几兆已经是很经常的事了。如果每次进入页面就请求所有的图片资源,那么可能等图片加载出来用户也早就走了。所以,我们需要懒加载,进入页面的时候,只请求可视区域的图片资源。


总结出来就两个点:


1.全部加载的话会影响用户体验


2.浪费用户的流量,有些用户并不像全部看完,全部加载会耗费大量流量。


懒加载原理

图片的标签是 img标签,图片的来源主要是 src属性,浏览器是否发起加载图片的请求是根据是否有src属性决定的。


所以可以从 img标签的 src属性入手,在没进到可视区域的时候,就先不给 img 标签的 src属性赋值。


懒加载实现

实现效果图:


imgLazyLoad


<!DOCTYPE html>

<html lang="en">

<head>

   <meta charset="UTF-8">

   <meta name="viewport" content="width=device-width, initial-scale=1.0">

   <title>Document</title>

   <style>

       div {

           display: flex;

           flex-direction: column;

       }

       img {

           width: 100%;

           height: 300px;

       }

   </style>

</head>

<body>

   <div>

       <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657907683.jpeg">

       <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657913523.jpeg">

       <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657925550.jpeg">

       <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657930289.jpeg">

       <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657934750.jpeg">

       <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657918315.jpeg">

   </div>

</body>


</html>

监听 scroll 事件判断元素是否进入视口

const imgs = [...document.getElementsByTagName('img')];

let n = 0;


lazyload();


function throttle(fn, wait) {

   let timer = null;

   return function(...args) {

       if(!timer) {

           timer = setTimeout(() => {

               timer = null;

               fn.apply(this, args)

           }, wait)

       }

   }

}

 

function lazyload() {

   var innerHeight = window.innerHeight;

   var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

   for(let i = n; i < imgs.length; i++) {

       if(imgs[i].offsetTop < innerHeight + scrollTop) {

           imgs[i].src = imgs[i].getAttribute("data-src");

           n = i + 1;

       }

       

   }

}

window.addEventListener('scroll', throttle(lazyload, 200));

可能会存在下面几个问题:


每次滑动都要执行一次循环,如果有1000多个图片,性能会很差

每次读取 scrollTop 都会引起回流

scrollTop跟DOM的嵌套关系有关,应该根据getboundingclientrect获取

滑到最后的时候刷新,会看到所有的图片都加载了

IntersectionObserver

Intersection Observer API提供了一种异步观察目标元素与祖先元素或文档viewport的交集中的变化的方法。


创建一个 IntersectionObserver对象,并传入相应参数和回调用函数,该回调函数将会在目标(target)元素和根(root)元素的交集大小超过阈值(threshold)规定的大小时候被执行。


var observer = new IntersectionObserver(callback, options);

IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数(即目标元素出现在root选项指定的元素中可见时,回调函数将会被执行),option是配置对象(该参数可选)。


返回的 observer是一个观察器实例。实例的 observe 方法可以指定观察哪个DOM节点。


具体的用法可以 查看 MDN文档


const imgs = [...document.getElementsByTagName('img')];

// 当监听的元素进入可视范围内的会触发回调

if(IntersectionObserver) {

    // 创建一个 intersection observer

    let lazyImageObserver = new IntersectionObserver((entries, observer) => {

        entries.forEach((entry, index) => {

            let lazyImage = entry.target;

            // 相交率,默认是相对于浏览器视窗

            if(entry.intersectionRatio > 0) {

               lazyImage.src = lazyImage.getAttribute('data-src');

               // 当前图片加载完之后需要去掉监听

                lazyImageObserver.unobserve(lazyImage);

            }


        })

    })

    for(let i = 0; i < imgs.length; i++) {

       lazyImageObserver.observe(imgs[i]);

    }

}

源码地址-codePen点击预览

vue自定义指令-懒加载

Vue自定义指令

下面的api来自官网自定义指令:


钩子函数

bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted: 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新

componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用。

unbind: 只调用一次,指令与元素解绑时调用。

钩子函数参数

指令钩子函数会被传入以下参数:


el:指令所绑定的元素,可以用来直接操作 DOM。

binding:一个对象,包含以下 property:


name:指令名,不包括 v- 前缀。

value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。

oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。

expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。

arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。

modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。

vnode:Vue 编译生成的虚拟节点。

oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

实现 v-lazyload 指令

<!DOCTYPE html>

<html lang="en">

   <head>

       <meta charset="UTF-8" />

       <meta name="viewport" content="width=device-width, initial-scale=1.0" />

       <title>Document</title>

       <style>

           img {

               width: 100%;

               height: 300px;

           }

       </style>

   </head>

   <body>

       <div id="app">

           <p v-for="item in imgs" :key="item">

               <img v-lazyload="item">

           </p>

       </div>

   </body>

   <script src="https://cdn.jsdelivr.net/npm/vue"></script>

   <script>

       Vue.directive("lazyload", {

           // 指令的定义

           bind: function(el, binding) {

医疗院感可视化

分享达人


可视化是利用计算机图形学和图像处理技术,将数据转换成图形或图像在屏幕上显示出来,再进行交互处理的理论、方法和技术。面对医疗行业,如何将行业数据,转化为视觉可视化中的点线面,是在这个项目中需要思考的问题。


本文将带来设计师在医疗院感可视化项目中的设计过程及思考,讲述如何在业务场景下对数据进行抽取表达。通过可视化打破传统院感系统的表单呈现,使院感场景具备互动性、观赏性,满足不同角色的使用需求。同时院感医生通过可视化的解决方案能清晰直观的了解到院感发生分布、病例分析,从而控制院感发生和预防。

本项目以浙江省建德市第一人民医院为案例,地理数据以建德医院坐标为准。



项目背景
院感是什么?院感为医院感染,入院48小时内都有可能感染到院感细菌。在医院里有专门的院感医生职位,对医院感染进行有效的预防与控制。而传统院感管理的工作流:医护人员及院感医生 > 院感系统分析疑似病例 > 得出结论预防或治疗。这种偏人工的方式数据获取方式,无法更的获取院感发生的原因、定位、以及未来的院感预测。


P1 因此我们通过对医院数据的整理,抽离出影响院感的数据,将院感发生、发展、管控、治疗、预测全流程进行整合。


P2 通过医院>楼层>人员三个层面,空间和时间两个维度对院感展示。打破传统院感系统的表单呈现,使院感场景具备互动性、观赏性,满足不同角色的使用需求:如院长的展示性需求。院感医生通过可视化的解决方案能清晰直观的了解到院感发生分布、病例分析,从而控制院感发生和预防。


P3 同时在这样的设计场景下,可以覆盖的医疗业务场景和数据单位也会更多元,具有一定的商业化价值。


P4 设计流程
整个项目的设计流程可以分为4个阶段:信息收集、可视化、线上搭建、效果调试。在此项目实践中,重点投入在前三大部分。


P5 Part1信息收集
我们基于项目背景,梳理要展现的数据指标,对整体业务场景进行故事脚本的规划(即如何展现前期的数据收集,并把其串联在整体业务场景中),设定动作摄像机语言,同时也需要了解最终呈现的硬件设备与使用环境。


P6 Part2可视化

1.交互信息框架
首先梳理院感的信息框架和交互方式。


整个大屏分为院楼层,呈现整体院感数据的统计;楼层屏,作为重点病区的监测预测;个人屏,分析病例回溯病程,从而预测院感。三屏之间相互跳转, 交互演示方式从医院的外部跳转内部结构、再到患者的个人维度,三屏都分别展示相关的数据指标。


P7 2.视觉风格
在大屏视效风格探索上,期初的目标是希望可以打造不一样行业视觉语言,所以选择不同于以往的设计大屏风格,选择白色的风格,符合大家对医护行业的认知。但到中期发现,在硬件设备上展示发是过曝的。因此对设计风格进行调整改为X光片的风格,色系上偏冷绿的感觉。这是在这个项目中的试错经验之一。


P8 3.建模设计
在可视化部分中遇到的难点:建筑模型的高还原。下图为建德第一人民医院实拍图。在大屏项目中,必须真实还原地理位置。而在此医院没有清晰的CAD图纸提供的;在Google的卫星地图下也没有的建筑结构的,所以我在建模的过程中,是踩了坑的,先盲画了一版,但是精细度不够,过于粗糙。


P9 因此我反复看得到的资料,通过在确定地理氛围内,去丰富场景。这样的好处是使业务场景更加丰富,包括扩展到院外的车流数据,为业务场景提供更多可能性 当然后期也摇到了建筑内部的消防图,根据消防图绘制内部结构。


P10 4.数据面板
对可视化组件的组件进行设计:时间筛选、数据统计、占比关系、趋势分析。设计之前也参考了很多概念版的可视化设计,希望在院感屏上可以呈现一种科技概念的感觉。



P11 Part3线上搭建
1.获取地理数据
这部分是非常耗时的,datav是数据驱动的可视化产品,在搭建部分,是全程依靠datav平台的。
首先需要明确地理数据,通过高德数据通过点线成面,可以作为场景定位,也就是物理模型的经纬度数据 后面还原数据效果,造虚拟数据,是非常依赖于这个坐标数据的。
119.291724 , 29.472365
这是建德医院的坐标,医院在地图上的数据是很简化的,颗粒度很大,具体位置无法显示。

P12 因此我们需要建立与地理数据绑定的建模,先对位置。

P13 在这个过程中我发现,如果最开始没有对准位置,也不用紧张,可以在DATAV平台增加hook数据过滤器,解决地理数据与世界坐标无法对齐的问题。

2.线上场景还原
根据对确定过位置模型进行烘焙还原。这个过程中遇到了一些不知名的原因烘焙失败,原因可能是命名有中文/位置数据错误/模型块面复杂等,遇到这样的问题就需要重新从头检查烘焙流程每一步。

P14  3.数据维度展示还原
这一步我们需要把前期做好的数据可视化效果,还原到线上模型中。在这一步我遇到的问题是因建德医院内部具体结构的缺失,使一些可视化效果无法精准匹配到模型上。所以设计过程中只能依赖于在对的地理位置上丰富的场景内造数据,这个过程是比较吃力的。


P15 这个问题的解决办法是通过开发工具和导出的结构俯视图,对位置,然后转化出LEGO的数据



P16 在数据效果还原的过程中,也发现我在前期设计的数据效果,不能全部实现,有些是依赖于开发的 。这时可以通过其他组件效果代替尝试,比如热力的效果用粒子放大,通过参数调节得到热力 再比如局部房间的扫管,通过设计部分多次烘焙模型,不断叠加扫光层,得到房间监测的效果
P17 设计小结
综合以上的经验,院感可视化从设计到落地,整体结构应该是这样从物理基础坐标的获取、到场景搭建、再到数据展示的过程。在这个过程中会用到DATAV、C4D、数据库、简单的代码等技术来实现。

P18 这个项目虽然这只是医疗行业中一个小的业务场景,但我们的业务数据提取及可视化设计思路,他不仅限于医疗行业,同时也可以成为场馆类大屏解决方案的一部分,是具有一定商业化价值的。同时在这过程中沉淀下来的人体结构模型,和一些设计经验,是可以复用到对应行业解决方案中,达到提效。

转自:

2020年这些

seo达人

火车车次

/^[GCDZTSPKXLY1-9]\d{1,4}$/

手机机身码(IMEI)

/^\d{15,17}$/

必须带端口号的网址(或ip)

/^((ht|f)tps?:\/\/)?[\w-]+(\.[\w-]+)+:\d{1,5}\/?$/

网址(url,支持端口和"?+参数"和"#+参数)

/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?$/

统一社会信用代码

/^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/

迅雷链接

/^thunderx?:\/\/[a-zA-Z\d]+=$/

ed2k链接(宽松匹配)

/^ed2k:\/\/\|file\|.+\|\/$/

磁力链接(宽松匹配)

/^magnet:\?xt=urn:btih:[0-9a-fA-F]{40,}.*$/

子网掩码

/^(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(?:\.(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/

linux"隐藏文件"路径

/^\/(?:[^\/]+\/)*\.[^\/]*/

linux文件夹路径

/^\/(?:[^\/]+\/)*$/

linux文件路径

/^\/(?:[^\/]+\/)*[^\/]+$/

window"文件夹"路径

/^[a-zA-Z]:\\(?:\w+\\?)*$/

window下"文件"路径

/^[a-zA-Z]:\\(?:\w+\\)*\w+\.\w+$/

股票代码(A股)

/^(s[hz]|S[HZ])(000[\d]{3}|002[\d]{3}|300[\d]{3}|600[\d]{3}|60[\d]{4})$/

大于等于0, 小于等于150, 支持小数位出现5, 如145.5, 用于判断考卷分数

/^150$|^(?:\d|[1-9]\d|1[0-4]\d)(?:.5)?$/

html注释

/^<!--[\s\S]*?-->$/

md5格式(32位)

/^([a-f\d]{32}|[A-F\d]{32})$/

版本号(version)格式必须为X.Y.Z

/^\d+(?:\.\d+){2}$/

视频(video)链接地址(视频格式可按需增删)

/^https?:\/\/(.+\/)+.+(\.(swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|mp4))$/i

图片(image)链接地址(图片格式可按需增删)

/^https?:\/\/(.+\/)+.+(\.(gif|png|jpg|jpeg|webp|svg|psd|bmp|tif))$/i

24小时制时间(HH:mm:ss)

/^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/

12小时制时间(hh:mm:ss)

/^(?:1[0-2]|0?[1-9]):[0-5]\d:[0-5]\d$/

base64格式

/^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*?)\s*$/i

数字/货币金额(支持负数、千分位分隔符)

/^-?\d+(,\d{3})*(\.\d{1,2})?$/

数字/货币金额 (只支持正数、不支持校验千分位分隔符)

/(?:^[1-9]([0-9]+)?(?:\.[0-9]{1,2})?$)|(?:^(?:0){1}$)|(?:^[0-9]\.[0-9](?:[0-9])?$)/

银行卡号(10到30位, 覆盖对公/私账户, 参考微信支付)

/^[1-9]\d{9,29}$/

中文姓名

/^(?:[\u4e00-\u9fa5·]{2,16})$/

英文姓名

/(^[a-zA-Z]{1}[a-zA-Z\s]{0,20}[a-zA-Z]{1}$)/

车牌号(新能源)

/[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}(([0-9]{5}[DF])|([DF][A-HJ-NP-Z0-9][0-9]{4}))$/

车牌号(非新能源)

/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/

车牌号(新能源+非新能源)

/^(?:[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}(?:(?:[0-9]{5}[DF])|(?:[DF](?:[A-HJ-NP-Z0-9])[0-9]{4})))|(?:[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9 挂学警港澳]{1})$/

手机号(mobile phone)中国(严谨), 根据工信部2019年公布的手机号段

/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/

手机号(mobile phone)中国(宽松), 只要是13,14,15,16,17,18,19开头即可

/^(?:(?:\+|00)86)?1[3-9]\d{9}$/

手机号(mobile phone)中国(最宽松), 只要是1开头即可, 如果你的手机号是用来接收短信, 优先建议选择这一条

/^(?:(?:\+|00)86)?1\d{10}$/

date(日期)

/^\d{4}(-)(1[0-2]|0?\d)\1([0-2]\d|\d|30|31)$/

email(邮箱)

/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/

座机(tel phone)电话(国内),如: 0341-86091234

/^\d{3}-\d{8}$|^\d{4}-\d{7}$/

身份证号(1代,15位数字)

/^[1-9]\d{7}(?:0\d|10|11|12)(?:0[1-9]|[1-2][\d]|30|31)\d{3}$/

身份证号(2代,18位数字),最后一位是校验位,可能为数字或字符X

/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0\d|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/

身份证号, 支持1/2代(15位/18位数字)

/(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/

护照(包含香港、澳门)

/(^[EeKkGgDdSsPpHh]\d{8}$)|(^(([Ee][a-fA-F])|([DdSsPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))\d{7}$)/

帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线组合

/^[a-zA-Z]\w{4,15}$/

中文/汉字

/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/

小数

/^\d+\.\d+$/

数字

/^\d{1,}$/

html标签(宽松匹配)

/<(\w+)[^>]*>(.*?<\/\1>)?/

qq号格式正确

/^[1-9][0-9]{4,10}$/

数字和字母组成

/^[A-Za-z0-9]+$/

英文字母

/^[a-zA-Z]+$/

小写英文字母组成

/^[a-z]+$/

大写英文字母

/^[A-Z]+$/

密码强度校验,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符

/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/

用户名校验,4到16位(字母,数字,下划线,减号)

/^[a-zA-Z0-9_-]{4,16}$/

ip-v4

/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/

ip-v6

/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i

16进制颜色

/^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/

微信号(wx),6至20位,以字母开头,字母,数字,减号,下划线

/^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/

邮政编码(中国)

/^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\d{4}$/

中文和数字

/^((?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])|(\d))+$/

不能包含字母

/^[^A-Za-z]*$/

java包名

/^([a-zA-Z_][a-zA-Z0-9_]*)+([.][a-zA-Z_][a-zA-Z0-9_]*)+$/

mac地址

/^((([a-f0-9]{2}:){5})|(([a-f0-9]{2}-){5}))[a-f0-9]{2}$/i

vue-router 导航守卫中 next 控制实现

seo达人

使用 vue-router 的导航守卫钩子函数,某些钩子函数可以让开发者根据业务逻辑,控制是否进行下一步,或者进入到指定的路由。


例如,后台管理页面,会在进入路由前,进行必要登录、权限判断,来决定去往哪个路由,以下是伪代码:


// 全局导航守卫

router.beforEach((to, from, next) => {

 if('no login'){

   next('/login')

 }else if('admin') {

   next('/admin')

 }else {

   next()

 }

})


// 路由配置钩子函数

{

 path: '',

 component: component,

 beforeEnter: (to, from, next) => {

   next()

 }

}


// 组件中配置钩子函数

{

 template: '',

 beforeRouteEnter(to, from, next) {

   next()

 }

}

调用 next,意味着继续进行下面的流程;不调用,则直接终止,导致路由中设置的组件无法渲染,会出现页面一片空白的现象。


钩子函数有不同的作用,例如 beforEach,afterEach,beforeEnter,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave,针对这些注册的钩子函数,要依次进行执行,并且在必要环节有控制权决定是否继续进入到下一个钩子函数中。


以下分析下源码中实现的方式,而源码中处理的边界情况比较多,需要抓住核心点,去掉冗余代码,精简出便于理解的实现。


精简源码核心功能

总结下核心点:钩子函数注册的回调函数,能顺序执行,同时会将控制权交给开发者。


先来一个能够注册回调函数的类:


class VueRouter {

 constructor(){

   this.beforeHooks = []

   this.beforeEnterHooks = []


   this.afterHooks = []

 }


 beforEach(callback){

   return registerHook(this.beforeHooks, callback)

 }

 beforeEnter(callback){

   return registerHook(this.beforeEnterHooks, callback)

 }

 afterEach(callback){

   return registerHook(this.afterHooks, callback)

 }

}

function registerHook (list, fn) {

 list.push(fn)

 return () => {

   const i = list.indexOf(fn)

   if (i > -1) list.splice(i, 1)

 }

}

声明的类,提供了 beforEach 、beforeEnter 和 afterEach 来注册必要的回调函数。


抽象出一个 registerHook 公共方法,作用:


注册回调函数

返回的函数,可以取消注册的回调函数

使用一下:


const router = new VueRouter()


const beforEach = router.beforEach((to, from, next) => {

 console.log('beforEach');

 next()

})

// 取消注册的函数

beforEach()

以上的回调函数会被取消,意味着不会执行了。



router.beforEach((to, from, next) => {

 console.log('beforEach');

 next()

})


router.beforeEnter((to, from, next) => {

 console.log('beforeEnter');

 next()

})


router.afterEach(() => {

 console.log('afterEach');

})

以上注册的钩子函数会依次执行。beforEach 和 beforeEnter 的回调接收内部传来的参数,同时通过调用 next 可继续走下面的回调函数,如果不调用,则直接被终止了。

最后一个 afterEach 在上面的回调函数都执行后,才被执行,且不接收任何参数。


先来实现依次执行,这是最简单的方式,在类中增加 run 方法,手动调用:



class VueRouter {

 // ... 其他省略,增加 run 函数


 run(){

   // 把需要依次执行的回调存放在一个队列中

   let queue = [].concat(

     this.beforeHooks,

     this.afterHooks

   )

   

   for(let i = 0; i < queue.length; i++){

     if(queue(i)) {

       queue(i)('to', 'from', () => {})

     }

   }

 }

}


// 手动调用


router.run()

打印:


'beforEach'

'beforeEnter'

上面把要依次执行的回调函数聚合在一个队列中执行,并传入必要的参数,但这样开发者不能控制是否进行下一步,即便不执行 next 函数,依然会依次执行完队列的函数。


改进一下:


class VueRouter {

 // ... 其他省略,增加 run 函数


 run(){

   // 把需要依次执行的回调存放在一个队列中

   let queue = [].concat(

     this.beforeHooks,

     this.afterHooks

   )

   queue[0]('to', 'from', () => {

     queue[1]('to', 'from', () => {

      console.log('调用结束');

     })

   })

 }

}


router.beforEach((to, from, next) => {

 console.log('beforEach');

 // next()

})


router.beforeEnter((to, from, next) => {

 console.log('beforeEnter');

 next()

})

传入的 next 函数会有调用下一个回调函数的行为,把控制权交给了开发者,调用了 next 函数会继续执行下一个回调函数;不调用 next 函数,则终止了队列的执行,所以打印结果是:


'beforEach'

上面实现有个弊端,代码不够灵活,手动一个个调用,在真实场景中无法确定注册了多少个回调函数,所以需要继续抽象成一个功能更强的方法:


function runQueue (queue, fn, cb) {

 const step = index => {

   // 队列执行结束了

   if (index >= queue.length) {

     cb()

   } else {

     // 队列有值

     if (queue[index]) {

       // 传入队列中回调,做一些必要的操作,第二个参数是为了进行下一个回调函数

       fn(queue[index], () => {

         step(index + 1)

       })

     } else {

       step(index + 1)

     }

   }

 }

 // 初次调用,从第一个开始

 step(0)

}

runQueue 就是执行队列的通用方法。


第一个参数为回调函数队列, 会依次取出来;

第二个参数是函数,它接受队列中的函数,进行一些其他处理;并能进行下个回调函数的执行;

第三个参数是队列执行结束后调用。

知道了这个函数的含义,来使用一下:



class VueRouter {

 // ... 其他省略,增加 run 函数


 run(){

   // 把需要依次执行的回调存放在一个队列中

   let queue = [].concat(

     this.beforeHooks,

     this.beforeEnterHooks

   )


   // 接收回到函数,和进行下一个的执行函数

   const iterator = (hook, next) => {

     // 传给回调函数的参数,第三个参数是函数,交给开发者调用,调用后进行下一个

     hook('to', 'from', () => {

       console.log('执行下一个回调时,处理一些相关信息');

       next()

     })

   }


   runQueue(queue, iterator, () => {


     console.log('执行结束');

     // 执行 afterEach 中的回调函数

     this.afterHooks.forEach((fn) => {

       fn()

     })

   })

 }

}

// 注册

router.beforEach((to, from, next) => {

 console.log('beforEach');

 next()

})


router.beforeEnter((to, from, next) => {

 console.log('beforeEnter');

 next()

})


router.afterEach(() => {

 console.log('afterEach');

})


router.run();

从上面代码可以看出来,每次把队列 queue 中的回调函数传给 iterator , 用 hook 接收,并调用。

传给 hook 必要的参数,尤其是第三个参数,开发者在注册的回调函数中调用,来控制进行下一步。

在队列执行完毕后,依次执行 afterHooks 的回调函数,不传入任何参数。


所以打印结果为:


beforEach

执行下一个回调时,处理一些相关信息

beforeEnter

执行下一个回调时,处理一些相关信息

执行结束

afterEach

以上实现的非常巧妙,再看 Vue-router 源码这块的实现方式,相信你会豁然开朗。

小白学VUE——快速入门

前端达人

文章目录

小白学VUE——快速入门

前言:什么是VUE?

环境准备:

vue的js文件

vscode

Vue入门程序

抽取代码片段

vue标准语法:

什么是vue指令?

v-bind指令

事件单向绑定

v-model:事件双向绑定

v-on事件监听指令

v: on:submit.prevent指令

v-if 判断指令

v-for 循环渲染指令

前言:什么是VUE?

Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架。 Vue 只关注视图层, 采用自底向上增量开发的设计。 Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

点击查看原图

环境准备:
vue的js文件
使用CDN外部导入方法
以下推荐国外比较稳定的两个 CDN,把这些网址放进script标签的src属性下即可,国内还没发现哪一家比较好,目前还是建议下载到本地。

Staticfile CDN(国内) : https://cdn.staticfile.org/vue/2.2.2/vue.min.js
unpkg:https://unpkg.com/vue/dist/vue.js, 会保持和 npm 发布的的版本一致。
cdnjs : https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js
2.VSCODE软件
(2).使用内部导入方法(自行下载js文件放进工作区js文件夹即可)

2.png

vscode

前往vscode官网下载对应版本的vscode

点击查看原图

Vue入门程序
首先了解一下什么是插值
插值:数据绑定最常见的形式就是使用 **{{…}}(双大括号)**的文本插值:

单独抽出这段来看一下:
Vue即是vue内置的对象,el(element)指的是绑定的元素,可以用#id绑定元素,data指的是定义页面中显示的模型数据,还有未展示的methods,指的是方法

var app = new Vue({
            el: "#app",//绑定VUE作用的范围
            data: {//定义页面中显示的模型数据
                message: 'hello vue'
            }
 });

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>


    <script src="js/vue.min.js"></script>

</head>
<body>
    

    <!-- 插值表达式 获取data里面定义的值 {{message}} -->
    <div id="app">{{ message }}</div>

    <script>
        //创建一个VUE对象
        var app = new Vue({
            el: "#app",//绑定VUE作用的范围
            data: {//定义页面中显示的模型数据
                message: 'hello vue'
            }
        });

    </script>

</body>
</html>

抽取代码片段

步骤:文件-首选项-用户片段
输入片段名称回车

复制以下片段覆盖之前的注释内容

{
"vh": {
"prefix": "vh", // 触发的关键字 输入vh按下tab键
"body": [
"<!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>",
"    <script src=\"js/vue.min.js\"></script>",
"</head>",
"",
"<body>",
"    <div id=\"app\"></div>",
"    <script>",
"        var vm=new Vue({",
"           el:'#app',",
"           data:{},",
"           methods:{}",
"        });",
"    </script>",
"</body>",
"",
"</html>",
],
"description": "vh components"
}
}

此时,新建一个html文件,输入vh在按下tab键即可快速填充内容

vue标准语法:
什么是vue指令?
在vue中提供了一些对于页面 + 数据的更为方便的输出,这些操作就叫做指令, 以v-xxx表示
类似于html页面中的属性 `

比如在angular中 以ng-xxx开头的就叫做指令
在vue中 以v-xxx开头的就叫做指令
指令中封装了一些DOM行为, 结合属性作为一个暗号, 暗号有对应的值,根据不同的值,框架会进行相关DOM操作的绑定

下面简单介绍一下vue的几个基础指令: v-bind v-if v-for v-on等

v-bind指令
作用:

给元素的属性赋值
可以给已经存在的属性赋值 input value
也可以给自定义属性赋值 mydata
语法
在元素上 v-bind:属性名="常量||变量名"
简写形式 :属性名="变量名"
例:
<div v-bind:原属性名="变量"></div> <div :属性名="变量"></div>

事件单向绑定

事件单向绑定,可以用 v-bind:属性名="常量||变量名,绑定事件,用插值表达式取出值

<body>
    <div id="app">
    
        <h1 v-bind:title="message">
            {{content}}
        </h1>

        <!-- 简写方式 -->
        <h2 :title="content">{{message}}</h2>


    </div>
    <script>
        var vm=new Vue({
           el:'#app',
           data:{
               content: '我是标题',
               message: '页面加载于' + new Date().toDateString()
           }
           
        });
    </script>
</body>

效果:
20200511203222885.png


————————————————
版权声明:本文为CSDN博主「热爱旅行的小李同学」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_46275020/java/article/details/106055312


JavaScript 对象可以做到的三件事

seo达人

1. 访问内部属性

JavaScript 对象无法以常规方式访问的内部属性。内部属性名由双方括号[[]]包围,在创建对象时可用。


内部属性不能动态地添加到现有对象。


内部属性可以在某些内置 JavaScript 对象中使用,它们存储ECMAScript规范指定的内部状态。


有两种内部属性,一种操作对象的方法,另一种是存储数据的方法。例如:


[[Prototype]] — 对象的原型,可以为null或对象

[[Extensible]] — 表示是否允许在对象中动态添加新的属性

[[PrivateFieldValues]] — 用于管理私有类字段

2. 属性描述符对象

数据属性包含了一个数据值的位置,在这个位置可以读取和写入值。也就是说,数据属性可以通过 对象.属性 访问,就是我么平常接触的用户赋什么值,它们就返回什么,不会做额外的事情。


数据属性有4个描述其行为的特性(为了表示内部值,把属性放在两对方括号中),称为描述符对象。


属性 解释 默认值

[[Configurable]] 能否通过delete删除属性从而重新定义属性;

能否修改属性的特性;

能否把属性修改为访问器属性 true

[[Enumerable]] 能否通过for-in循环返回属性 true

[[Writable]] 能否修改属性的值 true

[[Value]] 包含这个属性的数据值 undefined

value 描述符是属性的数据值,例如,我们有以下对象 :


let foo = {

 a: 1

}

那么,a 的value属性描述符为1。


writable是指该属性的值是否可以更改。 默认值为true,表示属性是可写的。 但是,我们可以通过多种方式将其设置为不可写。


configurable 的意思是可以删除对象的属性还是可以更改其属性描述符。 默认值为true,这意味着它是可配置的。


enumerable 意味着它可以被for ... in循环遍历。 默认值为true,说明能通过for-in循环返回属性


将属性键添加到返回的数组之前,Object.keys方法还检查enumerable 描述符。 但是,Reflect.ownKeys方法不会检查此属性描述符,而是返回所有自己的属性键。


Prototype描述符有其他方法,get和set分别用于获取和设置值。


在创建新对象, 我们可以使用Object.defineProperty方法设置的描述符,如下所示:


let foo = {

 a: 1

}

Object.defineProperty(foo, 'b', {

 value: 2,

 writable: true,

 enumerable: true,

 configurable: true,

});

这样得到foo的新值是{a: 1, b: 2}。


我们还可以使用defineProperty更改现有属性的描述符。 例如:


let foo = {

 a: 1

}

Object.defineProperty(foo, 'a', {

 value: 2,

 writable: false,

 enumerable: true,

 configurable: true,

});

这样当我们尝试给 foo.a 赋值时,如:


foo.a = 2;

如果关闭了严格模式,浏览器将忽略,否则将抛出一个错误,因为我们将 writable 设置为 false, 表示该属性不可写。


我们还可以使用defineProperty将属性转换为getter,如下所示:


'use strict'

let foo = {

 a: 1

}


Object.defineProperty(foo, 'b', {

 get() {

   return 1;

 }

})

当我们这样写的时候:


foo.b = 2;

因为b属性是getter属性,所以当使用严格模式时,我们会得到一个错误:Getter 属性不能重新赋值。


3.无法分配继承的只读属性

继承的只读属性不能再赋值。这是有道理的,因为我们这样设置它,它是继承的,所以它应该传播到继承属性的对象。


我们可以使用Object.create创建一个从原型对象继承属性的对象,如下所示:


const proto = Object.defineProperties({}, {

 a: {

   value: 1,

   writable: false

 }

})


const foo = Object.create(proto)

在上面的代码中,我们将proto.a的 writable 描述符设置为false,因此我们无法为其分配其他值。


如果我们这样写:


foo.a = 2;

在严格模式下,我们会收到错误消息。


总结

我们可以用 JavaScript 对象做很多我们可能不知道的事情。


首先,某些 JavaScript 对象(例如内置浏览器对象)具有内部属性,这些属性由双方括号包围,它们具有内部状态,对象创建无法动态添加。


JavaScript对象属性还具有属性描述符,该属性描述符使我们可以控制其值以及可以设置它们的值,还是可以更改其属性描述符等。


我们可以使用defineProperty更改属性的属性描述符,它还用于添加新属性及其属性描述符。


最后,继承的只读属性保持只读状态,这是有道理的,因为它是从父原型对象继承而来的。

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档