首页

为什么越简单的设计越说不出理由

鹤鹤

常见场景


设计师日常工作中,经常遇见的那些显而易见的设计逻辑,却难以给出设计理由的情况到底指的是什么呢?


以我自己近期的工作为例,不妨为大家举个简单的例子:


下图是一个补贴规则的设置流程,在用户未设置补贴规则之前,明确告知用户还未设置补贴规则,当用户设置了补贴规则后,可以对补贴规则进行修改。



我认为这是一个非常简单且合理的页面路径跳转,并且在很多现有的产品中都有类似这样的逻辑存在。然而在与上下游同学对接的过程中,却遇到了不同的意见:有人认为没有必要区分用户是否设置了规则,用户未设置规则可以直接展示系统默认的补贴条件和补贴范围,如果用户认为不合理,自行修改便是。

不知道大家看到这里是什么感觉,我在听到「方案二」时的反应是:能够理解却并不认同,说不出来哪里不对,就是觉得这事它不应该这样。


以上,就是在我工作中发生的一次非常具体的案例。


直觉真的不如推理靠谱吗?


那么为什么我会产生“感觉哪里不对,却又说不出为什么”的强烈感受呢?


最直接的原因是,这样的设计方案(方案一)是用直觉做出来的,缺少了对方案本身的思考。


直觉,指的是那些没有经过分析推理的观点,因此常常给人一种不靠谱的感觉。


可是有时候我们依赖「直觉」做事真的就完全不靠谱吗?带着这样的疑惑,我去查阅了一些相关的资料来辅助我更好理解这种直觉性思考,恰好找到了一个真实的实验案例:


1997年,Bechara, Antoine et在一个赌博游戏实验中发现:「直觉」比「意识」更能指引正常人做出有利的的选择。

该实验先后邀请了10名正常人和6名前额叶损伤的决策缺陷患者,以探究人们做出正确选择是在对相关知识进行推理之前还是之后。在游戏开始前,工作人员给予每位参与者2000美金、发放4副牌,要求参与者在游戏过程中翻出100张卡片,并尽可能的多赢钱,但他们不会告知参与者每副牌中的卡片价值:从A、B两副牌中翻出正常的卡片能赚100美金,C、D两副牌中的正常卡片值50美金,同时每副牌中也隐藏着罚款,A、B两副牌中的罚款比C、D两副牌的罚款重,参与者很可能会输光所有的钱。

实验结果显示:正常人在意识到哪副牌赢面更大之前就开始选择有利的卡牌,而决策缺陷患者即使知道了正确的策略,却仍然继续选择对自己不利的卡牌。


根据上述实验,我们起码能发现直觉未见得不可靠,也就是说,凭借直觉出的设计方案,并不意味着不是一个正确的方案。


可是在日常沟通协作的过程中,「直觉设计」一旦遇到不同的意见,就会缺少理论支撑。决策者无法判断设计师的直觉是否可靠,从而觉得方案本身也不可靠。


遇到这种看似「死胡同」的情况,我们应该怎么去思考呢?


很简单,直觉在前,策略性推理在后。


乔纳森•海特(Jonathan Haidt),著名社会心理学家,在《正义之心》中有提到:


判断和论证是两个相互分离的过程,直觉与推理的关系就像大象与骑象人,骑象人(推理)骑在大象(直觉)上,骑象人不断发展以服务于大象。


拿上述的方案来说,当我面对这类的质疑时,我也会有愣住,但我知道我不能惊慌,而是该让骑象人(推理)表演了。


其实仔细分析一下上述案例就会发现,方案一和方案二最本质的区别在于:


是否需要区分用户(商家)自行设置过「补贴规则 」?


百度百科对规则的定义是:


规则,一般指由群众共同制定、公认或由代表人统一制定并通过的,由群体里的所有成员一起遵守的条例和章程。


规则本身是属于利益相关者之间的约定。按照这个逻辑,「补贴规则」可以理解为用户(商家)与消费者形成的基本约定。


用户(商家)未设置规则,如果系统直接展示默认的补贴条件和补贴范围,就会给用户(商家)一种平台借以商家的名义与消费者形成了约定的印象,这与现实不符,甚至可能给用户(商家)的业务带来不必要的纷争。因此,区分用户是否设置过补贴规则是非常有必要的。


为什么要为自己的设计辩护?


在上述场景中,虽然我的直觉先于理性给我发送了信号,但设计师如果光依靠直觉却给不出任何说明,同样会带来一系列麻烦。


汤姆·格里弗(Tom Greever)在一篇文章提到,伟大的设计往往取决于你怎么说。


描述设计不是一件容易的事情,但每个设计师都不得不向很多没有设计经验的人讲述自己的设计,并且要让他们信服自己是对的,这群人很可能是方案的决策者。决策者通常会选择那个听上去最合理的方案,所以方案的表述对于最终方案的确立至关重要。


普通设计师和顶级设计师之间的差距不仅仅是他们解决问题的能力,还在于他们能否用一种让人心服口服、并促使人们同意的方式来阐述他们是怎么解决问题的。理论上,最好的设计应该胜出,然而事实并非如此,设计评审很容易变成设计批判会,每个人都在告诉设计师要怎么设计。


最终,那些能够说服别人“我是对的”的人会胜出。设计师如果没有办法说服别人为什么他们要这么做,就不得不按照他们不同意的方式去修改设计,原因仅仅是因为他们没有办法简要的为自己的设计辩护。


听起来似乎这些决策有失公允,甚至成了设计师的辩护大会,那么对于一些有着出色能力却不善言辞的设计师而言,就真的没有任何方法了吗?


如何突破直觉,能言善辩?


设计师要想守卫自己的设计,就要警惕那些单凭设计直觉做出来的方案。


设计直觉的形成与个人经历、阅读经历相关。遇到相似的问题,设计师如果有这方面的经验固然是好的,直接复用之前的做法可以大大提升设计效率。但我们完成设计后,最好想想哪些地方存在路径依赖,以确保自己的方案能经得住质疑。


一个最实用的可以判断自己的设计方案,是不是由直觉得来的,就是多向自己提问。


同样,我们来用实际的案例做个说明:


想想下图中「智能上传」「更多操作」按钮放在表格的左下方行不行?


很明显不行,但重点是支持这么做不行的理由是什么?


如果你的理由是:


“别的页面是将按钮放在了列表左上角的”

“放在左下角不好看呀”

“没见过有产品这么放啊”

“......”


那这个方案就是直觉设计的产物了。


想要突破直觉设计,设计师需要尽可能在每个设计点上多思考几步,比如:


为什么别的页面会将按钮放在左上角?


根据2006年NNGroup 在眼动实验中的发现,人们在网络中的阅读成F型,即用户进入页面中的第一眼,通常会落在页面的左上角,也就是说左上方的区域是页面的黄金区。「智能上传」「更多操作」属于页面的核心操作,那么放在列表左上方是非常合理的。


此外,我们可以看到页面的翻页器是可以筛选列表展示的条数,假设用户设置的条数,超出了屏幕显示范围,也就意味用户进入到页面会看不到操作选项,所以按钮放在表格的左下方也是不合理的。


为什么别的页面按钮放在左上角这个页面也要这么做?


因为我们需要保障产品的一致性,产品的核心操作方式保持一致,可以有效地降低用户的学习成本,避免不必要的思考。


总结


在设计协作的过程中,设计师不可避免地会接收到来自四面八方的声音,而我自己也曾在设计沟通中陷入过类似困境,因此我越发明白只有清晰地表述出自己的设计思考,才可能赢得每个人的支持。


  • 凭借直觉出的设计方案,并不意味着不是一个正确的方案。

  • 直觉在前,策略性推理在后。

  • 顶尖的设计师,也是顶尖的交流者。

  • 要想守卫自己的设计,就要警惕那些单凭设计直觉做出来的方案。

  • 多向自己提问,以确保自己的方案能经得住质疑。


以上是关于这篇文章的关键思考总结,回到设计师个体而言,我认为我们需要对直觉设计保持警惕,因为看似简单的设计背后往往蕴含着复杂的设计原理,而一个好的设计师除了拥有过硬的设计能力,强大的设计思考力和表达能力以外,我相信同样在跨学科的研究和学习上会远强于普通设计师,否则根本无法支撑起背后的设计思考。

文章来源:站酷  作者:范思蜀
分享此文一切功德,皆悉回向给文章原作者及众读者.

免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务


js_防抖与节流(闭包的使用)

前端达人

js的防抖与节流

防抖事件

定义:持续触发事件,一定时间内没有触发事件,事件处理函数只会执行一次,
当设定的时间内触发过一次事件后会重新开始延时。
例:输入框的事件(2s显示内容,不是实时刷新显示内容),对比输入框的内容事件。
实时刷新的效果

 <div class="container"> <div class="left"> <p>实时刷新显示内容</p> <input type="text" id="leftInput" /> <div class='textDiv' id="textShow"></div> </div> <div class="right"></div> </div> <script> var inputDom=document.getElementById('leftInput'); inputDom.addEventListener('keyup',function(e){ var textDom=document.getElementById('textShow'); console.log(e.target.value); textDom.innerText=e.target.value; }) </script>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

输入123456会依次打印123456的金字塔
在这里插入图片描述

防抖(1s内显示输入内容)

 <div class="container"> <div class="left"> <p>防抖(1s内显示输入内容)</p> <input type="text" id="leftInput" /> <div class='textDiv' id="textShow"></div> </div> <div class="right"></div> </div> <script> // 防抖 var inputDom = document.getElementById('leftInput'); // 函数柯里化 function debounce(delay, callback) { let timer return function(value) { //闭包内存泄漏 clearTimeout(timer) timer = setTimeout(function() { //执行 callback(value) }, delay) } } // 显示内容的函数 function showText(value) { var textDom = document.getElementById('textShow'); console.log(value) textDom.innerText = value; } var debounceFunc = debounce(1000, showText); inputDom.addEventListener('keyup', function(e) { let value = e.target.value debounceFunc(value) }) </script>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

1s内输入123456只会打印一次123456
在这里插入图片描述

节流事件

定义:一段时间直只调用一次事件处理函数
实际用例:提交事件 、游戏的技能cd(在游戏cd中点击n次都不会发动技能)

// 节流
            var skillDom = document.getElementById('skillTriger');
            function throttle(wait,callback) {
                let timeOut;
                return function(value) {
                    if (!timeOut) {
                        timeOut = setTimeout(function() {
                            callback(value);
                            //执行一次,时间段内的都不知执行
                            timeOut = null;
                        }, wait)
                    }
                }
            }
            function skillEvent(value){
                var textDom = document.getElementById('skillEventId');
                console.log(value)
                ++count
                textDom.innerText = value+count;
            }
            var skillAc=throttle(3000,skillEvent)
            var count=0
            skillDom.addEventListener('click', function(e) {
                let value = e.target.value
                skillAc(value)
            })  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在这里插入图片描述

完整的html

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>防抖与节流</title> </head> <style> * { margin: 0; padding: 0; } .container { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 600px; height: 400px; background: #262626; display: flex; } .left { position: relative; width: 50%; height: 100%; background: #00cec9; box-sizing: border-box; overflow: hidden; } .right { position: relative; width: 50%; height: 100%; background: #b2bec3; } </style> <body> <div class="container"> <div class="left"> <p>防抖(1s内显示输入内容)</p> <input type="text" id="leftInput" /> <div class='textDiv' id="textShow"></div> </div> <div class="right"> <p>节流(3s内触发一次)</p> <input type="submit" id="skillTriger" value="发动技能" /> <div class='skillEvent' id="skillEventId"></div> </div> </div> <script> // 防抖 var inputDom = document.getElementById('leftInput'); // 函数柯里化 function debounce(delay, callback) { let timer return function(value) { //闭包内存泄漏 clearTimeout(timer) timer = setTimeout(function() { //执行 callback(value) }, delay) } } // 显示内容的函数 function showText(value) { var textDom = document.getElementById('textShow'); console.log(value) textDom.innerText = value; } var debounceFunc = debounce(1000, showText); inputDom.addEventListener('keyup', function(e) { let value = e.target.value debounceFunc(value) }) // 节流 var skillDom = document.getElementById('skillTriger'); function throttle(wait, callback) { let timeOut; return function(value) { if (!timeOut) { timeOut = setTimeout(function() { callback(value); //执行一次,时间段内的都不知执行 timeOut = null; }, wait) } } } function skillEvent(value) { var textDom = document.getElementById('skillEventId'); console.log(value) ++count
                textDom.innerText = value + count; } var skillAc = throttle(3000, skillEvent) var count = 0 skillDom.addEventListener('click', function(e) { let value = e.target.value skillAc(value) }) </script> </body> </html>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113

1

蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

分享此文一切功德,皆悉回向给文章原作者及众读者.

转自:csdn
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务

配合前端vue实现表格的增删改查

前端达人

前言:

        这个章节内容是比较多的,分为前端部分和后端部分。

目录:

实现效果:增删改查

 一、后端部分:

(1)数据库:

        新建一张表user,设置几个字段,效果如下:

 (2)egg逻辑部分:

提供的接口:http://localhost:7001/setUserList

 (3)egg具体实现步骤:

1、router.js中添加:

2、新建:app / controller / new / user.js

(1)查,模糊查询 

(2)增

 (3)改

 (4)删

 user.js 源码:

3、引入mysql:点我

4、跨域问题解决:点我

 二、前端部分:

新建vue文件:testApi.vue

1、初始化查询列表数据:页面展示列表,列表数据mouted获取

2、页面点击新增:展示弹框,并将弹框内容去除掉,点击新增,将弹框内容发送给后端

 3、页面点击列表里面具体数据的编辑:弹框,并回填数据,修改将当前数据的id和表格数据传给后端

4、删除按钮,点击出现二次确认弹框,点击确认将当前数据的id给后端就行 

 testApi.vue 源码:


实现效果:增删改查

 一、后端部分:

(1)数据库:

        新建一张表user,设置几个字段,效果如下:

 (2)egg逻辑部分:

提供的接口:http://localhost:7001/setUserList

  1. get 请求,获取数据,支持模糊查询
  2. post 请求,新增数据
  3. put 请求,给后端当前数据id,修改内容
  4. delete 请求,根据数据id删除当前条数据

 (3)egg具体实现步骤:

1、router.js中添加:

 
  1. /**
  2. * 路由配置
  3. * @param app
  4. */
  5. module.exports = app => {
  6. const { router, controller } = app;
  7. //--------------------------------------------------
  8. // server接口
  9. //--------------------------------------------------
  10. app.get('/setUserList', controller.new.user.getUserList);
  11. app.post('/setUserList', controller.new.user.postUserList);
  12. app.put('/setUserList', controller.new.user.putUserList);
  13. app.delete('/setUserList', controller.new.user.deleteUserList);
  14. }

2、新建:app / controller / new / user.js

(1)查,模糊查询 

select * from user where name like ? % 内容 %

(2)增

this.app.mysql.insert('表名',内容)

 (3)改

UPDATE loginlist SET `password` = 'Ad123456' WHERE id = 2

 (4)删

delete from user where id = 1

 user.js 源码:

 
  1. /**
  2. * 用户信息路由
  3. * @param app
  4. * @returns {HomeController}
  5. */
  6. const Controller = require('egg').Controller;
  7. class NewsController extends Controller {
  8. async getUserList() {
  9. //查询库里的user表
  10. let params = this.ctx.query //获取路径后面的参数
  11. console.log('用户的参数:');
  12. console.log(params);
  13. let sql = 'select * from user'
  14. let initSql = sql
  15. let content = [];//参数
  16. let isMore = false;//是否有多个查询参数
  17. /**
  18. * @模糊查询-量大的时候效率低
  19. * select * from user where name like ? % 内容 %
  20. * 在user表中全局查找name值 == 内容的
  21. * % 内容 % 全局查找内容
  22. * 内容 % 查找以 内容 开头的数据
  23. * */
  24. if(params.name){
  25. sql += " where name like ?";
  26. content.push( "%"+params.name+"%" );
  27. isMore = true;
  28. }
  29. if(params.age){
  30. if(isMore){//true代表有多个参数
  31. sql += "and age LIKE ?";//and是两个条件都必须满足,or是或的关系
  32. }else{
  33. sql += " WHERE age LIKE ?";
  34. }
  35. content.push( "%"+params.age+"%" )
  36. isMore = true;
  37. }
  38. if(params.address){
  39. if(isMore){//true代表有多个参数
  40. sql += "and address LIKE ?";//and是两个条件都必须满足,or是或的关系
  41. }else{
  42. sql += " WHERE address LIKE ?";
  43. }
  44. content.push( "%"+params.address+"%" )
  45. isMore = true;
  46. }
  47. if(params.phone){
  48. if(isMore){//true代表有多个参数
  49. sql += "and phone LIKE ?";//and是两个条件都必须满足,or是或的关系
  50. }else{
  51. sql += " WHERE phone LIKE ?";
  52. }
  53. content.push( "%"+params.phone+"%" )
  54. }
  55. //开启分页
  56. if(params.page || params.pageSize){
  57. let current = params.page;//当前页码
  58. let pageSize = params.pageSize;//一页展示多少条数据
  59. sql += " limit ?,?";
  60. content.push((current-1)*pageSize,parseInt(pageSize));
  61. }
  62. let allList = await this.app.mysql.query(initSql);
  63. let userList= await this.app.mysql.query(
  64. sql,content
  65. );
  66. this.ctx.body = {
  67. code:200,
  68. masg:'success',
  69. data: {
  70. list:userList,
  71. total:allList.length
  72. }
  73. };
  74. }
  75. /**
  76. * 新增用户信息
  77. * INSERT INTO loginlist (aaa,bbb,ccc) VALUES ('"'+111+'","'+222+'","'+333+'"')
  78. * this.app.mysql.insert('表名',内容)
  79. * this.app.mysql.insert('表名',{
  80. * name:123,
  81. * age:1,
  82. * address:西安
  83. * })
  84. */
  85. //
  86. async postUserList() {
  87. console.log(this.ctx.request.body);
  88. //新增数据-user表
  89. let data = this.ctx.request.body
  90. data.id = parseInt(Math.random()*100000)
  91. let insertResult = await this.app.mysql.insert(
  92. 'user', data
  93. );
  94. this.ctx.body = {
  95. code:200,
  96. masg:'success',
  97. data:insertResult
  98. };
  99. }
  100. /**
  101. * 修改用户信息
  102. * UPDATE loginlist SET `password` = 'Ad123456' WHERE id = 2(唯一值),修改一个
  103. * UPDATE loginlist SET username = 'admins', `password` = 'Ad123456' WHERE id = 2,修改多个
  104. */
  105. async putUserList(){
  106. //新增数据-user表
  107. let id = this.ctx.query.id //获取路径后面的参数
  108. let data = this.ctx.request.body
  109. let sql = 'update user set '
  110. let isMore = false;//是否有多个查询参数
  111. if(!id){
  112. this.ctx.body = {
  113. code:200,
  114. masg:'warning',
  115. data:'id没有传'
  116. };
  117. return
  118. }
  119. //姓名
  120. if(data.name){
  121. sql += 'name = "'+data.name+'"'
  122. isMore = true
  123. }
  124. //年龄
  125. if(data.age){
  126. if(isMore){
  127. sql += ',age = "'+data.age+'"'
  128. }else{
  129. sql += 'age = "'+data.age+'"'
  130. }
  131. isMore = true
  132. }
  133. //地址
  134. if(data.address){
  135. if(isMore){
  136. sql += ',address = "'+data.address+'"'
  137. }else{
  138. sql += 'address = "'+data.address+'"'
  139. }
  140. isMore = true
  141. }
  142. //手机号
  143. if(data.phone){
  144. if(isMore){
  145. sql += ',phone = "'+data.phone+'"'
  146. }else{
  147. sql += 'phone = "'+data.phone+'"'
  148. }
  149. isMore = true
  150. }
  151. //邮箱地址
  152. if(data.email){
  153. if(isMore){
  154. sql += ',email = "'+data.email+'"'
  155. }else{
  156. sql += 'email = "'+data.email+'"'
  157. }
  158. }
  159. sql += ' where id = ' + id
  160. let insertResult = await this.app.mysql.query(sql)
  161. this.ctx.body = {
  162. code:200,
  163. masg:'success',
  164. data:insertResult
  165. };
  166. }
  167. /**
  168. * 注销用户接口
  169. * DELETE FROM loginlist WHERE username = 'superman'
  170. */
  171. async deleteUserList(){
  172. //查询库里的user表
  173. let params = this.ctx.query //获取路径后面的参数
  174. console.log('用户的参数:');
  175. console.log(params);
  176. let sql = 'delete from user where id = '+ params.id
  177. let res = await this.app.mysql.query(
  178. sql
  179. );
  180. this.ctx.body = {
  181. code:200,
  182. masg:'success',
  183. data:res
  184. };
  185. }
  186. /**
  187. * 模糊查询封装方法
  188. * @params = auth
  189. * */
  190. async query( auth ) {
  191. const TABLE_NAME = 'user';
  192. const QUERY_STR = 'id, name, age, phone, address';
  193. let sql = `select ${QUERY_STR} from ${TABLE_NAME} where authName like "%${auth.authName}%"`;
  194. const row = await this.app.mysql.query(sql);
  195. return row;
  196. }
  197. }
  198. module.exports = NewsController;

3、引入mysql:点我

4、跨域问题解决:点我

 二、前端部分:

新建vue文件:testApi.vue

1、初始化查询列表数据:页面展示列表,列表数据mouted获取

 
  1. <el-table
  2. :data="tableInfo.list"
  3. style="width: 100%">
  4. <el-table-column
  5. type="index"
  6. label="序号"
  7. align="center">
  8. </el-table-column>
  9. <el-table-column
  10. prop="name"
  11. label="姓名"
  12. align="center">
  13. </el-table-column>
  14. <el-table-column
  15. prop="age"
  16. label="年龄"
  17. align="center">
  18. </el-table-column>
  19. <el-table-column
  20. prop="address"
  21. label="地址">
  22. </el-table-column>
  23. <el-table-column
  24. prop="phone"
  25. label="手机号">
  26. </el-table-column>
  27. <el-table-column
  28. prop="email"
  29. label="邮箱">
  30. </el-table-column>
  31. <el-table-column
  32. label="操作"
  33. width="100">
  34. <template slot-scope="scope">
  35. <el-button type="text" size="small" @click="editFun(scope.row)">编辑</el-button>
  36. <el-button type="text" size="small" @click="deleteFun(scope.row)">删除</el-button>
  37. </template>
  38. </el-table-column>
  39. </el-table>
 
  1. mounted(){
  2. this.initPage()
  3. },
  4. methods: {
  5. /***
  6. * 获取列表数据
  7. */
  8. initPage() {
  9. let params = {
  10. page: this.tableInfo.page, //当前页
  11. pageSize: this.tableInfo.pageSize //一页展示多少条
  12. }
  13. this.$axios.get('http://localhost:7001/setUserList',{
  14. params
  15. }).then(res => {
  16. this.tableInfo.list = res.data.data.list
  17. this.tableInfo.total = res.data.data.total
  18. }).catch(error => {
  19. this.$message(error.data.message)
  20. })
  21. },
  22. }

2、页面点击新增:展示弹框,并将弹框内容去除掉,点击新增,将弹框内容发送给后端

 3、页面点击列表里面具体数据的编辑:弹框,并回填数据,修改将当前数据的id和表格数据传给后端

 

 

4、删除按钮,点击出现二次确认弹框,点击确认将当前数据的id给后端就行 

 testApi.vue 源码:

 
  1. <template>
  2. <div class="pro_body">
  3. <el-button type='success' @click="addModal">新增</el-button>
  4. <el-table
  5. :data="tableInfo.list"
  6. style="width: 100%">
  7. <el-table-column
  8. type="index"
  9. label="序号"
  10. align="center">
  11. </el-table-column>
  12. <el-table-column
  13. prop="name"
  14. label="姓名"
  15. align="center">
  16. </el-table-column>
  17. <el-table-column
  18. prop="age"
  19. label="年龄"
  20. align="center">
  21. </el-table-column>
  22. <el-table-column
  23. prop="address"
  24. label="地址">
  25. </el-table-column>
  26. <el-table-column
  27. prop="phone"
  28. label="手机号">
  29. </el-table-column>
  30. <el-table-column
  31. prop="email"
  32. label="邮箱">
  33. </el-table-column>
  34. <el-table-column
  35. label="操作"
  36. width="100">
  37. <template slot-scope="scope">
  38. <el-button type="text" size="small" @click="editFun(scope.row)">编辑</el-button>
  39. <el-button type="text" size="small" @click="deleteFun(scope.row)">删除</el-button>
  40. </template>
  41. </el-table-column>
  42. </el-table>
  43. <el-pagination
  44. class="work_pagination"
  45. background
  46. :layout="'total,prev, pager, next,sizes'"
  47. :total="tableInfo.total"
  48. :hide-on-single-page="false"
  49. :current-page="tableInfo.page"
  50. :page-size="tableInfo.pageSize"
  51. @current-change="pageChange"
  52. @size-change="pageSizeChange"
  53. >
  54. </el-pagination>
  55. <!--弹框部分-->
  56. <el-dialog
  57. custom-class="modalName"
  58. :visible.sync="modal.show"
  59. :width="modal.type === 'delete'? '470px' : '800px'">
  60. <h3 v-if="modal.type === 'add'" slot="title">新增</h3>
  61. <h3 v-if="modal.type === 'edit'" slot="title">修改</h3>
  62. <h3 v-if="modal.type === 'delete'" slot="title">提示</h3>
  63. <div v-if="modal.type !== 'delete'" class="editNameBody">
  64. <el-form class="editNameFrom" :model="modalFrom" label-width="120px" label-position="right">
  65. <el-form-item label="姓名:">
  66. <el-input v-model="modalFrom.name"></el-input>
  67. </el-form-item>
  68. <el-form-item label="年纪:">
  69. <el-input v-model="modalFrom.age"></el-input>
  70. </el-form-item>
  71. <el-form-item label="手机号:">
  72. <el-input v-model="modalFrom.phone"></el-input>
  73. </el-form-item>
  74. <el-form-item label="邮箱:">
  75. <el-input v-model="modalFrom.email"></el-input>
  76. </el-form-item>
  77. <el-form-item label="地址:">
  78. <el-input v-model="modalFrom.address" type="textarea"></el-input>
  79. </el-form-item>
  80. </el-form>
  81. </div>
  82. <div v-if="modal.type === 'delete'">
  83. <i class="el-icon-warning" style="margin-right:7px;color:#FFAA00;font-size: 16px;"></i>请确认是否删除
  84. </div>
  85. <div slot="footer" class="dialog-footer">
  86. <el-button @click="modal.show = false">取 消</el-button>
  87. <el-button v-if="modal.type === 'add'" type="primary" @click="addUserList">新增</el-button>
  88. <el-button v-if="modal.type === 'edit'" type="primary" @click="editUserList">修改</el-button>
  89. <el-button v-if="modal.type === 'delete'" type="primary" @click="deleteUserList">确 认</el-button>
  90. </div>
  91. </el-dialog>
  92. </div>
  93. </template>
  94. <script>
  95. export default {
  96. data() {
  97. return {
  98. tableInfo:{
  99. columns:[
  100. {
  101. title: '序号',
  102. type: 'index',
  103. },{
  104. title: '姓名',
  105. prop: 'name',
  106. align: 'center'
  107. },
  108. {
  109. title: '年龄',
  110. prop: 'age',
  111. align: 'center'
  112. },{
  113. title: '地址',
  114. prop: 'address',
  115. align: 'center'
  116. },{
  117. title: '邮箱',
  118. prop: 'email',
  119. align: 'center'
  120. },{
  121. title: '手机号',
  122. prop: 'phone',
  123. align: 'center'
  124. },
  125. {//内容slot
  126. slot: 'operateT'
  127. }
  128. ],
  129. list:[],
  130. page:1,
  131. pageSize:10,
  132. total:0,
  133. },
  134. modal:{
  135. show:false,
  136. type:'add'
  137. },
  138. modalFrom:{
  139. name:'',
  140. age:'',
  141. phone:'',
  142. address:'',
  143. email:''
  144. }
  145. };
  146. },
  147. mounted(){
  148. this.initPage()
  149. },
  150. methods: {
  151. /***
  152. * 获取列表数据
  153. */
  154. initPage() {
  155. let params = {
  156. page: this.tableInfo.page, //当前页
  157. pageSize: this.tableInfo.pageSize //一页展示多少条
  158. }
  159. this.$axios.get('http://localhost:7001/setUserList',{
  160. params
  161. }).then(res => {
  162. this.tableInfo.list = res.data.data.list
  163. this.tableInfo.total = res.data.data.total
  164. }).catch(error => {
  165. this.$message(error.data.message)
  166. })
  167. },
  168. /**
  169. * 当前页发生改变
  170. * */
  171. pageChange(page) {
  172. this.tableInfo.page = page
  173. this.initPage() //刷新列表数据
  174. },
  175. /**
  176. * 当前第几页发生改变
  177. * */
  178. pageSizeChange(pageSize) {
  179. this.tableInfo.page = 1
  180. this.tableInfo.pageSize = pageSize
  181. this.initPage() //刷新列表数据
  182. },
  183. //新增弹框
  184. addModal(){
  185. this.modalFrom ={
  186. name:'',
  187. age:'',
  188. phone:'',
  189. address:'',
  190. email:''
  191. }
  192. this.modal.show = true
  193. this.modal.type = 'add'
  194. },
  195. //修改弹框
  196. editFun(row){
  197. this.modalFrom = row
  198. this.modal.show = true
  199. this.modal.type = 'edit'
  200. },
  201. //删除弹框
  202. deleteFun(row){
  203. this.modalFrom = row
  204. this.modal.show = true
  205. this.modal.type = 'delete'
  206. },
  207. addUserList(){
  208. this.$axios({
  209. method:'post',
  210. url:'http://localhost:7001/setUserList',
  211. data:this.modalFrom
  212. }).then(res => {
  213. this.$message.success('新增成功')
  214. this.initPage() //刷新数据
  215. this.modal.show = false
  216. })
  217. },
  218. editUserList(){
  219. this.$axios({
  220. method:'put',
  221. url:'http://localhost:7001/setUserList',
  222. params:{
  223. id:this.modalFrom.id
  224. },
  225. data:this.modalFrom
  226. }).then(res => {
  227. this.$message.success('修改成功')
  228. this.initPage() //刷新数据
  229. this.modal.show = false
  230. })
  231. },
  232. deleteUserList(){
  233. this.$axios({
  234. method:'delete',
  235. url:'http://localhost:7001/setUserList',
  236. params:{
  237. id:this.modalFrom.id
  238. }
  239. }).then(res => {
  240. this.$message.success('删除成功')
  241. this.initPage() //刷新数据
  242. this.modal.show = false
  243. })
  244. }
  245. }
  246. }
  247. </script>
  248. <style lang='scss'>
  249. .modalName{
  250. h3{
  251. padding:10px;
  252. }
  253. }
  254. </style>


蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

分享此文一切功德,皆悉回向给文章原作者及众读者.

转自:csdn
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务

全面总结图表设计的思路和方法

涛涛

数据成为了日常工作的一大重要角色,越来越多的公司以数据驱动产品进行迭代。但令人困惑的是可视化图表无处不矮,但却给大家带来了误导。作者分享了一些简单的思路与方法,以此来改善这些问题,希望对你有所帮助。


越来越多的公司以数据驱动产品进行迭代,从中我们能看出数据的重要性。

日常工作中,无论是汇报还是设计,都离不开图表的使用。但令人困惑的可视化图表无处不在,往往给人带来误导性,通过遵循下面这些简单的思路和方法可以有效改善这些问题。

一、选择正确的图表类型

选择错误的图表类型,或默认使用最常见的类型,可能会混淆用户对数据产生误解。

一组数据可以有多种表示方式,具体类型取决于用户希望看到的内容。

二、根据正负值确定方向

当数据中出现正负值时,要先确定基线的位置,再确定数据位置,将正值分布在基线上侧(X轴)或右侧(Y轴),负值分布在下侧(X轴)或左侧(Y轴)。

避免在基线的同一侧同时添加正值和负值,造成用户对图表信息理解错误。

三、始终从零开始绘制条形图

单看左侧的条形图,能发现B的值比D的值要多3倍以上,但在右侧从零开始的条形图中,实际差异要小得多。从零开始可确保用户获得更准确的数据展示。

四、折线图使用自适应Y轴

对折线图来说,如果始终将Y轴的比例限制为从零开始,一旦数据波动幅度很小,那整个折线图会看起来很平坦,效果不明显。

折线图主要用来表现趋势,根据给定时间的数据调整比例,并保持折线区域能占到Y轴范围的三分之二。

五、使用折线图时考虑时间间隔

折线图是由一条条小线段连接组成,这些线段展示了在短时间内数据是如何变化的。当时间间隔很大或数据更新不频繁时,就要慎重考虑是否使用折线图。

例如想表示年收入,左侧的两个折线图样式都不太合适,每个月的收入是固定的数字,而折线图展现的数据更像是收入的变化,相反右侧的条形图更适合来展示每月具体的收入。

六、不要使用平滑的折线

平滑的折线图可能看着很舒服,但它们歪曲了背后的实际数据,而且过粗的线条掩盖了真正的节点。

七、谨慎使用双轴折线图

当两组数据出现X轴代表的信息相同但Y轴不同时,为节省空间我们可能会考虑用双轴图。

但大部分双轴图难以阅读,只是感觉图表上有很多数据,但远远没有单个图表展示的清晰。

八、限制饼图的切片数量

饼图是最受欢迎但经常被误用的图表之一。在使用饼图时,首先要注意切片的数量最好保持在5-7片。

如果还有很多占比很小的切片,可以将这些全部归到“其他”切片中。

九、直接在图表上标注

如果没有正确的标注,无论图表设计的多好看都没有意义。

直接在图表上标注数据或信息对使用者来说更直观,更节省时间和精力。

十、不要在切片上标注

将数值放在切片上虽然很直观,但可能会导致很多问题,例如左侧饼图数值的可读性问题、切片太薄无法添加数值等,对比来看,右侧饼图添加标注的方式更合适。

十一、饼图切片的排序

饼图切片的排序是一个很容易忽略的问题,将饼图切片只是一个开始,通过合理的排序保证用户清晰观看图表才是关键。

常见的排序方法是将面积最大的切片放在12点钟位置,然后按顺时针降序放置第二大的切片,以此类推。

十二、避免随机性

同样的建议适用于其他类型的图表。尽量不要默认按字母顺序排序,将最大值放在顶部(水平条形图)或左侧(对于垂直条形图),以确保最重要的值占据最突出的空间,减少视线运动和阅读图表所需的时间。

十三、避免极端的环形图

环形图,又称为甜甜圈图,是饼图的一种变体,本质是饼图将中间区域挖空,用在多样品间的多种数据的比较中。

虽然环形图腾出中间区域来显示额外的信息,但牺牲清晰度走极端会让图表变得毫无用处。

十四、让数据自己说话

不必要的设计样式不仅会分散注意力,还可能导致用户对数据误解并产生错误印象,图表在设计上应避免:

  • 3D元素、阴影、渐变;
  • 斑马纹、过多的网格线;
  • 装饰性过强的斜体、粗体或衬线字体。

十五、选择与数据性质匹配的调色板

颜色是保持数据可视化有效的组成部分,在设计时考虑3种调色板类型:

  • 分类色板(左)适合显示分类数据,当你想区别不连续且内在没有顺序关系的数据时可以使用这种类型;
  • 连续色板(中)适用于需要按特定顺序放置的变量中,使用色调/亮度或两者组合创建色板。
  • 离散色板(右)是两个连续色板的组合,中间有一个中心值(通常为零)。不同的调色板会传达正值和负值。

十六、无障碍设计

根据眼科研究中心的数据,大约每12个人中就有1个色盲,图表只有在广泛受众可以访问的情况下才是成功的:

  • 在调色板中使用不同的饱和度、亮度;
  • 黑白打印可视化图表,检查对比度和可读性。

十七、注重易读性

确保图表排版在传达信息并帮助用户专注于数据,而不是分散注意力:

  • 选择字迹清晰的字体,避免使用衬线和装饰性很强的字体;
  • 避免使用斜体、粗体和全部大写;
  • 确保文本与背景形成高对比度;
  • 不要旋转文本。

十八、使用水平条形图代替旋转标注

这个简单的技巧可以确保用户能够更有效地浏览图表,而不会使他们感到紧张。

十九、建立图表库

如果你的任务是将交互式图表添加到Web和移动项目中,那么首要考虑问题是将使用什么样的图表?

基于定义的库(Highcharts)进行设计将确保易于实现,并为我们提供大量的交互想法。

二十、超越静态报告

通过更改参数、可视化类型、时间线帮助用户进行探索,得出最大价值化的结论。例如IOS Health结合使用了各种数据表示来发挥优势。



蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码ben_lanlan,报下信息,会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

文章来源:人人都是产品经理  作者:Clippp
分享此文一切功德,皆悉回向给文章原作者及众读者.

免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务



如何提升用户体验

涛涛

现代人的生活离不开应用程序,我们的手机中有着各种各样的app帮我们处理各种琐事。各大互联网公司开发多种应用商店,努力使我们的生活变得有趣,但要想提高整体的用户体验,还需要注意五点,不然再好的app也会让用户放弃使用。


1.禁止使用大段的功能介绍

我们必须要提示一下:如果一个应用程序要在手机屏幕上发挥作用——必须简单。这个应用需要使用冗长的功能介绍,才能让用户熟悉使用起来,那很可能是开发人员在这个版本中设置太多功能。用户并不介意多次更新程序,适应新版本,但他们也不可能为了使用程序而阅读长如圣经般的介绍。


2.牢记用户需求


如果想要急于求成,跳过用户调研,上线新的应用,那么失败的概率会大大增加。大多数应用开发团队都容易犯这样的错误,只拿到了用户发送给支持团队的数据。通过分析数据,就能迅速确定用户所需的功能,使用缺陷等,但是,总的来说,联系支持团队的用户是那些产生挫败感的人;那么99%(或更多)的用户从来没有和支持部门谈过?难道你不愿意让他们一直满意,而是花费时间,只让使用支持功能的1%用户满意?


即使你认为自己了解用户需求,也需要与他们进行沟通。


3.使用屏幕技术


当人们花费了近万元买了一部智能手机时,他们希望能使用手机上的所有功能。所以制作这款手机时没有任何借口在视觉内容上偷工减料(或忽视任何技术能力)。这就是用户花钱的原因,要想提高用户体验,就需要满足用户期望。


4.停用费时的设置向导


设置向导的本意是为用户提供更好的内容,更能让用户获取自己想要的。为了优化用户体验可以让用户填写一些内容,但并不意味着暴露全部隐私。最好在需要时索取,而非提前取得全部数据。


5.减少通知次


每个软件都想要发送通知,但是每一个通知都是必要的吗?每隔两秒钟就有一些应用告知用户一些无用消息,比如谁谁谁又发了微博,谁谁谁上线了,又或者谁谁谁开始直播了,可以根据用户的兴趣选取推送而不是一股脑全部告知用户,所以请尽量减少的通知消息吧!

蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码ben_lanlan,报下信息,会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

文章来源:站酷  作者:马克笔留学设计

分享此文一切功德,皆悉回向给文章原作者及众读者.

免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务


B端产品设计规范之数据展示

涛涛


数据展示有哪些?



01.徽标

是收纳消息数量的样式,一般出现在图标或者头像右上角。



02.标签

数据展示里面抽取出来的共性特征,将它们转化为标签。标签样式有线框、带不透明底或者面性。



03.走马灯

相当于c端的轮播图




04.文字提示

可以出现在鼠标悬浮按钮时候的行为解释说明,也可以是文案或者导航图标的解释说明。鼠标移入时候出现移出时候消失。



05.气泡卡片

比起文字提示可以承载更多内容,相对弹窗,气泡卡片操作更轻盈。




06.标签页/选项卡

标签页可以帮助用户在一个页面内快速切换不同类型内容,提升单个页面整体的扩展性。标签本质上就是内容区的导航。



07.折叠面板

折叠面板可以更好的收纳内容区域,提高页面利用率。可以和表格结合使用,折叠表格部分详情内容,使得纵向空间更节约。




08.表格

表格是数据展示的重要内容。当有大量结构化数据需要展示时或者需要对数据进行排序、搜索分页时可以用表格进行展现。


当笔记本过小,表格展示不全时候,可以固定首尾重要信息进行滚动。


带排序的表头,可对数量或者金额进行排序。


带分组的表格,建议带边框并且用色块区分表头和内容。


单元格可编辑


批量选中时只会选中当前页,因为分页还没加载出来,为了给用户正确的引导,可以给上提示性文案,例如“已选中XX项内容”。


如果当前页批量选中的数据量不满足要求,可以改变分页器,增加当前页数据量,从而增加选项。


蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码ben_lanlan,报下信息,会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

文章来源:站酷  作者:最多三分糖

分享此文一切功德,皆悉回向给文章原作者及众读者.

免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务



如何做好直播设计的体系化复盘?

涛涛

前言

“直播”作为一个大热领域,用户规模早在 2020 年就突破了 5 亿大关,在“直播+”的模式下,电商、教育甚至是医美,都能在直播领域下找到新赛道,并产生持续性获客。但就是这样一个核心领域,具体要怎么分析复盘,却鲜少看到系统化的文章。本文主要是结合我近期参与产品直播项目的思考,总结了自己的一套直播复盘方法论和大家分享。

为什么要做直播复盘?

虽然“直播+”模式已成为行业趋势,但在产品接入直播模块而产生的付费效益又不明显时,其存在的意义和价值仍时常被挑战,因此首要的就是验证其价值;

同时,新领域缺乏可复用的方法论,只有不断探索、优化、沉淀才能促进其健康化生长;

最后,在产品范围下的直播领域,定向吸引的还是产品用户为主,持续挖掘探索直播能为产品开辟的新赛道,为产品带来拉新,是“直播+”模式对于产品的直接价值体现。

如何做好直播设计的体系化复盘?

直播复盘怎么做?

直播具备快节奏性和强竞争性,在兼顾快速复盘的同时,还需要考虑阶段性的整体化的对直播数据进行监控,因此需要将直播复盘分为快速复盘和阶段复盘 2 个大方向。

1. 快速复盘

在直播结束当天或隔天对昨日数据进行快速复盘,此时重点关注单期直播下的直观数据表现和用户反馈。目的是获得沉淀当天直播的经验并快速应用至后续直播中,是一个不断 PDCA 的过程。

在复盘维度上,可以分为数据侧和用户侧:

数据侧中我们重点聚焦本期的数据表现,同时横向对比其他直播期,去明确本期的数据表现

1)横向对比时,需要将直播关注的 3 大核心数据(总人数、最高在线人数、评论人数、在线时长均值)与其他期对比,明确该期直播的“优劣”。

如何做好直播设计的体系化复盘?

当期直播核心数据

2)聚焦本期时,重点关注直播数据的整体在线人数变化趋势,定位到最高在线点,和直播内容拟合,能够帮助我们定位直播的“高光点”。

用户侧中则更关注主播在播、用户在听和答的过程,包含:

  • 直播过程中,用户情绪、反馈异常的节点(如消极发言等),定位用户可能存在的痛点。
  • 固定同学作为“直播观众”时,察觉到的异常问题(如信息衔接不自然等),及时复盘。
  • 在直播间的互动评论中,呼声最高的内容,可以作为后续直播可以“返场”的内容,纳入直播内容需求池中。

2. 阶段复盘

在累计多场直播后,此时最需要的就是阶段性的复盘,从宏观角度对多期直播进行收敛分析,能够帮助我们明确直播对于产品的核心价值,并定位核心用户圈层、吸引点及直播应该要有的“节奏”。

在阶段复盘中,除了数据侧、用户侧外,我们还需要关注市场侧:

数据侧—不同于快速复盘,数据侧我们关注整体宏观角度上直播对产品的价值,以及直播整体的数据表现,热点分布等

1)从直播对产品价值定位来说,在未探索付费的情况下,直播能为产品做的就是吸引新用户,增加用户活跃、延长用户在产品内停留的时长上。

如何做好直播设计的体系化复盘?

价值定位时关注的数据范围

在明确价值后,就应该明确后续要如何把直播做的更好,这里数据上可以分为 2 个大的方向,一是热点分析,二是直播节奏分析。

2)热点分析上,需要从单期、分类、词项拆解上看。首先,单期直播就是简单地对单期直播数据进行排序,找到“好”的直播提炼其特征性;其次,分类分析上关注各类直播数据间的横向对比,能从分类数据对比上,看出哪一类直播更吸引用户;最后,通过对每期直播的标题进行词项拆解,筛除掉无意义的词项,再乘以对应期直播的人数,对每个出现 2 次及以上的词项数据取出其对应的单期直播人数均值,即可定位到用户最关注、最能吸引用户的核心关键词。

如何做好直播设计的体系化复盘?

热点分析

3)直播节奏分析上,得益于阶段性复盘的庞大数据量,我们可以聚类出多期直播聚类下的直播趋势线,在趋势线上,亦关注 3 大核心点 “拉新、增长、流失”。

a. 用户进入直播的高峰期(即新增高峰),可用于定位直播亮点,并对应布局以留住用户

如何做好直播设计的体系化复盘?

用户进入直播节奏分析

b. 通过直播数据排序后的高于中位数直播、低于中位数直播的 2 种直播数据走势的对比,我们能够看出好的直播应有的数据走势应该是什么样的。以我这次分析的直播为例,能够直观看出,“差”的直播在增长黄金期都“爬”的很慢,而在黄金期后直播数据逐渐平缓,“差”的直播数据就成了定局。因此要做的就是在增长黄金期,去尽量促进增长。

如何做好直播设计的体系化复盘?

用户流入流出关系分析

c. 那么在增长黄金期,从产品策略来看,要做的是留住用户?还是尽量去拉新?从数据上,我们有 2 种方式判别,分别是相关性分析和假设分析。在相关性分析中,将新增人数/流失率对标最高在线人数,以数据模型分析其相关性,从相关系数来看哪个指标与最高在线人数相关性最高,即影响最大;在假设分析中,我们可以将“低于中位数”的直播数据中的新增人数/流失率分别对标“高于中位数”直播的数据,其他保持不变,看哪种情况下,最高在线人数是更高,数据是更好的,就可以定位出更应该在哪块发力。

用户侧—要明确直播受众及用户圈层,并针对这部分受众的观看体验,进行服务体验走查,更能够帮我们明确后续业务重心、范围及宣传模式。

在用户圈层上,我们对观众数据清洗,从年龄、性别、地域、渠道等各项维度去定位观众的特征。同时,考虑到直播本身是以产品为载体,其用户圈层基本上与产品本身重合,但会存在一定差异性。因此,此处可以结合 TGI 分析,可以定位到直播用户圈层相较于产品来说的差异点,从而更针对受众进行直播内容调整。

如何做好直播设计的体系化复盘?

用户圈层分析

同时,直播本身因其特殊性质,也可类比至服务体验设计的思维,因此在用户侧分析时,从直播前-中-后 3 个大环节上分析用户行为、需求、痛点、快点,从而定位到各个环节直播优化的机会点,推进优化。

如何做好直播设计的体系化复盘?

直播-体验地图

市场侧—在市场分析时,我们不仅需要关注那些在直播领域做的好的同类竞品,同时也要去多分析在受众群体中,最近的热门话题是什么,有助于后续在宣发时引出直播核心话题,也能够带领我们思考直播的新赛道,以协助产品拓宽其用户圈层。

小结

直播作为互联网新赛道,直播+的模式可用于产品宣发、获客、促活等各个方面,挑战与机遇并存。但正因为是新赛道,也相对缺乏体系化的方法论。本文主要是分享我在工作中沉淀下来的直播复盘方法论,也欢迎大家有什么见解或者想法也可以与我分享。

蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码ben_lanlan,报下信息,会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

文章来源:优设  作者:番茄

分享此文一切功德,皆悉回向给文章原作者及众读者.

免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务




CORS和JSONP的区别,如何解决跨域问题?

前端达人

在我们了解JSONP 和 CORS 之前我们先明确一下:

我们为什么要使用cors和jsonp呢?

实际上,cors和jsonp都是用于解决跨域问题,当两个页面的协议、域名、端口号中有一个不一致时就存在了跨域,一旦出现跨域,浏览器发送跨域请求后,请求回来的数据都会被浏览器所拦截,准备一张图给大家看看:

 

核心点:如何实现跨域数据请求?(⭐⭐⭐⭐⭐)

现下实现跨域数据请求,最主要的两种解决方案分别是 JSONP 和 CORS 。

JSONP : 出现的早,兼容性好(兼容低版本 IE )。是前端程序员为了解决跨域问题,被迫想出来的一种 临时解决方案,最主要的缺点 是只支持 GET 请求,不支持 POST 请求。
CORS : 出现的较晚,它是 W3C 标准,属于跨域 Ajax 请求的根本解决方案。支持 GET 和 POST 请 求。缺点 是不兼容某些低版本的浏览器。

什么是JSONP(⭐⭐⭐)

JSONP (JSON with Padding) 是 JSON 的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。

JSONP的实现原理(⭐⭐⭐) 

  1. 概念:浏览器端通过 <script> 标签的 src 属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做 JSONP

  2. 特点: 

   JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 这个对象

    JSONP 仅支持 GET 请求,不支持 POST、PUT、DELETE 等请求

什么是CORS(⭐⭐⭐⭐⭐)

  1. CORS (跨域资源共享) 由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器 是否阻止前端 JS 代码跨域获取资源

  2. 浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可以解除浏览器端的跨域访问限制

 

 

下面分别向大家演示通过CORS和JSONP实现跨域的案例:

一、通过CORS中间件解决跨域问题 :

index.html文件代码演示:


    
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. <script src="jquery.min.js"></script>
  9. </head>
  10. <body>
  11. <button class="get">get请求</button>
  12. <button class="post">post请求</button>
  13. <script>
  14. $('.get').on('click', function() {
  15. $.ajax({
  16. method: 'get',
  17. url: 'http://127.0.0.1/api/get?name=hua&age=18',
  18. success: function(res) {
  19. console.log(res);
  20. }
  21. })
  22. })
  23. $('.post').on('click', function() {
  24. $.ajax({
  25. method: 'post',
  26. url: 'http://127.0.0.1/api/post',
  27. data: {
  28. name: 'lajitong',
  29. age: '111'
  30. },
  31. success: function(res) {
  32. console.log(res);
  33. }
  34. })
  35. })
  36. </script>
  37. </body>
  38. </html>

此时会出现跨域问题,我们需要使用 cors 中间件解决跨域问题

  1. cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件,可以很方便地解决跨域问题

  2. 使用步骤

    • 安装中间件: npm install cors

    • 导入中间件: const cors = require('cors')

    • 配置中间件: 在路由之前调用app.use(cors())

  3. express接口案例代码


    
  1. // 导入 express 模块
  2. const express = require('express')
  3. // 创建 express 的服务器实例
  4. const app = express()
  5. // 导入中间件
  6. const cors = require('cors')
  7. // 配置中间件
  8. app.use(cors())
  9. // 配置解析表单数据的中间件
  10. app.use(express.urlencoded({ extended: false }))
  11. // 导入路由模块(被单独分离后导入)
  12. const router = require('./apiRouter')
  13. // 把路由模块,注册到 app 上
  14. app.use('/api', router)
  15. // 调用 app.listen 方法,指定端口号并启动 web 服务器
  16. app.listen(80, () => {
  17. console.log('http://127.0.0.1')
  18. })

apiRouter路由文件代码:


    
  1. const express = require('express');
  2. const router = express.Router();
  3. router.get('/get', (req, res) => {
  4. const query = req.query;
  5. res.send({
  6. status: 0,
  7. msg: 'get请求成功',
  8. data: query
  9. })
  10. })
  11. router.post('/post', (req, res) => {
  12. // const body = req.body; //获取客户端请求的数据
  13. res.send({
  14. status: 0,
  15. msg: 'post请求成功',
  16. data: req.body
  17. })
  18. })
  19. module.exports = router;

在终端中运行express接口代码后打开index.html文件并点击get及post按钮得到请求结果:

 二、通过JSONP中间件解决跨域问题 :

创建 JSONP 接口的注意事项

  1. 如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口

    否则 JSONP 接口会被处理成开启了 CORS 的接口

  2. 实现步骤:

        (1)获取客户端发送过来的回调函数的名字

        (2)得到要通过 JSONP 形式发送给客户端的数据

        (3)根据前两步得到的数据,拼接出一个函数调用的字符串

        (4)把上一步拼接得到的字符串,响应给客户端的 <script> 标签进行解析执行

案例代码如下:


    
  1. //导入express模块
  2. const express = require('express');
  3. //创建express服务器实例
  4. const app = express();
  5. //挂载路由
  6. app.get('/jsonp', (req, res) => {
  7. // 通过解构req.query客户端通过查询字符串的形式发送到客户端的参数fn
  8. const { callback } = req.query
  9. //在服务器端定义一个obj对象
  10. const obj = {
  11. uname: 'zjj',
  12. age: '18'
  13. }
  14. //obj对象转为res.send可处理的字符串形式后从服务器端相应回调函数至客户端
  15. res.send(`${callback}(${JSON.stringify(obj)})`)
  16. })
  17. app.listen(80, () => {
  18. console.log('http://127.0.0.1');
  19. })

创建jsonp.html客户端来接收服务器端响应过来的回调函数,代码如下:

url中callback=fn为客户端发送请求携带的参数 既服务器端中的req.query.callback


    
  1. <script>
  2. function fn(res) {
  3. console.log(res);
  4. }
  5. </script>
  6. <script src="http://127.0.0.1/jsonp?callback=fn"></script>




蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

分享此文一切功德,皆悉回向给文章原作者及众读者.

转自:csdn
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务

移动端、PC端 网页特效

前端达人

移动端网页特效

触屏事件

移动端浏览器兼容性较好,不需要考虑以前 JS 的兼容性问题,可以放心的使用原生 JS 书写效果,但是移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android 和 IOS 都有。

touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。

常见的触屏事件:
在这里插入图片描述
触摸事件对象(TouchEvent)

TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等

touchstart、touchmove、touchend 三个事件都会各自有事件对象。

触摸事件对象常见对象列表:
在这里插入图片描述
因为平时都是给元素注册触摸事件,所以重点记住 targetTocuhes

移动端拖动元素JS代码实现:

// (1) 触摸元素 touchstart:  获取手指初始坐标,同时获得盒子原来的位置 // (2) 移动手指 touchmove:  计算手指的滑动距离,并且移动盒子 // (3) 离开手指 touchend: var div = document.querySelector('div'); var startX = 0; //获取手指初始坐标 var startY = 0; var x = 0; //获得盒子原来的位置 var y = 0; div.addEventListener('touchstart', function(e) { //  获取手指初始坐标 startX = e.targetTouches[0].pageX; startY = e.targetTouches[0].pageY; x = this.offsetLeft; y = this.offsetTop; }); div.addEventListener('touchmove', function(e) { //  计算手指的移动距离: 手指移动之后的坐标减去手指初始的坐标 var moveX = e.targetTouches[0].pageX - startX; var moveY = e.targetTouches[0].pageY - startY; // 移动我们的盒子 盒子原来的位置 + 手指移动的距离 this.style.left = x + moveX + 'px'; this.style.top = y + moveY + 'px'; e.preventDefault(); // 阻止屏幕滚动的默认行为 }); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

classList属性

classList属性是HTML5新增的一个属性。返回元素的类名,该属性用在元素中添加、移除及切换CSS类

<style> .bg { background-color: black; } </style> <body> <div class="one two"></div> <button> 开关灯</button> <script> // classList 返回元素的类名 var div = document.querySelector('div'); // console.log(div.classList[1]); // 1. 添加类名  是在后面追加类名不会覆盖以前的类名 注意前面不需要加. div.classList.add('three'); // 2. 删除类名 div.classList.remove('one'); // 3. 切换类 var btn = document.querySelector('button'); btn.addEventListener('click', function() { document.body.classList.toggle('bg'); }) </script> </body> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

常用开发插件

移动端 要求的是快速开发,所以经常会借助于一些插件来帮完成操作

JS 插件是 js 文件,它遵循一定规范编写,方便程序展示效果,拥有特定功能且方便调用。如轮播图和瀑布流插件

插件的使用:

  1. 引入 js 插件文件
  2. 按照规定语法使用

特点: 它一般是为了解决某个问题而专门存在,其功能单一,并且比较小。比如移动端常见插件:iScroll、Swiper、SuperSlider

PC端网页特效

偏移量系列 offset

offset 翻译过来就是偏移量, 使用 offset 系列相关属性可以 动态 的得到该元素的位置(偏移)、大小等。

  • 获得元素距离带有定位父元素的位置
  • 获得元素自身的大小(宽度高度)
  • 注意: 返回的数值都不带单位

常用属性:
在这里插入图片描述
图示:
在这里插入图片描述

offset与style区别:

offset style
可以得到任意样式表中的样式值 只能得到行内样式表中的样式值
offset系列获得的数值是没有单位的 style.width 获得的是带有单位的字符串
offsetWidth 包含padding+border+width style.width 获得不包含padding和border 的值
offsetWidth 等属性是只读属性,只能获取不能赋值 style.width 是可读写属性,可以获取也可以赋值
获取元素大小位置,用offset更合适 元素更改值,则需要用style改变

案例——获取鼠标在盒子内的坐标:

效果展示:
在这里插入图片描述

实现代码(JS):

// 在盒子内点击, 想要得到鼠标距离盒子左右的距离。 // 首先得到鼠标在页面中的坐标( e.pageX, e.pageY) // 其次得到盒子在页面中的距离(box.offsetLeft, box.offsetTop) // 用鼠标距离页面的坐标减去盒子在页面中的距离, 得到 鼠标在盒子内的坐标 var box = document.querySelector('.box'); box.addEventListener('mousemove', function(e) { // console.log(e.pageX); // console.log(e.pageY); // console.log(box.offsetLeft); var x = e.pageX - this.offsetLeft; var y = e.pageY - this.offsetTop; this.innerHTML = 'x坐标是' + x + ' y坐标是' + y; }) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

案例——模态拖拽框:

  • 点击弹出层, 会弹出模态框, 并且显示灰色半透明的遮挡层。
  • 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层。
  • 鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
  • 鼠标松开,可以停止拖动模态框移动。

效果展示:
在这里插入图片描述

实现代码:

<head lang="en"> <meta charset="UTF-8"> <title></title> <style> .login-header { width: 100%; text-align: center; height: 30px; font-size: 24px; line-height: 30px; } ul,li,ol,dl,dt,dd,div,p,span,h1,h2,h3,h4,h5,h6,a { padding: 0px; margin: 0px; } .login { display: none; width: 512px; height: 280px; position: fixed; border: #ebebeb solid 1px; left: 50%; top: 50%; background: #ffffff; box-shadow: 0px 0px 20px #ddd; z-index: 9999; transform: translate(-50%, -50%); } .login-title { width: 100%; margin: 10px 0px 0px 0px; text-align: center; line-height: 40px; height: 40px; font-size: 18px; position: relative; cursor: move; } .login-input-content { margin-top: 20px; } .login-button { width: 50%; margin: 30px auto 0px auto; line-height: 40px; font-size: 14px; border: #ebebeb 1px solid; text-align: center; } .login-bg { display: none; width: 100%; height: 100%; position: fixed; top: 0px; left: 0px; background: rgba(0, 0, 0, .3); } a { text-decoration: none; color: #000000; } .login-button a { display: block; } .login-input input.list-input { float: left; line-height: 35px; height: 35px; width: 350px; border: #ebebeb 1px solid; text-indent: 5px; } .login-input { overflow: hidden; margin: 0px 0px 20px 0px; } .login-input label { float: left; width: 90px; padding-right: 10px; text-align: right; line-height: 35px; height: 35px; font-size: 14px; } .login-title span { position: absolute; font-size: 12px; right: -20px; top: -30px; background: #ffffff; border: #ebebeb solid 1px; width: 40px; height: 40px; border-radius: 20px; } </style> </head> <body> <div class="login-header"><a id="link" href="javascript:;">点击,弹出登录框</a></div> <div id="login" class="login"> <div id="title" class="login-title">登录会员 <span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span> </div> <div class="login-input-content"> <div class="login-input"> <label>用户名:</label> <input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="list-input"> </div> <div class="login-input"> <label>登录密码:</label> <input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="list-input"> </div> </div> <div id="loginBtn" class="login-button"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div> </div> <!-- 遮盖层 --> <div id="bg" class="login-bg"></div> <script> // 1. 获取元素 var login = document.querySelector('.login'); var mask = document.querySelector('.login-bg'); var link = document.querySelector('#link'); var closeBtn = document.querySelector('#closeBtn'); var title = document.querySelector('#title'); // 2. 点击弹出层这个链接 link  让mask 和login 显示出来 link.addEventListener('click', function() { mask.style.display = 'block'; login.style.display = 'block'; }) // 3. 点击 closeBtn 就隐藏 mask 和 login  closeBtn.addEventListener('click', function() { mask.style.display = 'none'; login.style.display = 'none'; }) // 4. 开始拖拽 // (1) 当我们鼠标按下, 就获得鼠标在盒子内的坐标 title.addEventListener('mousedown', function(e) { var x = e.pageX - login.offsetLeft; var y = e.pageY - login.offsetTop; // (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值 document.addEventListener('mousemove', move) function move(e) { login.style.left = e.pageX - x + 'px'; login.style.top = e.pageY - y + 'px'; } // (3) 鼠标弹起,就让鼠标移动事件移除 document.addEventListener('mouseup', function() { document.removeEventListener('mousemove', move); }) }) </script> </body> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168

可视区系列 client

client 翻译过来就是客户端,使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到该元素的边框大小、元素大小等。

常用属性:
在这里插入图片描述
client和offset最大的区别就是 :不包含边框
图示:
在这里插入图片描述

滚动系列 scroll

scroll 翻译过来就是滚动的,使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。

常用属性:
在这里插入图片描述
图示:
在这里插入图片描述

滚动条:

  • 如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。
  • 当滚动条向下滚动时,页面上面被隐藏掉的高度,称为页面被卷去的头部。
  • 滚动条在滚动时会触发 onscroll 事件。

案例——固定右侧侧边栏:

  • 原先侧边栏是绝对定位
  • 当页面滚动到一定位置,侧边栏改为固定定位
  • 页面继续滚动,会让 返回顶部显示出来

效果展示:
在这里插入图片描述

实现代码:

<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .slider-bar { position: absolute; left: 50%; top: 300px; margin-left: 600px; width: 45px; height: 130px; background-color: pink; } .w { width: 1200px; margin: 10px auto; } .header { height: 150px; background-color: purple; } .banner { height: 250px; background-color: skyblue; } .main { height: 1000px; background-color: yellowgreen; } span { display: none; position: absolute; bottom: 0; } </style> </head> <body> <div class="slider-bar"> <span class="goBack">返回顶部</span> </div> <div class="header w">头部区域</div> <div class="banner w">banner区域</div> <div class="main w">主体部分</div> <script> //1. 获取元素 var sliderbar = document.querySelector('.slider-bar'); var banner = document.querySelector('.banner'); // banner.offestTop 就是被卷去头部的大小 一定要写到滚动的外面 var bannerTop = banner.offsetTop // 当侧边栏固定定位之后应该变化的数值 var sliderbarTop = sliderbar.offsetTop - bannerTop; // 获取main 主体元素 var main = document.querySelector('.main'); var goBack = document.querySelector('.goBack'); var mainTop = main.offsetTop; // 2. 页面滚动事件 scroll document.addEventListener('scroll', function() { // window.pageYOffset 页面被卷去的头部 // console.log(window.pageYOffset); // 3 .当页面被卷去的头部大于等于了 172 此时 侧边栏就要改为固定定位 if (window.pageYOffset >= bannerTop) { sliderbar.style.position = 'fixed'; sliderbar.style.top = sliderbarTop + 'px'; } else { sliderbar.style.position = 'absolute'; sliderbar.style.top = '300px'; } // 4. 当我们页面滚动到main盒子,就显示 goback模块 if (window.pageYOffset >= mainTop) { goBack.style.display = 'block'; } else { goBack.style.display = 'none'; } }) </script> </body> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

三大系列作用区别:
在这里插入图片描述

它们主要用法:

系列 作用 属性
offset 用于获得元素位置 offsetLeft offsetTop
client 用于获取元素大小 clientWidth clientHeight
scroll 用于获取滚动距离 scrollTop scrollLeft

注意:页面滚动的距离通过 window.pageXOffset 获得

动画原理

核心原理:通过定时器 setInterval() 不断移动盒子位置

实现步骤:

  1. 获得盒子当前位置
  2. 让盒子在当前位置加上1个移动距离
  3. 利用定时器不断重复这个操作
  4. 加一个结束定时器的条件
  5. 注意此元素需要添加定位,才能使用 element.style.left

简单动画函数封装:

// 简单动画函数封装obj目标对象 target 目标位置 function animate(obj, target) { var timer = setInterval(function() { if (obj.offsetLeft >= target) { // 停止动画 本质是停止定时器 clearInterval(timer); } //每次均匀向右移动1px obj.style.left = obj.offsetLeft + 1 + 'px'; }, 30); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

缓动效果原理:

缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
  2. 核心算法: (目标值 - 现在的位置 ) / 10 做为每次移动的距离步长
  3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
  4. 注意步长值需要取整
// 缓动动画函数封装obj目标对象 target 目标位置 // 思路: // 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。 // 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长 // 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器 function animate(obj, target) { // 先清除以前的定时器,只保留当前的一个定时器执行 clearInterval(obj.timer); obj.timer = setInterval(function() { // 步长值写到定时器的里面 var step = (target - obj.offsetLeft) / 10; if (obj.offsetLeft == target) { // 停止动画 本质是停止定时器 clearInterval(obj.timer); } // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10 obj.style.left = obj.offsetLeft + step + 'px'; }, 15); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

多个目标值之间移动:

当开始移动时候,判断步长是正值还是负值

  • 如果是正值,则步长 往大了取整
  • 如果是负值,则步长 向小了取整

动画函数封装到单独JS文件: animate.js

function animate(obj, target, callback) { // 先清除以前的定时器,只保留当前的一个定时器执行 clearInterval(obj.timer); obj.timer = setInterval(function() { // 步长值写到定时器的里面 // 把步长值改为整数 不要出现小数的问题 // var step = Math.ceil((target - obj.offsetLeft) / 10); var step = (target - obj.offsetLeft) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); if (obj.offsetLeft == target) { // 停止动画 本质是停止定时器 clearInterval(obj.timer); // 回调函数写到定时器结束里面 // if (callback) { //     // 调用函数 //     callback(); // } callback && callback(); } // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10 obj.style.left = obj.offsetLeft + step + 'px'; }, 15); }

蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

分享此文一切功德,皆悉回向给文章原作者及众读者.

转自:csdn 免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务

展开全文后需要支持收起吗?从运用场景分析交互逻辑

seo达人



图片

展开和收起属于比较细节的问题,按照惯性思维,内容多了我就折叠一部分,用户需要看更多内容就点击「展开」,内容展开之后,相应的还应该支持内容的「收起」。

整个流程这样看起来没什么毛病,但是却缺少一个重要的前提,哪些场景或者什么情况下才需要展开或收起呢?

如果只是单纯给出一个流程,显然缺乏对产品逻辑和用户体验上的思考。可能很多设计师之前没怎么注意过这个地方的交互逻辑,或者深入思考需要注意的问题,这次借着答疑的机会咱们一起来深入探讨下。

 

1、抛开展开和收起

虽然提出的问题是分析展开和收起的运用场景,但是我觉得不应该一上来就陷入到具体的细节里。

这个问题相对来说属于比较小的点,直接钻到细节里会让我们只盯着这一小部分,缺少对全局的思考,因小失大。

盯着已有的设计进行推导,这样多少带有个人主观的臆想。先不要盯着具体的展开和收起功能来想场景,反过来想,在设计过程中有哪些地方需要这样的交互逻辑,为什么需要呢?

无论面对什么样的设计需求,这样的思考过程会一直伴随着我们。

 

2、先场景后行为

先回到场景上,考虑哪些场景会触发「展开和收起」行为,再具体问题具体分析。

场景一:当某段内容字数太多,占据了大面积的空间,如果用户不想看这些长篇大论,想看下一个内容,要滑动屏幕很久才能略过这些内容,显然我们不想给用户带来这样的操作负担,这时候就可以考虑添加收起功能,把多余的内容收起来。

这里的收起需要注意两点:

  • 内容被收起不代表被收起的内容不重要;
  • 收起相对节约屏幕空间,用户更方便上下滑动查看内容。

场景二:页面上仍然有很多内容,但是这些内容有主次之分,我们想让用户最先看到前面的内容,后面的内容可以考虑收起来突出重点并节省空间。

这里的收起就和场景一有差异,可能被收起的内容相对没这么重要,只露出重点/热点内容或权益来吸引用户,相对次要的内容被收起,这里的收起会涉及到内容重要程度的排序,这种排序就可以体现在排行榜设计中。

 

3、展开然后收起

展开和收起属于一组行为,看到展开我们必然会联想到收起。展开是为了看到完整的内容,收起则是内容看完的标志,点击收起可以继续下一个内容。

在知乎问答中,我们首先看到的是标题的问题,这些问题往往言简意赅,通常光看标题我们就能知道这个问题想表达什么意思。

图片

▲ 标题下面的问题简介是对标题的进一步解释和补充,这里的简介作为说明性文字,就支持展开和收起。当简介内容太多时,就要考虑折叠一部分简介,这样能保证在首屏上能同时看到问题和答案。

另外当内容很多,以信息流的形式呈现,并且需要上下滑动来浏览时,就可以考虑使用现展开后收起。

比如在微信朋友圈,如果文字内容较多,就会被收起一部分,想看完整的内容则需要点击「全文」选项,看完后点击「收起」,内容就会被折叠起来,方便我们继续滑动浏览其他朋友圈。

 

4、展开后不收起

点完展开看到了全部内容,却没有收起选项,但是这样并不影响我们的观看体验,而且展开这个动作给用户和页面带来的反应都很小,不会让用户感到不适。之所以选择「展开」,一个重要的原因是因为我们有想看完整内容的强烈需求。在这种意愿的号召下,我们会毫不犹豫地选择展开功能。图片

▲ 在豆瓣电影介绍页中,在剧情简介区域会有「展开」提示,如果我们选择展开,说明我们有观看完整简介的意愿,并且是主动选择展开,不添加「收起」选项正好避免了给我们增加操作上的负担。

另外,当内容相对没那么多,能在一屏上显示,不需要用户上下滑动太多时,可以考虑省去收起,比如在QQ群介绍、抖音个人页的简介等。

 

最后

以上是我对展开和收起的运用场景的一些小思考,主要从场景角度来考虑交互逻辑。

授人以鱼不如授人以渔,关于展开和收起的场景还有很多,希望大家能掌握这样的思考逻辑,去发掘更深层的设计内容,当然有问题咱们可以随时沟通~

 

原文地址:Clip设计夹(公众号)

作者:Clippp

转载请注明:学UI网》展开全文后需要支持收起吗?从运用场景分析交互逻辑

蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。

截屏2021-05-13 上午11.41.03.png

分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。

蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务




日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档