首页

交互问答 | 产品设计的稀缺性 & 截图功能 & 分页与无限滚动加载差异

分享达人

通过交互思维,从常见功能模式梳理产品设计的底层逻辑。

通过读者提出的三个问题,我们从交互设计的角度来聊聊它们的底层逻辑。


稀缺性的正反价值


读者提问:

呆总,有什么心理学或经济学的概念是在产品设计上也被沿用的?


当然有,比如稀缺性。


让我们从你的生活小故事开始讲起。


在某个饿着肚子的下午,你走进一家超市,想要买桶泡面充饥,但是各式品牌与类型的泡面让你应接不暇,于是纠结起来,正犹豫不决中,一款所剩无几的泡面在茫茫商品中跳进了你的视野,不禁心中窃喜,“没错,就是它了!卖得这么好,味道一定不错~”。


回到家里,你一边吃着泡面一边刷着手机,突然看到了节假日商品促销的广告,于是打开了购物车,果然每件商品上都赫然标着今日满减的红字,让你蠢蠢欲动,之前嫌贵的商品突然在今天有了剁手的理由。不仅如此,其中你的最爱竟然显示库存紧张,如何能忍,赶紧结账下单,在付完款的最后一刻,你终于松了口气并开始暗自庆幸自己的英明决策。


这时,你的舍友突然推门而入,迫不及待地告诉了你她偶然得知的一个八卦消息,之前从未听闻的你感到激动不已,能够获得鲜见的信息让你感到无比地满足,两人就此事展开了激烈地讨论....


以上。


就像故事中描绘的那样,我们的生活就是由这些琐碎的片段重复构成,在无数个场景中,我们做着自觉或不自觉的思考和判断,它们影响着我们的感知,主导着我们的行为。其中,稀缺性就是我们估值的必要条件和决策的重要基础,那些热销的商品、限时的促销和不为人知的秘密唤起了我们内心的需求与渴望,催促着我们去行动。在设计产品功能时,产品人员也总是会刻意营造给用户一种稀缺的感知来突出其价值,以引导用户进行点击、浏览、购买等一系列行为。


稀缺的分类


总的来说,我们对稀缺性的利用大致体现在三个方面:时间、产品、信息。


时间的紧迫感。


这一点经常应用在电商产品中,比如特定节日内的商品限时优惠、抢购活动等等。对时间的限制能够传递给用户一种紧迫感,暗示他们尽快购买,从而减少他们的决策时间,达到促销的目的。

产品的缺失。


我们经常能在电商产品中看到商品库存的数量,特别是当库存较少时,能起到刺激用户的购买欲望。谷歌早前在发布线上邮箱的时候也充分利用了类似的手段,因为个人测试版的邮箱受技术的限制,不能为每个人开放足够大的储藏空间,于是他们采用了「邀请制」来推动这项服务,结果非常有效。


当你成为了其中一员,就能够再邀请 2-3 个朋友,这项服务的「供应不足」在推荐系统的支持下得到了病毒式的传播。类似的,知乎早期也利用了邀请的方式去帮助自己获得初期的忠实用户,产品的不易得性反而让人更看重它,间接提高了产品的价值,达到了意想不到的效果。


再比如部分商品「限购 2 件」,会让用户产生再买一件的念头 —— 其实原本就只想买一件。


信息的限制。


因为我们总是假定少的东西更具价值,所以对于审查和限制我们获得信息的动作尤为敏感,我们更渴望得到那些被禁的隐秘信息而不是唾手可得的信息。这种稀缺性所起的作用甚至比法律还要强,我们会本能地认为它更有说服力。这也是一些新奇罕见的消息更容易传播的原因之一,得到不常见的信息能够使人享受到额外资源的优越感。一些媒体恰恰利用了这一点,常通过夸张的新闻标题去抓住人们的注意,试图扩大其影响力。


或者一些特权(虚拟)商品,譬如 VIP。就是提供给用户部分信息,再告知用户 VIP 权限更高,能获得多有「价值」信息,诱导购买。


稀缺作用的原理


天然的,出于原始的不断追逐猎物、获取资源的本能,我们总是格外珍惜稀缺资源,并认为它们更具价值。稀缺性在一定程度上鼓动了我们内心贪婪的欲望,除此之外,我们还有「喜欢走捷径」的弱点,同时「厌恶失去」。


一方面,当事物很难被获取的时候,我们通过易得性进行快速的价值判断,遵循这样的方式我们总能地做出决定。

另一方面,我们讨厌失去选择的自由,所以常倾向于快速地决策来留住一些东西,甚至想要得更多。

虽然我们的这些本能,在帮助我们快速行动,但却不一定会做出最适合自己的选择。毕竟人的判断大多偏向于主观,一些用心不良的企业,可能会利用它让一些人做出错误的决定。


稀缺作用的条件


相比于一些错误所导致的稀缺,比如最近由于猪瘟造成的猪肉供应量的减少,在有限资源的竞争中由社会需求导致的稀缺能够发挥更强的效应,零售商早已充分洞悉了我们的这种倾向,所以常告诉我们产品正在热销当中,应尽快购买。这不仅是社会认同在发挥作用,即我们认为其他人认同的产品是好的,而且我们正参与到产品的竞争当中。

稀缺原则的应用焦点在于利用紧张的时间或者强调某些东西未来的不可得性,防止用户花很多的时间做出决策,推动用户马上做出对商家有利的决定。如果给人们这个世界上的所有时间去做出决定,他们要不花上所有的时间,要不根本不做决定。


通过应用稀缺性的技巧,可以从根本上迫使人们采取对策。人们在稀缺性原则的压力下反应更快,因为他们害怕永远失去机会。稀缺性使得他们优先做出决定,所以这些决定相比无限制的决策变得更重要,更紧急,更被需要。


但是仅仅告诉人们他们将要获得的利益是不够的,如果他们选择你的产品或者服务,你还需要指出其特别之处以及被放弃时的成本。

月活超10亿的微信,是如何做好用户体验的

雪涛

微信是一种生活方式。作为月活超 10 亿的国民级产品,它有着独特的设计之道。

同时,微信也是互联网界的一个异类,张小龙曾在微信公开课上回应道:「我们只是守住了做一个好产品的底线,居然就与众不同了」。

好产品自然是体验和价值至上。下面,我就为大家解读微信的用户体验设计。

二次确认的微创新

先从最简单的二次确认讲起。

微信针对首页消息和收藏列表的删除操作,做了二次确认的微创新。像同类 IM 聊天工具,如 iOS 短信、Facebook Messenger、飞聊等等,二次确认都是采用底部系统弹窗。这样做,从程序架构的角度来看兼容性和通用性更强。

而从体验设计的角度来看,则刚好相反,因为从第一次删除操作,到第二次确认系统弹窗。之间的目标距离太长,耗时也就变长了。根据菲茨定律(Fitts’ Law),获取目标的时间取决于目标的距离和大小。这意味着提升交互的效率,不仅需要减少距离,同时还要增加目标大小。

△ 收藏列表

回过头看来微信,就是这样设计的。二次确认是在第一次的基础上延展,距离几乎为 0,同时,目标按钮的宽度也增加了几倍,大大地提升了交互效率。

互动体验广告

其实,商业和用户体验往往是有冲突的。而微信广告很好的平衡了这一点。

通过丰富有趣的互动体验式创意,或画圆、或画方、或画爱心,吸引大家主动参与互动。

1. 开放首条评论

另外,首条评论功能让品牌像朋友一样与大家对话,利用明星效应,从而带动更多人参与评论,有效提升评论区活跃度和广告点击率。

△ 朋友圈刘雯广告

以刘雯发布的朋友圈广告为例,大表姐把款的 vivo X30 系列手机交到你手中,并在首条评论中邀请你帮她拍照。数十万用户积极回复刘雯,评论率高于历史均值 40 倍+,「你这么漂亮怎么拍都好看」、「天天给你拍」,大表姐的魅力折服了众多用户,有效提升了品牌的亲和力与好感度。

2. 打开仪式 · 最强震动级别

在交互方面,如果你是 iPhone 用户,可以体验到 Taptic Engine 线性震动马达,通常力度由轻到重分别是 Light、Medium、Heavy。在打开广告的那一刻,它给你的是最强震动级别,满满的仪式感!整个微信应该找不到第二个这样级别的震动了。

提供反馈信息

再举一个震动的例子,当你的好友拍摄了时刻视频后,可以看到 TA 的头像右上角多了一个蓝色的小圈圈,双击它就能看到好友的时刻视频了。

当然,你双击没有拍摄时刻视频的好友,TA 的头像会左右晃动,并且会有 Failure 的震动反馈,动画和震动节奏完美匹配,这个体验就像你解锁 iPhone 输错密码时的震动是一样的。

△ 没有时刻视频时的反馈

我们做产品设计时也一样,对于用户的操作,应当给予清晰明了的反馈,帮助用户更好地理解信息。

跨平台能力

微信的起步阶段是基于手机来做 App,不基于 PC 来做,PC 端只是辅助,而现在,它的跨平台能力也逐渐增强。

一周前,微信 PC 版「微信测试版 for Windows」发布了 2.9.0 的内测,同步了移动端的新功能,主要有两点:

支持打开小程序,也可以玩「贪吃蛇」「跳一跳」等小游戏了。

△ Windows 微信客户端

另外,此前的微信 PC 端只支持引用文字消息,也没有达到手机上引用消息的视觉效果。此次更新中,还新增了很多支持的应用类型,包括但不限于图片/视频、表情包、公众号链接、小程序、文件等。

如此看来,Mac 端的更新也不远了,可以期待一下。

语音实时翻译

最近的微信更新了,除了引入深色模式之外。值得一提的是,语音消息的交互体验得到了优化,上滑转文字更方便了。

此前的方式是按住说话,滑到转文字按钮,说完松开手指后,才把语音解析成文字。

现在交互则少了一步操作,达到了实时边说边转文字的功能。别小看这一步界面上的优化,它背后代表的是微信语音识别能力上的技术突破。

△ 语音实时转文字

锚点定位

微信有很多隐性和显性的锚点,隐性锚点就比如你打开的这篇文章,关闭后,再重新点进来,还是显示上次阅读的位置。

△ 订阅号列表

显性的锚点就比如上面这个:当你刷公众号列表时候,如果有新文章更新,标题栏会出现一个锚点按钮,点击它让你快速回到顶部,方便查看文章。

△ 朋友圈「跳到还没看的位置」

基于此,在新版微信朋友圈中,增加了一个「跳到还没看的位置」。很多信息流产品是往下刷内容,直到给你一个分界线提示:下面内容已经看过了。而微信这是一个逆向操作,我认为这个功能还是很有必要的,因为经常会有刷朋友圈刷到一半聊天退出去,当回来查看朋友圈时,需要重新拉到底部,费时费力。

自然的语音听筒播放

《在你身边,为你设计》一书中有提到语音听筒播放的优化。大家都知道,手机带有距离感应器,在感应到耳边贴近时,屏幕会关闭以节省电力,并且避免由于耳朵与屏幕的触碰导致的误操作。

微信在聊天界面中,也启用了距离感应,以实现手机贴近耳边时,自动将语音从扬声器切换到听筒进行播放,这样你可以用最自然的姿势来听语音,这是一个很好的体验。

但要完美地实现这个体验,就需要解决距离感应器的时延问题。播放语音时,如果你非常迅速地将手机移至耳边,这时候距离感应器并不会同样迅速地对这个动作产生反馈。大约在延迟了 300 毫秒后,感应器发出信号,微信将 iPhone 的屏幕关闭。而在这个时间内,你的耳廓极有可能已经触碰到了 iPhone 的屏幕上。触碰的位置大部分时候是左上角的返回按钮区域。于是很容易出现手机移至耳边,语音戛然而止。

△ 延时响应判断流程图

为了解决这个问题,微信设计了一个解决办法:在响应返回操作时,先等待 500 毫秒,这时候如果侦听到距离感应器有发出信号,则认为是贴耳的动作,此情况下不执行返回操作,如上图所示。而 500 毫秒的延时大部分时候你是不会感知到的。这一解决办法极大降低了贴耳时候的误操作。

总结

在微信的产品设计中,我们看到了交互的细微迭代和背后的技术突破,我们看到了商业创意与用户体验的平衡。给用户带来希望,让创造者体现价值,这就是微信的设计之道。

文章来源:优设    作者:洋爷

vue + vuex + koa2开发环境搭建及示例开发

seo达人

写在前面

这篇文章的主要目的是学会使用koa框架搭建web服务,从而提供一些后端接口,供前端调用。
搭建这个环境的目的是: 前端工程师在跟后台工程师商定了接口但还未联调之前,涉及到向后端请求数据的功能能够走前端工程师自己搭建的http路径,而不是直接在前端写几个死数据。即,模拟后端接口。

当然在这整个过程(搭建环境 + 开发示例demo)中,涉及到以下几点知识点。
包括:

  • koa2的知识点
  • node的知识点
  • 跨域问题
  • fetch的使用
  • axios的使用
  • promise的涉及
  • vuex -> state、mutations、actions的使用

第一部分:环境搭建

vue + vuex环境

首先是vue + vue-router + vuex的环境。我们用vue-cli脚手架生成项目,会用vue的同学对这块应该很熟了。

// 全局安装脚手架工具 npm i vue-cli -g // 验证脚手架工具安装成功与否 vue --version // 构建项目 vue init webpack 项目名 // 测试vue项目是否运行成功 npm run dev

因为脚手架生成的vue项目不包含vuex,所以再安装vuex。

// 安装vuex npm i vuex --save

koa2环境

前端项目构建好了,就开始构建我们的后端服务。

首先在你的开发工具(不管是webstorm还是sublime)里新建一个目录,用来搭建基于koa的web服务。

在这里,我们不妨给这个目录起名为koa-demo。

然后执行:

// 进入目录 cd koa-demo // 生成package.json npm init -y // 安装以下依赖项 npm i koa npm i koa-router npm i koa-cors

安装好koa和两个中间件,环境就算搭建完成了。

第二部分:示例开发

搭建环境是为了使用,所以我们立马来写一个demo出来。
demo开发既是一个练习如何在开发环境中写代码的过程,反过来,也是一个验证环境搭建的对不对、好不好用的过程。

后端接口开发

本例中,后端我们只提供一个服务,就是给前端提供一个返回json数据的接口。代码中包含注释,所以直接上代码。

server.js文件

 // server.js文件 let Koa = require('koa'); let Router = require('koa-router'); let cors = require('koa-cors'); // 引入modejs的文件系统API let fs = require('fs'); const app = new Koa(); const router = new Router(); // 提供一个/getJson接口 router
    .get('/getJson', async ctx => { // 后端允许cors跨域请求 await cors(); // 返回给前端的数据 ctx.body = JSON.parse(fs.readFileSync( './static/material.json'));

    }); // 将koa和两个中间件连起来 app.use(router.routes()).use(router.allowedMethods()); // 监听3000端口 app.listen(3000);

这里面用到了一个json文件,在'./static/material.json'路径,该json文件的代码是:

// material.json文件 [{ "id": 1, "date": "2016-05-02", "name": "张三", "address": "北京 清华大学",
}, { "id": 2, "date": "2016-05-04", "name": "李四", "address": "上海 复旦大学",
}, { "id": 3, "date": "2016-05-01", "name": "王五", "address": "广东 中山大学",
}, { "id": 4, "date": "2016-05-03", "name": "赵六", "address": "广东 深圳大学",
}, { "id": 5, "date": "2016-05-05", "name": "韩梅梅", "address": "四川 四川大学",
}, { "id": 6, "date": "2016-05-11", "name": "刘小律", "address": "湖南 中南大学",
}, { "id": 7, "date": "2016-04-13", "name": "曾坦", "address": "江苏 南京大学",
}] 

然后我们是用以下命令将服务启动

node server.js

测试接口是否良好

打开浏览器,输入http://127.0.0.1:3000/getJson。看一看页面上是否将json文件中的json数据显示出来,如果能够显示出来,则说明这个提供json数据的服务,我们已经搭建好了。

前端调用后端接口示例

为突出重点,排除干扰,方便理解。我们的前端就写一个组件,组件有两部分:首先是一个按钮,用来调用web服务的getJson接口;然后是一个内容展示区域,拿到后端返回的数据以后,将其在组件的这块区域显示出来

首先我们看组件文件

<template> <div class="test"> <button type="button" @click="getJson">从后端取json</button> <div class="showJson">{{json}}</div> </div> </template> <script> import {store} from '../vuex' export default { computed: {
          json(){ return store.state.json;
          }
        }, methods: {
          getJson(){
              store.dispatch("getJson");
          }
        }
    } </script> <style scoped> .showJson{ width:500px; margin:10px auto; min-height:500px; background-color: palegreen;
  } </style> 

非常简单,就不多解释了。
然后看我们的vuex文件

import Vue from 'vue' import Vuex from 'vuex';

Vue.use(Vuex) const state = { json: [],
}; const mutations = {
  setJson(state, db){
    state.json = db;
  }
} const actions = {
  getJson(context){ // 调用我们的后端getJson接口 fetch('http://127.0.0.1:3000/json', { method: 'GET', // mode:'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json',
      },
    }).then(function (res) { if(res.status === 200){ return res.json()
      }
    }).then(function (json) { //console.log(typeof Array.from(json), Array.from(json)); context.commit('setJson', Array.from(json));
    })
  }
}; export const store = new Vuex.Store({ state: state, mutations: mutations, actions: actions,
})

ok, 代码撸完了,获取后端数据之前是这样的。

获取后端数据之后是这样的。

说说axios

想要把本demo的fetch改为axios方式,要做的工作有以下几处:
1、安装axios、在vuex文件引用axios

npm i axios import axios from 'axios'

2、将fetch部分代码替换为:

const actions = {
  getJson(context){
    axios.get('/json', { method: 'GET', // mode:'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json',
      },
    }).then(function (res) { if(res.status === 200){ return res.data
      }
    }).then(function (json) { //console.log(typeof Array.from(json), Array.from(json)); context.commit('setJson', Array.from(json));
    })
  }
};

3、又会遇到跨域,在webpack中修改,路径config/index.js文件中添加proxyTable项的配置:

proxyTable: { '/json': { target: 'http://127.0.0.1:3000', changeOrigin: true, pathRewrite: { '^/json': '/json' }
      }
    },

后记

基于vue脚手架搭建的项目,模拟异步取数据,也可以直接在脚手架生成的static文件夹下放置数据,假装是后台拿过来的数据。

不过搭建一个基于express或者koa的web服务,确实也该是一个前端工程师应该掌握的。

OK,以上就是全文了。
如果这篇文章使你有所收获,不胜荣幸。
欢迎点赞,以期能帮助更多同学!

GitHub如何配置SSH Key

前端达人

文章目录

    • 步骤


    • https://github.com/xiangshuo1992/preload.git
      git@github.com:xiangshuo1992/preload.git
      这两个地址展示的是同一个项目,但是这两个地址之间有什么联系呢?
      前者是https url 直接有效网址打开,但是用户每次通过git提交的时候都要输入用户名和密码,有没有简单的一点的办法,一次配置,永久使用呢?当然,所以有了第二种地址,也就是SSH URL,那如何配置就是本文要分享的内容。
      GitHub配置SSH Key的目的是为了帮助我们在通过git提交代码是,不需要繁琐的验证过程,简化操作流程。
      
      步骤
              

      一、设置git的user name和email

      如果你是第一次使用,或者还没有配置过的话需要操作一下命令,自行替换相应字段。
      git config --global user.name "Luke.Deng"
      git config --global user.email  "xiangshuo1992@gmail.com"
            
              

      二、检查是否存在SSH Key

      cd ~/.ssh
      ls
      或者
      ll
      //看是否存在 id_rsa 和 id_rsa.pub文件,如果存在,说明已经有SSH Key
      如果没有SSH Key,则需要先生成一下
      
      
      ssh-keygen -t rsa -C "xiangshuo1992@gmail.com"
            
              

      三、获取SSH Key

      cat id_rsa.pub
      //拷贝秘钥 ssh-rsa开头
            
              

      四、GitHub添加SSH Key

      GitHub点击用户头像,选择setting
       
      新建一个SSH Key 
      取个名字,把之前拷贝的秘钥复制进去,添加就好啦。
            
              

      五、验证和修改

      测试是否成功配置SSH Key
      
      
      ssh -T git@github.com
      //运行结果出现类似如下
      Hi xiangshuo1992! You've successfully authenticated, but GitHub does not provide shell access.
      之前已经是https的链接,现在想要用SSH提交怎么办?
      直接修改项目目录下 .git文件夹下的config文件,将地址修改一下就好了。
            
              


      ———————————————— 版权声明:本文为CSDN博主「前端向朔」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u013778905/java/article/details/83501204



深入理解vue中的slot与slot-scope

seo达人

写在前面

vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项使用频率、使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧,回头再学,反正已经可以写基础组件了”,于是就关闭了vue说明文档。

实际上,插槽的概念很简单,下面通过分三部分来讲。这个部分也是按照vue说明文档的顺序来写的。

进入三部分之前,先让还没接触过插槽的同学对什么是插槽有一个简单的概念:插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。 实际上,一个slot最核心的两个问题这里就点出来了,是显示不显示怎样显示

由于插槽是一块模板,所以,对于任何一个组件,从模板种类的角度来分,其实都可以分为非插槽模板插槽模板两大类。
非插槽模板指的是html模板,指的是‘div、span、ul、table’这些,非插槽模板的显示与隐藏以及怎样显示由插件自身控制;插槽模板是slot,它是一个空壳子,因为它显示与隐藏以及最后用什么样的html模板显示由父组件控制。但是插槽显示的位置确由子组件自身决定,slot写在组件template的哪块,父组件传过来的模板将来就显示在哪块

单个插槽 | 默认插槽 | 匿名插槽

首先是单个插槽,单个插槽是vue的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽。因为它不用设置name属性。

单个插槽可以放置在组件的任意位置,但是就像它的名字一样,一个组件中只能有一个该类插槽。相对应的,具名插槽就可以有很多个,只要名字(name属性)不同就可以了。

下面通过一个例子来展示。

父组件:


    
  1. <template>
  2. <div class="father">
  3. <h3>这里是父组件</h3>
  4. <child>
  5. <div class="tmpl">
  6. <span>菜单1</span>
  7. <span>菜单2</span>
  8. <span>菜单3</span>
  9. <span>菜单4</span>
  10. <span>菜单5</span>
  11. <span>菜单6</span>
  12. </div>
  13. </child>
  14. </div>
  15. </template>

子组件:


    
  1. <template>
  2. <div class="child">
  3. <h3>这里是子组件</h3>
  4. <slot></slot>
  5. </div>
  6. </template>

在这个例子里,因为父组件在<child></child>里面写了html模板,那么子组件的匿名插槽这块模板就是下面这样。也就是说,子组件的匿名插槽被使用了,是被下面这块模板使用了。


    
  1. <div class="tmpl">
  2. <span>菜单1</span>
  3. <span>菜单2</span>
  4. <span>菜单3</span>
  5. <span>菜单4</span>
  6. <span>菜单5</span>
  7. <span>菜单6</span>
  8. </div>

最终的渲染结果如图所示:



    
  1. 注:所有demo都加了样式,以方便观察。其中,父组件以灰色背景填充,子组件都以浅蓝色填充。

具名插槽

匿名插槽没有name属性,所以是匿名插槽,那么,插槽加了name属性,就变成了具名插槽。具名插槽可以在一个组件中出现N次。出现在不同的位置。下面的例子,就是一个有两个具名插槽单个插槽的组件,这三个插槽被父组件用同一套css样式显示了出来,不同的是内容上略有区别。

父组件:


    
  1. <template>
  2. <div class="father">
  3. <h3>这里是父组件</h3>
  4. <child>
  5. <div class="tmpl" slot="up">
  6. <span>菜单1</span>
  7. <span>菜单2</span>
  8. <span>菜单3</span>
  9. <span>菜单4</span>
  10. <span>菜单5</span>
  11. <span>菜单6</span>
  12. </div>
  13. <div class="tmpl" slot="down">
  14. <span>菜单-1</span>
  15. <span>菜单-2</span>
  16. <span>菜单-3</span>
  17. <span>菜单-4</span>
  18. <span>菜单-5</span>
  19. <span>菜单-6</span>
  20. </div>
  21. <div class="tmpl">
  22. <span>菜单->1</span>
  23. <span>菜单->2</span>
  24. <span>菜单->3</span>
  25. <span>菜单->4</span>
  26. <span>菜单->5</span>
  27. <span>菜单->6</span>
  28. </div>
  29. </child>
  30. </div>
  31. </template>

子组件:


    
  1. <template>
  2. <div class="child">
  3. // 具名插槽
  4. <slot name="up"></slot>
  5. <h3>这里是子组件</h3>
  6. // 具名插槽
  7. <slot name="down"></slot>
  8. // 匿名插槽
  9. <slot></slot>
  10. </div>
  11. </template>

显示结果如图:


可以看到,父组件通过html模板上的slot属性关联具名插槽。没有slot属性的html模板默认关联匿名插槽。

作用域插槽 | 带数据的插槽

最后,就是我们的作用域插槽。这个稍微难理解一点。官方叫它作用域插槽,实际上,对比前面两种插槽,我们可以叫它带数据的插槽。什么意思呢,就是前面两种,都是在组件的template里面写


    
  1. 匿名插槽
  2. <slot></slot>
  3. 具名插槽
  4. <slot name="up"></slot>

但是作用域插槽要求,在slot上面绑定数据。也就是你得写成大概下面这个样子。


    
  1. <slot name="up" :data="data"></slot>
  2. export default {
  3. data: function(){
  4. return {
  5. data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
  6. }
  7. },
  8. }

我们前面说了,插槽最后显示不显示是看父组件有没有在child下面写模板,像下面那样。


    
  1. <child>
  2. html模板
  3. </child>

写了,插槽就总得在浏览器上显示点东西,东西就是html该有的模样,没写,插槽就是空壳子,啥都没有。
OK,我们说有html模板的情况,就是父组件会往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的html+css共同决定,但是这套样式里面的内容呢?

正因为作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:样式父组件说了算,但内容可以显示子组件插槽绑定的。

我们再来对比,作用域插槽和单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件是提供的模板要既包括样式由包括内容的,上面的例子中,你看到的文字,“菜单1”,“菜单2”都是父组件自己提供的内容;而作用域插槽,父组件只需要提供一套样式(在确实用作用域插槽绑定的数据的前提下)。

下面的例子,你就能看到,父组件提供了三种样式(分别是flex、ul、直接显示),都没有提供数据,数据使用的都是子组件插槽自己绑定的那个人名数组。

父组件:


    
  1. <template>
  2. <div class="father">
  3. <h3>这里是父组件</h3>
  4. <!--第一次使用:用flex展示数据-->
  5. <child>
  6. <template slot-scope="user">
  7. <div class="tmpl">
  8. <span v-for="item in user.data">{{item}}</span>
  9. </div>
  10. </template>
  11. </child>
  12. <!--第二次使用:用列表展示数据-->
  13. <child>
  14. <template slot-scope="user">
  15. <ul>
  16. <li v-for="item in user.data">{{item}}</li>
  17. </ul>
  18. </template>
  19. </child>
  20. <!--第三次使用:直接显示数据-->
  21. <child>
  22. <template slot-scope="user">
  23. {{user.data}}
  24. </template>
  25. </child>
  26. <!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
  27. <child>
  28. 我就是模板
  29. </child>
  30. </div>
  31. </template>

子组件:


    
  1. <template>
  2. <div class="child">
  3. <h3>这里是子组件</h3>
  4. // 作用域插槽
  5. <slot :data="data"></slot>
  6. </div>
  7. </template>
  8. export default {
  9. data: function(){
  10. return {
  11. data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
  12. }
  13. }
  14. }

结果如图所示:

github

以上三个demo就放在GitHub了,有需要的可以去取。使用非常方便,是基于vue-cli搭建工程。

https://github.com/cunzaizhuyi/vue-slot-demo

你真的了解重排和重绘吗?

前端达人

做过前端开发的小伙伴就算不是非常理解重排与重绘,但是肯定都听过这两个词。那为什么这两个东西这么重要?因为他与我们的页面性能息息相关,今天,我们就来好好研究一下这两个东西。



浏览器的渲染流程

在讲解重排和重绘之前,我们有必要说一下浏览器的渲染流程。下面是浏览器渲染过程中最关键的几个部分。如果想了解完整的浏览器渲染流程,推荐大家去阅读李兵老师的浏览器工作原理实践,需要付费阅读。后期我也会整理一下再出一篇博客详细介绍浏览器的渲染过程。


点击查看原图


JavaScript:一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations、Transitions 和 Web Animation API。
样式计算:此过程是根据匹配选择器(例如 .headline 或 .nav > .nav__item)计算出哪些元素应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。
布局:在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。
绘制:绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
合成:由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。
其中,重排和重绘影响的就是其中的布局和绘制过程。

什么是重排和重绘制
重排:当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高,元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。
重绘:完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。
简单来说,涉及元素的几何更新时,叫重排。而只涉及样式更新而不涉及几何更新时,叫重绘。对于两者来说,重排必定引起重绘,但是重绘并不一定引起重排。所以,当涉及重排时,浏览器会将上述的步骤再次执行一遍。当只涉及重绘时,浏览器会跳过Layout步骤,即:


点击查看原图


而如果既不需要重排,也不需要重绘,那么就是下面这样:


点击查看原图



浏览器会直接跳到合成阶段。显然,对于页面性能来说,不重排也不重绘 > 重绘 > 重排。

什么操作会引起重排和重绘
显然,触发重排的一般都是几何因素,这是比较好理解的:

页面第一次渲染 在页面发生首次渲染的时候,所有组件都要进行首次布局,这是开销最大的一次重排
浏览器窗口尺寸改变
元素位置和尺寸发生改变的时候
新增和删除可见元素
内容发生改变(文字数量或图片大小等等)
元素字体大小变化
还有其他一些操作也可能引发重排

查询某些属性或调用某些方法
offset(Top|Left|Width|Height)
scroll(Top|Left|Width|Height)
client(Top|Left|Width|Height)
getComputedStyle()
我们可能不太理解为什么这些操作也能引起重排,这里我先简单解释一下。因为现在的浏览器已经非常完善了,会自动帮我们做一些优化。当我们用js操作DOM的时候,浏览器并不是立马执行的,而是将操作存储在一个队列中。当达到一定数量或者经过一定时间以后浏览器再统一的去执行队列中的操作。那么回到我们刚才的问题,为什么查询这些属性也会导致重排?因为当你查询这些属性时,浏览器就会强制刷新队列,因为如果不立马执行队列中的操作,有可能得到的结果就是错误的。所以相当于你强制打断了浏览器的优化流程,引发了重排。下面我们通过一些小例子来进一步理解这段话:

首先我们来一个显然会引发重排的操作

<!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>
    #test {
      width: 100px;
      height: 100px;
      background-color: red;
      position: relative;
    }
  </style>
</head>
<body>
  <div id="test">

  </div>
  <button onclick="reflow()">click</button>
  <script>
    function reflow() {
      var div = document.querySelector("#test");
      div.style.left = '200px';
    }
  </script>
</body>
</html>



把时间轴往后拉,可以看到这几个过程,先简单介绍一些这些名词代表的含义:

Recalculate Style:这个过程就是生成CSSOM的过程
Layout:这就是布局阶段,即重排的过程
Update Layer Tree:这个阶段是更新层树的过程
Paint:该阶段是为每一层准备绘制列表的过程
Composite Layers:该阶段是利用绘制列表来生成相应图层的位图了,还涉及到合成线程和光栅化,performence面板中的Raster就是光栅化线程池 。
这里只做一个简单的介绍,对其中内容不太明白的同学可以参考李兵老师的文章或者在我的下一篇介绍浏览器渲染过程的文章中会详细解释。

那通过这个图我们可以看到,我们改变了div的left之后就触发了Layout,即重排的过程。下面我们仅改变div的背景颜色,给大家一个对比。


即不重排也不重绘
说完了重排和重绘,不要忘记我们最开始提到的,最的方式就是跳过重排和重绘阶段。你可能会想,什么情况下可以做到这一点?其实这就是我们平时说的GPU加速,具体是如何实现呢?在开发过程中,如果我们使用了某些属性,浏览器会帮助我们将使用了该属性的div提升到一个单独的合成层,而在后面的渲染中,提升到该层的div将跳过重排和重绘的操作,直接到合成阶段。在stack overflow上有问题提到了这块内容。我们翻译一下就是:
下面几个属性能让浏览器帮助我们将div提升到一个单独的合成层:

图层具有3D或透视变换CSS属性
使用加速视频解码的 video 元素
拥有 3D(WebGL) 上下文或者加速 2D 上下文的 canvas 元素
混合插件(Flash)
对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素
图层使用加速的CSS过滤器
层具有作为合成层的后代
图层具有较低z索引的同级元素,该同级元素具有合成层(换句话说,该层在合成层的顶部渲染)
css will-change属性
最后一点是我加上去的,同时根据文中的内容我们可以知道,css3硬件加速是浏览器的行为,所以在不同浏览器下可能会有不同的表现形式。下面我们用一个例子来理解一下。这是李兵老师在他的专栏中提出的一个例子,我拿过来借用一下,注意box中的will-change属性:

<html>

  <head>
      <title>观察will-change</title>
      <style>
          .box {
              will-change: transform, opacity;
              display: block;
              float: left;
              width: 40px;
              height: 40px;
              margin: 15px;
              padding: 10px;
              border: 1px solid rgb(136, 136, 136);
              background: rgb(187, 177, 37);
              border-radius: 30px;
              transition: border-radius 1s ease-out;
          }

          body {
              font-family: Arial;
          }
      </style>
  </head>

  <body>
      <div id="controls">
          <button id="start">start</button>
          <button id="stop">stop</button>
      </div>
      <div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
          <div class="box">旋转盒子</div>
      </div>
      <script>

          let boxes = document.querySelectorAll('.box');
          let boxes1 = document.querySelectorAll('.box1');
          let start = document.getElementById('start');
          let stop = document.getElementById('stop');
          let stop_flag = false

          start.addEventListener('click', function () {
              stop_flag = false
              requestAnimationFrame(render);
          })

          stop.addEventListener('click', function () {
              stop_flag = true
          })

          let rotate_ = 0
          let opacity_ = 0
          function render() {
              if (stop_flag)
                  return 0
              rotate_ = rotate_ + 6
              if (opacity_ > 1)
                  opacity_ = 0
              opacity_ = opacity_ + 0.01
              let command = 'rotate(' + rotate_ + 'deg)';
              for (let index = 0; index < boxes.length; index++) {
                  boxes[index].style.transform = command
                  boxes[index].style.opacity = opacity_
              }
              requestAnimationFrame(render);
          }
      </script>
  </body>

  </html>



————————————————
版权声明:本文为CSDN博主「溪宁」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38164763/article/details/105406580

【CSS基础学习】复杂选择器

前端达人

文章目录

    • CSS第二课-复杂选择器
      • 群组选择器
      • 属性选择器
      • 派生选择器
      • CSS第二课-复杂选择器

        群组选择器

        格式

        选择器1,选择器2,,,选择器n{声明的属性和属性值}

        p,h1{
            color: blue;
        }


        用于对于多个选择器进行样式修改,由简单选择器组合而成的选择器,可以是简单选择器中的任意组合,如上面代码例,就是修改了p标签和h1标签的字体颜色。

        属性选择器

        根据属性名查找元素

        格式

        元素[属性名]{
            声明的属性和属性值;
        }


        p[id]{
            color: blue;
        }


        前面添加元素的名字,然后后面加上属性名,比如上例,就是p标签,其中带有id的元素,然后把字体颜色设置为蓝色。

        根据属性值查找

        格式

        元素[属性名=属性值]{
            声明的属性和属性值;
        }


        p[class = 'p2']{
            color: blue;
        }


        和上面的根据属性名查找差不多,只不过更加了,到了属性名后面的属性值,上例就是作用于p标签,只不过条件是为带有class属性,并且属性值为p2的p标签。

        多属性选择器

        格式


        元素[属性名或属性表达式][属性名或属性表达式]..{
            声明的属性和属性值;
        }
        p[title][class]{
            color: blue;
        }



        元素后面加。属性名或属性表达式,可以加+∞个,但是没必要。上例为:设置title属性和class属性的段落p标签的样式

        根据属性值近似查找

        格式


        元素[元素名~=属性值]{
            声明的属性和属性值;
        }


        元素[属性名|=值]{
            声名的属性和属性值;
        }


        p[class~='red']{
            color: blue;
        }



        注意,这里是~=,为约等于,就是找满足符合约等于条件的标签,上例为:设置class属性的值,包含red属性名的标签

        根据标签查找

        格式


        元素名1~元素名2{
            声名的属性和属性值;
        }


        a~p{
            color: blue;
        }


        a标签后面的每一个p标签,都进行了样式的修改。

        派生选择器

        后代选择器

        格式


        父类标签 子类标签{ /*注意俩标签中间有空格*/
            声名的属性和属性值;
        }


        div strong{
            color: blue;
        }


        相邻兄弟选择器

        格式


        父标签+子标签{
            声名的属性和属性值;
        }


        #div1 + p{
            color: blue;
        }


        相邻兄弟选择器可选择紧接在另一个元素后的元素,且二者具有相同的父亲元素。注释:与子结合符一样,相邻兄弟结合符旁边可以有空白符。




        ————————————————
        版权声明:本文为CSDN博主「董小宇」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
        原文链接:https://blog.csdn.net/lolly1023/article/details/105413125

B端系统设计全方位指南

雪涛

什么是B端产品?

B 端产品也叫 2B(to Business)产品,使用对象一般为企业客户或组织。B 端产品帮助企业或组织通过协同办公,解决某类管理问题,承担着为企业或组织提升效率、降低成本、控制风险从而提高企业收入,减少企业内部损耗的重要职责。B 端产品的工作是合理实现企业需求,提高产品核心竞争力,并提升市场价值。在国内互联网语境中,B 端产品的狭义解释也基本可以和面向企业的 「网页程序」 等同,用更接地气的称呼可以叫「管理平台」、「管理端」 。

B 端产品大致分为两类,一种是支撑前台产品的,一种是管理各种资源的,前者就是我们熟悉的后台产品,后者就是给各个企业服务,提高各个企业工作效率的 B 端产品。

1. 支撑前台产品的:

C 端产品线的后台产品,比如我们常用的淘宝、微信、饿了么、美团这种 C 端产品,他都需要有个后台进行各种前端信息的管理。

平台级工具产品,比如微信公众号、头条号等对用户和文章的数据实时统计,可编辑文章,回复消息等

2. 管理各种资源的:

  • OA 办公系统(OA 系统是通过程序的形式使办公流程自动化的解决方案)
  • CRM 系统 (CRM 是企业专门用来管理客户的工具)
  • SaaS 系统(SAAS 通常它指第三方提供给企业的线上软件服务,它既可以包含前面几种服务类型,也可以是一些更细化的需求。)
  • ERP 系统(ERP 是对企业所拥有、调动的资源进行统一管理的平台)
  • WMS 系统(WMS 是仓库管理系统的缩写,通过入库业务、出库业务、仓库调拨、库存调拨和虚仓管理等功能,综合批次管理、物料对应、库存盘点、质检管理、虚仓管理和即时库存管理等功能综合运用的管理系统)
  • TMS 系统(TMS 是运输管理系统能够对物流公司的所有车辆进行实时跟踪(结合 GPS 系统),保持信息流和物流的畅通。)
  • 呼叫中心客服系统
  • FMS 财务管理系统

B端和C端的区别

1. 从定义上:

  • B 端:To B 就是 To business,面向企业或者特定用户群体的企业级别产品;
  • C 端:To C 就是 To customer,产品面向普通大众消费者。

2. 从用户群体上:

  • B 端:产品一般是多种角色,有决策者、管理者和执行者,B 端往往是基于公司层面多人对某一问题解决方案进行整体评估。
  • C 端:用户群体较单一,或者是专注于某一领域群体,可根据性别,职业或行为偏好等关键属性进行分类。

3. 业务场景

  • B 端:业务场景多、逻辑复杂,根据每个人角色需要有不同的解决方案
  • C 端:业务场景较单一,或者专注于某个垂直的使用场景。

4. 用户的诉求:

  • B 端:控制成本、提率,注重安全和稳定
  • C 端:重视人性和用户体验

5. 盈利模式:

  • B 端:有明确的盈利模式和用户群体。
  • C 端:大部份 C 端产品都是使用免费,以此吸引用户使用,等积累到一定数量需要探索变现的路径,或者寻找其他变现的路径。

B端产品该如何设计?

了解了 B 端和 C 端的区别,B 端产品的实用性大于美观性,能切实解决问题、配置资源的 B 端产品才是一个好的 B 端产品。在设计上对于操作和展示内容无关的元素,越少越好。很多人刚开始接触 B 端,会热衷于以 C 端视觉标准进行设计,对于真实的使用体验来说显得过于冗余、炫技。那么在 B 端设计中该如何思考去设计呢?下面我以设计前、中、后的三个方向去阐述我在做 B 端设计中的一些思考:

1. 设计前:

需求分析

  • 分析产品的背景是什么,要做什么,要给用户怎样的视觉感受?他的竞品状况是怎样的进行一些分析(一般 b 端的产品竞品是极少的),了解一些行业内的资料。
  • 目标用户群有哪些?不同角色的用户有哪些具体的权限?(这里简要了解下大概的人群,没必要像 c 端产品那种那么明确)
  • 设计的产品主要解决用户或者业务上的哪些具体痛点,复现分析下使用场景,在需求分析阶段,要对产品本身和行业本身有一些基本的认知。

使用场景、硬件情况

  • 了解用户的使用场景,可以有助于我们复现分析用户是如何使用我们设计的界面的。
  • 用户的硬件情况,了解主流用户的屏幕分辨率是多少,根据主流分辨率做设计稿。现在 PC 主流的分辨率占比排名前三的是 1920*1080、1366*768、1440*900,以 1440 来设计的话,向上适配或者向下适配误差会比较小。这里也不是必需,如果用户全部都是用小屏笔记本办公,在 1920 设计稿上做适配可能小屏幕上展示会出现滚动条,会比较拥挤。

梳理交互流程:拿到需求后切勿打开花瓣站酷一阵撸,一定要对需求进行梳理(有的公司有专门的交互设计来做)做 B 端产品最重要的是对业务的理解,在梳理过程中一定要跟产品随时沟通,产品的业务流程是怎样的?有哪些同类型的产品?和他们相比我们的产品有什么特色和特点?充分理解了业务再去做设计就会有事半功倍的效果。

情绪版,定义风格:梳理完需求就可以定义设计风格了,在设计风格上尽量做到简洁,B 端产品实用性大于美观性,减少不必要的装饰元素给用户操作上带来干扰。这里可以运用情绪版去定义设计风格,大概的流程就是,根据产品业务背景定义设计关键词、根据关键词去找参考图片定义设计风格,产品的主题色也可以根据情绪版来去定义。

2. 设计中:

布局上根据导航可分为以下三种:

水平导航布局:类似于普通网页的布局样式导航,顺应了从上至下的正常浏览 顺序,方便浏览信息;顶部宽度限制了导航的数量和文本长度。适用于导航较少,页面篇幅较长的产品。

垂直导航布局:可将导航栏固定在左侧,提高导航可见性,方便页面之间切换;顶部可放置常用工具,如搜索条、帮助按钮、通知按钮等。菜单栏还可以展开收起,适用于结构简单的产品。

混合导航布局:结合了水平导航和垂直导航的特点,层级可以扩展到二、三级菜单,且能够更加清晰地区分常用业务功能操作和辅助操作。适用于功能模块较多、较复杂的工具类型后台。

不同形态的布局,适配方式也不同。在做设计之前了解下适配的原理,避免在不同设备上出现视觉上的误差。水平导航布局,在适配方案中做法是对两边留白区域进行最小值的定义,当留白区域到达限定值之后再对中间的主内容区域进行动态缩放。如图:

垂直导航布局和混合型导航布局,在适配方案中常见的做法是将左边的导航栏固定,对右边的工作区域进行动态缩放。如图:

最后说一下布局上的栅格,栅格会使整体页面更加工整简洁,内容区域采用 24 栅格(并非固定数值,参照案例 24 栅格布局),就不一一叙述栅格的定义与运用了,大家可以参考下网上有好多关于栅格的文章,如图:

颜色

颜色上大致分为品牌色(可以结合业务属性)、功能色(比如红黄绿蓝灯成功、出错、失败、提醒、链接等。功能色的选取需要遵循用户对色彩的基本认知)、 中性色(如深灰中灰浅灰,文字部分应用居多,此外背景、边框、分割线、等场景中也非常常见) 其他色如统计图、数据可视化、多个标签的不同配色方案根据项目情况单独设定。

字体

在 B 端系统中优先使用系统默认的界面字体,常用中文字体有微软雅黑、苹方、思源黑体,英文字体有 Arial、Helvetica 系统不同展示的字体也不相同。

字体大小常见的有 12px、13px、14px、16px、20px、24px、30px 等。

规范

一份好的规范能够让设计和开发更加的工作,并且能够指引一些设计的细节,通过对大小、颜色、边距等、呼吸感等一些细节点的标注,可以让新加入的设计师快速上手设计。一个项目会有多个设计师的参与,规范化的设计语言,避免因设计控件混乱而影响设计输出。好的设计规范还能统一在产品中不同页面的相同板块的样式问题,为开发组件库奠定基础。

如何建立一份规范呢?大概总结以下几点:

  • 首先是要梳理产品中不同板块所出现的场景进程收集(收集产品所有出现过的场景进行整理)
  • 其次是把收集完的板块归纳分类(不同的样式、状态等可以分成不同的种类)
  • 最后就可以定义规范了。

定义好设计规范能大大提高设计师的工作效率,在同一个项目中也能更好的把控项目的视觉规范,帮助设计师复用及设计延展。

组件

B 端产品的决策方是老板和管理层,在设计 B 端产品中往往也是大多数情况下会有接到比较紧急的需求的情况,所以在设计过程中就需要我们设计师更加去注重效率,提高产能。做东西时要有组件化思维,去总结分析页面模块中的一些共性,跟开发共同完成产品的组件库。

如果没有前端工程师,我们的设计组件库就是 PNG。所以,在开工前,一定要和那个技术不错的前端工程师聊聊做设计组件库的事情,其中最重要的是确定好:选择什么样的前端框架。

如果组件库服务的是 B 端或者中后台系统,那其实有很多可参考的开源组件框架:Ant Design、ElemetUI、Layui 等,注意不同的框架用到的前端技术不一样,兼容的浏览器版本也不一样,这要根据你们技术情况做选择。如果觉得开源框架的风格和你们的产品不统一,那就需要二次设计和开发封装。

下面是蚂蚁金服组件库,如图根据组件的功能特点做出了分类:通用、布局、导航、数据录入、数据展示、反馈、其他等。大家可以去官网查看,这里就不再一一做阐述了。

这个是饿了么的组件库:

推荐几个官方组件库:

做之前大家一定要去多去查看这些大厂做的已成型的开源组件库,然后再结合工作业务特色做出自己公司特有的定制化组件库。有了组件库之后,再接到紧急需求,我们就可以做到事半功倍的效果。先去分析页面的构成,然后花费 80% 的时间去设计需求中 20% 的页面,剩下的通用型页面就可以直接用组件库堆出来了。

3. 设计后:

设计走查

在完成设计后,要整体对设计页面进行走查。从信息层级、文字、图标、图片等方面进行多次设计验证与测试。

高质量设计交付

设计稿命名一定要清晰规范,现在好多切图标注的插件,一键生成切图标注。现在就没有必要单独整理切图标注了,但是设计稿在交付前切图的命名一定要在设计稿里整理清楚,规范命名方便开发查阅。

推荐几款比较常用的切图标注的插件:

设计追踪、校验

设计稿给到开发在设计过程中,要随时跟开发沟通。项目上线后要对线上效果进行实时 UI 校验,保证开发同学对设计稿的高度还原。还有就是对设计上线后的验证工作,用户使用和反馈是优化产品的重要途径,针对性地在一些主流的页面模块按钮进行一些数据的埋点,统计下用户的点击状况,实时对数据进行一些跟进,为下次页面改版优化,提供有力的数据支撑,提升产品的用户体验。

总结

不管 B 端还是 C 端,设计的价值在于通过视觉表现的方式去助力公司、助力产品实现用户的需求、帮助用户解决问题。B 端产品相对而言,场景、功能、业务流程、信息架构要比 C 端更复杂,面对的异常情况也比较多,所以 B 端在设计风格上尽量做到简洁,B 端产品实用性大于美观性,在每一个功能的设计都需要你去思考很多方面:用户易用、信息层级、未来扩展,你都要做出取舍,而对于每个模块都需要你思考、结合用户场景。所以想要做好 B 端设计,一定要去了解业务,了解用户需求。

文章来源:优设    作者:小六可视化设计

爱奇艺 VR 设计实战案例:界面文字篇

雪涛

本系列文章旨在由浅入深、由理论到实践地介绍 VR 设计。无论你在做哪个领域的设计,都能通过这个系列了解 VR 设计的特点。本文是第一集,深入分析 VR 界面的文字设计。


文章来源:优设    作者:爱奇艺XRD

爱奇艺 VR 设计实战案例:空间布局篇

雪涛

本系列文章旨在由浅入深、由理论到实践系统地介绍了本团队在近几年的设计工作中的一些经验总结和重点思考。本系列面向广大设计师,不论你目前在做什么领域/哪个端的设计,都可以了解到 VR 端和其他端的异同。希望给正在看文章的你带来收获。

团队介绍:爱奇艺 XRD——爱奇艺 VR/AR/XR 设计团队,目前主要负责爱奇艺 VR app 的设计工作(包括各个 VR 设备端和手机端)。

初步认识:空间里的界面

1. VR与其他端的区别

传统的数字终端(手机、电脑、pad 等),用户界面存在于二维的物理屏幕里,也就是在一个平面里。而屏幕和界面通常都是长方形的。

在 VR 中,有空间感的不仅仅是虚拟场景,用户界面也是布局在三维空间里的。「屏幕边界」的概念被打破了,设计师的画板不再是各类手机不同尺寸的屏幕,而是一个「无限的」空间。界面本身也可以是三维的、打破传统的,不再必须是一个个的长方形。

真正的 z 轴

这不同于在 2D 屏幕终端上,元素只拥有(x、y)坐标点的属性,而并没有一个 z 轴的概念。Z 轴,也就是深度概念,是通过设计师利用阴影、动效等视觉效果,「模拟」出来的前后关系。

在 VR 中看到的内容物(包括 UI 界面、场景、模型、视频资源等)有真实概念的前后关系,每个物件摆在一个具体的(x、y、z)坐标点上。物件拥有绝对位置,物件和物件之间有相对位置,物件和 camera(指用户观测点)之间有一个具体的距离。

角度和曲面

除了 z 轴坐标,因为 VR 界面存在在空间里,所以还拥有一个属性就是角度,这包含了在(x、y、z)三个轴上旋转的角度。每一个物件也可以不再是一个平面,而是曲面的、三维的。

角度和位置共同决定了,当用户看向某个物件时,这个物件的样子。

「有多大?」

一个物件在 VR 中「有多大」很难简单描述清楚。在传统端只需标注某个 UI 元素的「大小」「间距」,描述单位是像素。而在设计 VR 时。需要从多个维度定义一个元素:「大小」「(x、y、z)位置」「(x、y、z)角度」。同时,「有多大」还跟用户观测点的位置息息相关。

2. VR基本术语认知

在介绍下文理论之前,让我们先认识两个常见的 VR 术语:

FOV:Field of View,视场角

视场角的大小决定了光学仪器的视野范围。在 VR 中,视场角是 VR 硬件设备的一个属性,设备 FOV 是一个固定值。

在我们团队日常工作用语中,通常也用来指代用户的视角范围、界面元素的角度范围(如,「某面板水平 FOV 是 60°」)

DOF:Degree of Freedom,自由度

物体在空间内具有 6 个自由度,即沿 x、y、z 三个直角坐标轴方向的移动自由度和绕这三个坐标轴的转动自由度 。

  • 3DOF 的手柄/头显:只有绕 x、y、z 轴的 3 个转动自由度,没有移动自由度,共 3 个自由度。通俗地说,3DOF 手柄可以检测到手柄在手中转动,但检测不到手柄拿在右手还是左手。
  • 6DOF 的手柄/头显:同时有绕 x、y、z 轴的 3 个转动自由度,和三个轴方向的 3 个移动自由度,共 6 个自由度。通俗的说,是完全模拟真实物理世界的,可以看的手柄的实际位置和转动方向。

理论:人眼的视野与视角

虽然说 VR 是一个 360° 全景三维空间,但对于目前大多数 VR 的使用场景来说,可供设计师创作的区域其实已被大大缩小了。

目前国内市面常见的可移动的 VR 设备(非主机类),如小米 VR 一体机、Pico 小怪兽系列、奇遇 VR、新上市的华为 VR Glass,标配均为 VR 头显配合3DOF手柄的硬件模式。对应此类 VR 硬件模式,常见的用户使用场景为:「坐在椅子上+偶尔头转+身体不动」,好比「坐在沙发上看电视」。即使用户使用一个 6DOF 的 VR 硬件,支持空间定位可以在房间里走动,但对于爱奇艺 VR 这类观影类 app 来说,「坐在沙发上看电视」仍是主要的使用场景。

因此, 主要的操作界面还是需要放在「头部固定情况下的可见范围内 」。这样用户无需费劲转头,就可以看到主要内容、操作主要界面。

那么,什么是「头部固定情况下的可见范围 」呢?我们需要先学习一些人机工程学,来了解一些人眼 在不同情况下的可视范围。

1. 水平视野(x轴)

(此图的中心点 为用户观测点。)可以看出,

头部固定的情况下,双眼能看到的最大范围约为 124°。但还要考虑到一个限制,目前 VR 硬件设备的 FOV 并未达到这个值,通常在 90°~100°。而其中,能看清晰的范围只有眼前中心处的 60°。这相差的范围可以理解为「余光」,在人眼中成像是不清晰的,但仍在视野范围里。——这个范围极大程度上决定了每一个 UI 面板(包括其中的图片、文字等)适合占据的空间,也决定了 VR 中视频播放窗的最小和最大值。

头部转动但身体不动的情况下,脖子舒适转动能看到的范围约有 154°,脖子转动到能看到的范围约有 204°。一些次要内容可以放在这个区域,用户需要转动头部才能看到,但也不用太费力。

偏后方的区域范围,必须移动身体才能看到,因此只能放一些「没看见也没关系」的内容。比如环境、彩蛋等。

2. 垂直视野(y轴)

在放松状态下,头部与眼睛的夹角约为 105°。也就是说,当头部竖直向前时,眼睛会自然看向水平线下 15° 的位置。头部固定仅眼球转动时的最佳视野是(上)25° 和(下)30°。应将操作界面和主要观看内容放在此范围内。

垂直方向最大视野范围是(上)50° 和(下)70°。这个范围也是超过了 VR 硬件设备的 FOV。

另外,点头比抬头舒适。现实世界中,我们通常都是低头状态比抬头多,比如玩手机、看书或看笔记本电脑时。所以在 VR 中,比起偏上方区域,应考虑更多利用起偏下方的区域。

3. 深度感知(z轴)

(见本章图1)

0.5m 之内的物件,双眼很难对焦,因此不要出现任何物体。(这个值对于全景 3D 视频的拍摄 意义较大,应该尽量规避离镜头过近的物体出现)

有立体感的范围在 0.5m~10m,这里应该放置主要内容,尤其是可操作的内容。

10m~20m 之间,人眼仍能感觉出立体感,但有限。此范围仍适合放置可以体现沉浸感的 3D 场景/模型。

超过 20m 的距离,人眼很难看出立体感了,物体和物体的前后关系不再明显。因此适合放置「仅仅作为背景」存在的内容,也就是环境。(值得注意的是,因为反正也感知不出立体感,所以此范围的环境可以处理为全景球(即一个球面),来代替 3D 模型,这大大降低了功效。)

4. 视角和视距

在现实世界中,每个信息媒介都有一个预设好的观测距离。例如,握在手中的手机,距人眼大约 20cm。放在桌面上的电脑主机显示屏,距人眼大约60cm。客厅墙上的电视,距人眼大约 5m。在马路另一头的广告牌,距人眼可达几十米。

内容在预设好的观测距离上,对应预设好的大小。例如,手机上的正文文字只有几毫米大,而广告牌上的文字需要好几米大。

而在我们(观测者)看来,这些文字都阅读舒适。甚至其实看起来是「一样大」的。

这是因为它们拥有同样的视角——被视对象两端点到眼球中心的两条视线间的夹角。具体举例来说,在 1m 处看一个 10cm 的物体,和 在 2m 处看一个20cm 的物体,在 3m 处看一个 30cm 的物体,这 3 个物体「看起来是一样大的」,他们具有相同的视角。但我们仍然清楚地知晓谁近谁远,是因为眼睛对焦在不同的距离上,也就是视距不一样。

在 VR 中,不能再像移动端那样用「像素」来度量一个物件的大小,而是通过「视角」。而视角是由物件的「大小」、「位置」、「旋转角度」共同决定的。在「用户观测点不动」的使用场景下,「位置」实际上就是与观测点的「视距」。

界面的「旋转角度」应遵守一个原则——一个便于观看和操作的界面,应该尽量垂直于视线、面向用户观测点。也就是说,当你确定好一个界面的「位置」后,就自然会有一个对其来说的「最佳旋转角度」。

在 VR 中,一个物件的视角由其「大小」和「视距」两个变量影响。当确定了「最佳视角」、「最小可阅读视角」时,这就决定了「需要在不同距离上放置的信息内容,各自应该放多大」。

实践:爱奇艺VR app 的假面布局

有了理论基础后,接下来就是不断实践。

首先需要读者了解的是,我们团队设计的对象是爱奇艺 VR app——是在 VR 设备上的爱奇艺,是爱奇艺的第四个端。不仅包含爱奇艺全网 2D 内容,还拥有海量 VR 全景视频、3D 大片。选片和观影相关功能齐全。在体验上主要打造有沉浸感的 VR 影院观影,并突出 VR 特色内容。

本文章针对于 VR 一体机内的爱奇艺 VR app 设计展开讨论,但我们同时也支持手机端 app,若感兴趣可以在各大应用商店搜索下载。

我们学习的大量二手资料,给开展实际工作创造了基础。然而最终设计效果其实是在反复验证、试错后决定的。

当根据理论资料开始做实践,却发现实际效果差强人意时,我们的建议是——注重实践。原因之一是,目前 VR 界从技术到产品设计仍旧处在实验性阶段,远未达到移动端设计规范的成熟性,国内外的相关理论经验总结,都还没有绝对定论的程度。Oculus 的设计指南中,都是建议「实验,实验,再实验」。之二是,能搜集到的二手资料,不完全是建立在「人带着 VR 设备手握手柄」这种使用场景上,所以导致实际结果「不是那么回事」。

1. 模块化界面布局

基于「坐着不动+头部转动」的使用场景,和「自然视角偏下」、「低头比抬头舒适」的理论。

我们采取「从正视角出发,向左、右、下延伸」的布局思路。正视角放置当前界面的核心内容,左右两侧放置辅助信息内容;在必要时,可加入下部模块。此类模块化布局适用于所有界面,只是具体的面板尺寸、旋转角度可以有所不同。根据每个界面需要承载的内容,因地制宜地合理规划。

图为我们使用的几种常见 UI 布局。

2. 界面的FOV

基于人眼水平和垂直方向的视野范围的理论,同时参考主要适配的硬件设备属性(目前 VR 设备的 FOV 都小于人眼的视野范围),即:

  • 小米 VR 一体机:FOV≈90°(实际)
  • Pico 小怪兽 2:FOV=101°(官方数据)
  • 华为 VR Glass:FOV=90°(官方数据)

——我们决定了爱奇艺 VR 中主要界面的 FOV。

选片主界面 FOV (左-中-右 布局)

△ 「实际截图」爱奇艺 VR 选片主界面

水平方向上:

  • 中部面板:两边边界在「头部固定时清晰 FOV」内。
  • 两侧面板:近中心的边界均出现在「头部固定时可见 FOV」内,即在默认可见的视角范围内。两边边界在「头部轻微转动时清晰 FOV」内,即用户只需轻微左右转动头部便可查看全部内容。

垂直方向上:

  • 面板在「头部固定时清晰 FOV」内。用户无需上下转头。
  • 该页面可左右滑动,用户可以只关注中部的面板。

影厅播控页面 FOV(左-下-右布局)

在爱奇艺 VR 中,观看非全景的 2D/3D 视频,用户会置身于一个电影院影厅场景中。视频画面会出现在影厅屏幕上。

影厅播控页面(操控台页) 指播放视频时(包括影厅场景影片和全景影片)的操作界面。

△ 「实际截图」爱奇艺 VR 影厅播控页面

影厅播控页面采用「左-下-右」布局。包括 3 个功能区块:相关推荐(左)、操控台播控(下)、详情 & 选集(右)。该页面在视频屏幕(或全景视频)的前方,靠单击触摸板来呼出和关闭。

这 3 个面板的边界均在正视角「头部固定时可见 FOV」之内,也就是保证了,当用户注视影片本身并呼出该面板时,能看到所有面板。而用户仅需轻微转动头部,就可看到完整的面板。

视频播放窗的最大/最小值

视频播放窗 即「影厅屏幕」,被我们设定了屏幕大小可调的功能,以此来适应不同用户的观影习惯。屏幕大小可在指定范围内平滑调整。

屏幕最小值(50°):完整的屏幕范围都在「头部固定情况下清晰 FOV」。

屏幕最大值(76°):屏幕范围在「头部固定情况下可见 FOV」,即「充满视野」。此状态的观感类似 IMAX 影厅。

垂直方向上:距水平线偏上而不是偏下。这里其实和理论值发生了冲突。理论资料中,人眼最舒适的对焦点在「水平线向下 15°」。在老版本中,我们曾经将主 UI 和视频屏幕都按理论值摆在了偏下方,但实际效果并未令人感到舒适,反倒明显感知到「内容都偏下了」。这就是上文所说,「当理论导致实践差强人意」时,我们选择了不断实验,以实际效果为准。另外,本场景下方有影厅座椅等实际模型,并且还有操控台播控的 UI。综上,影厅屏幕被放在了水平偏上的位置。

3. 深度距离与层级(z轴)

根据前文理论提到的,z 轴距离的合适范围(0.5m~20m)比起水平和垂直 FOV 来说,数值范围要大得多,也就是说可试验的范围很大。因此在界面距离这一点上,我们进行了大量反复的实践——最终决定了当前版本中各级页面的深度层级和具体数值,如下图:

跟随式提示:

  • 适合于临时性的提示内容,几秒后自动消失。如 toast。
  • 与 camera(用户观测点)锁定,跟随 camera 移动。不可交互。
  • 保证让用户注意到,又不至于遮挡视线。

阻断式提示:

  • 适用于必须让用户看到且用户必须对其处理的提示。如弹窗。
  • 正视角区域固定显示,不跟随 camera 移动。有按钮可以交互。
  • 需要在其它操作界面之前。

辅助操作界面:

  • 适用于重操作的界面,而非重展示的界面。如播控按钮组、键盘,而非视频列表。
  • 通常在平视视线偏下的位置。(模拟「在手边」的位置)

减少眼球调焦,缩小距离差

值得注意的是,在我们的老版本中,不同层级的界面曾经被摆放的距离差距很大。如,toast 在 1.75m,主 UI 在 3m,而视频屏幕在 12m。之所以改动至上图数值,主要是为了达到「在看向不同层级界面时,尽量减少眼球调焦的程度」的目的。

眼球在看向不同距离上的内容时,需要对焦到不同的平面,距离差距越大,眼球需要调整得越大。如果频率高的话,容易导致(一部分)人的眼球疲劳。

Z轴上的获焦效果

VR 独有的「z 轴」,不仅允许了界面可以被放在不同距离上,还支持了利用 z 轴上的位移和旋转 来表达获焦效果。控件被获焦时,只需要在 z 轴上轻微的位移或轻微的角度旋转,便可体现出与众不同的有趣效果。

文章来源:优设    作者:爱奇艺XRD

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档