这是一个最基本的左侧菜单栏,实现的过程很简单,官方的说明文档就有,但是我在导入layer.js之后,直接复制这段官方代码到我的编辑器上时,发现页面是这样的:
发现,绑定属性的菜单并没有下拉选项,这个问题在我导入layer.all.js之后解决了,而且发现如果是在页面的最上方导入的话也没有下拉选项,只有在html代码下面导入,才可以显示 ,不知道是什么原因。
页面截图:
tab项右键菜单:
这里右键菜单的样式并没有做太多的美化。
html代码:(页面中关于引入js和css文件的部分被我省略了,还有要注意jQuery的引入顺序)
<div class="layui-tab layui-tab-card site-demo-button" style="position: relative;">
<ul class="layui-nav layui-nav-tree layui-nav-side">
<li class="layui-nav-item layui-nav-itemed">
<a href="javascript:;">默认展开</a>
<dl class="layui-nav-child">
<dd>
<a data-url="a" data-id="11" data-title="选项a" href="#" class="site-demo-active" data-type="tabAdd">选项a</a>
</dd>
<dd>
<a href="#" data-url="b" data-title="选项b" data-id="22" class="site-demo-active" data-type="tabAdd">选项b</a>
</dd>
<dd>
<a href="">跳转</a>
</dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">解决方案</a>
<dl class="layui-nav-child">
<dd>
<a href="">移动模块</a>
</dd>
<dd>
<a href="">后台模版</a>
</dd>
<dd>
<a href="">电商平台</a>
</dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="#" data-url="c" data-title="选项c" data-id="33" class="site-demo-active" data-type="tabAdd">产品c</a>
</li>
<li class="layui-nav-item">
<a href="">大数据</a>
</li>
</ul>
<div class="layui-tab" lay-filter="demo" lay-allowclose="true" style="margin-left: 200px;">
<ul class="layui-tab-title">
</ul>
<ul class="rightmenu" style="display: none;position: absolute;">
<li data-type="closethis">关闭当前</li>
<li data-type="closeall">关闭所有</li>
</ul>
<div class="layui-tab-content">
</div>
</div>
</div>
js代码:
layui.use('element', function() {
var $ = layui.jquery;
var element = layui.element; //Tab的切换功能,切换事件监听等,需要依赖element模块
//触发事件
var active = {
//在这里给active绑定几项事件,后面可通过active调用这些事件
tabAdd: function(url,id,name) {
//新增一个Tab项 传入三个参数,分别对应其标题,tab页面的地址,还有一个规定的id,是标签中data-id的属性值
//关于tabAdd的方法所传入的参数可看layui的开发文档中基础方法部分
element.tabAdd('demo', {
title: name,
content: '<iframe data-frameid="'+id+'" scrolling="auto" frameborder="0" src="'+url+'.html" style="width:100%;height:99%;"></iframe>',
id: id //规定好的id
})
CustomRightClick(id); //给tab绑定右击事件
FrameWH(); //计算ifram层的大小
},
tabChange: function(id) {
//切换到指定Tab项
element.tabChange('demo', id); //根据传入的id传入到指定的tab项
},
tabDelete: function (id) {
element.tabDelete("demo", id);//删除
}
, tabDeleteAll: function (ids) {//删除所有
$.each(ids, function (i,item) {
element.tabDelete("demo", item); //ids是一个数组,里面存放了多个id,调用tabDelete方法分别删除
})
}
};
//当点击有site-demo-active属性的标签时,即左侧菜单栏中内容 ,触发点击事件
$('.site-demo-active').on('click', function() {
var dataid = $(this);
//这时会判断右侧.layui-tab-title属性下的有lay-id属性的li的数目,即已经打开的tab项数目
if ($(".layui-tab-title li[lay-id]").length <= 0) {
//如果比零小,则直接打开新的tab项
active.tabAdd(dataid.attr("data-url"), dataid.attr("data-id"),dataid.attr("data-title"));
} else {
//否则判断该tab项是否以及存在
var isData = false; //初始化一个标志,为false说明未打开该tab项 为true则说明已有
$.each($(".layui-tab-title li[lay-id]"), function () {
//如果点击左侧菜单栏所传入的id 在右侧tab项中的lay-id属性可以找到,则说明该tab项已经打开
if ($(this).attr("lay-id") == dataid.attr("data-id")) {
isData = true;
}
})
if (isData == false) {
//标志为false 新增一个tab项
active.tabAdd(dataid.attr("data-url"), dataid.attr("data-id"),dataid.attr("data-title"));
}
}
//最后不管是否新增tab,最后都转到要打开的选项页面上
active.tabChange(dataid.attr("data-id"));
});
function CustomRightClick(id) {
//取消右键 rightmenu属性开始是隐藏的 ,当右击的时候显示,左击的时候隐藏
$('.layui-tab-title li').on('contextmenu', function () { return false; })
$('.layui-tab-title,.layui-tab-title li').click(function () {
$('.rightmenu').hide();
});
//桌面点击右击
$('.layui-tab-title li').on('contextmenu', function (e) {
var popupmenu = $(".rightmenu");
popupmenu.find("li").attr("data-id",id); //在右键菜单中的标签绑定id属性
//判断右侧菜单的位置
l = ($(document).width() - e.clientX) < popupmenu.width() ? (e.clientX - popupmenu.width()) : e.clientX;
t = ($(document).height() - e.clientY) < popupmenu.height() ? (e.clientY - popupmenu.height()) : e.clientY;
popupmenu.css({ left: l, top: t }).show(); //进行绝对定位
//alert("右键菜单")
return false;
});
}
$(".rightmenu li").click(function () {
//右键菜单中的选项被点击之后,判断type的类型,决定关闭所有还是关闭当前。
if ($(this).attr("data-type") == "closethis") {
//如果关闭当前,即根据显示右键菜单时所绑定的id,执行tabDelete
active.tabDelete($(this).attr("data-id"))
} else if ($(this).attr("data-type") == "closeall") {
var tabtitle = $(".layui-tab-title li");
var ids = new Array();
$.each(tabtitle, function (i) {
ids[i] = $(this).attr("lay-id");
})
//如果关闭所有 ,即将所有的lay-id放进数组,执行tabDeleteAll
active.tabDeleteAll(ids);
}
$('.rightmenu').hide(); //最后再隐藏右键菜单
})
function FrameWH() {
var h = $(window).height() -41- 10 - 60 -10-44 -10;
$("iframe").css("height",h+"px");
}
$(window).resize(function () {
FrameWH();
})
});
tab项是放在<div class="layui-tab" lay-filter="demo" lay-allowclose="true" style="margin-left: 200px;"> 这个div中的,其中有一个重要的属性lay-filter,在js中调用的tabAdd,tabDelete等多种方法都携带了这个参数,我对此的理解是相当于一个判断拦截功能,将tab项放在lay-filter=‘demo’的div中。可以借助该参数,完成指定元素的局部更新。
其中还有关于element的操作,var element = layui.element
element模块的实例
返回的element变量为该实例的对象,携带一些用于元素操作的基础方法,我们就是用这些方法进行tab项的新增和删除还有切换。
这是element 中的tabAdd方法,其中的content可以是一个iframe页面,在此例中,我就是传递了一个简单的页面,这就实现了不同页面间的一些切换。
element.tabAdd('demo', {
title: '选项卡的标题'
,content: '选项卡的内容' //支持传入html
,id: '选项卡标题的lay-id属性值'
});
————————————————
版权声明:本文为CSDN博主「nb7474」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/nb7474/article/details/79413460/
要实现JavaScript的拖拽效果,首先我们需要知道事件对象几个有关于实现拖拽效果的坐标
获取事件对象 var e = e || window.event;
根据需求需要用到的拖拽效果的坐标
clientX:鼠标点击位置相对于浏览器可视区域的水平偏移量(不会计算水平滚动的距离)
clientY:鼠标点击位置相对于浏览器可视区域的垂直偏移量(不会计算垂直滚动条的距离)
offsetX:鼠标点击位置相对于触发事件对象的水平距离
offsetY:鼠标点击位置相对于触发事件对象的垂直距离
pageX:鼠标点击位置相对于网页左上角的水平偏移量,也就是clientX加 上水平滚动条的距离
pageY:鼠标点击位置相对于网页左上角的垂直平偏移量,也就是clientY加上垂直滚动条的距离
offsetLeft:如果父元素中有定位的元素,那么就返回距离当前元素最近的定位元素边缘的距离
offsetTop:如果父元素中没有定位元素,那么就返回相对于body左边缘距离
获取元素自身大小:offsetWidth和offsetHeight / clientWidth和clientHeight
offsetWidth和clientWidth的区别:就是offsetWidth包含边框,clientWidth不包含边框
实现拖拽需要用到:clientWidth、clientHeight、clientX、clientY、offsetLeft、offsetTop
首先搭建好html结构和css样式
<div class="wrap">
<div class="cover">
</div>
</div>
* {
margin: 0;
padding: 0;
}
.wrap {
width: 500px;
height: 500px;
border: 1px solid deeppink;
position: relative;
margin: 50px auto;
}
.cover {
width: 150px;
height: 150px;
background: rgba(200, 7, 99, 0.5);
display: none;
position: absolute;
left: 0;
top: 0;
cursor: move;
}
注意:需要给大盒子和小盒子进行定位:子绝父相
<script>
var wrap = document.querySelector(".wrap");
var cover = document.querySelector(".cover");
wrap.onmouseover = function() {
cover.style.display = "block";
wrap.onmousemove = function(e) {
var e = e || window.event;
var x1 = e.clientX;
var y1 = e.clientY;
//这里获取到的e.clientX和e.clientY,可以看情况和需求改为e.pageX和e.pageY
var halfWidth = cover.clientWidth / 2;
var halfHeight = cover.clientHeight / 2;
var wrapLeft = wrap.offsetLeft;
var wrapTop = wrap.offsetTop;
var l = x1 - wrapLeft - halfWidth;
var t = y1 - wrapTop - halfHeight;
if (l <= 0) {
l = 0
}
if (l >= wrap.clientWidth - cover.clientWidth) {
l = wrap.clientWidth - cover.clientWidth
}
if (t <= 0) {
t = 0
}
if (t >= wrap.clientHeight - cover.clientHeight) {
t = wrap.clientHeight - cover.clientHeight
}
cover.style.left = l + "px";
cover.style.top = t + "px"
}
}
wrap.onmouseout = function() {
cover.style.display = "none";
}
</script>
var halfWidth = cover.clientWidth / 2;
var halfHeight = cover.clientHeight / 2;
var wrapLeft = wrap.offsetLeft;
var wrapTop = wrap.offsetTop;
var l = x1 - wrapLeft - halfWidth;
var t = y1 - wrapTop - halfHeight;
//限制范围
if (l <= 0) {
l = 0
}
if (l >= wrap.clientWidth - cover.clientWidth) {
l = wrap.clientWidth - cover.clientWidth
}
if (t <= 0) {
t = 0
}
if (t >= wrap.clientHeight - cover.clientHeight) {
t = wrap.clientHeight - cover.clientHeight
}
注意:这里要限制小盒子在大盒子之间移动的范围,左上角的限制,当小盒子超出范围时,将0赋值给l和t。右下角小盒子移动的范围在大盒子宽度减去小盒子的宽度。
css面试题@响应式布局如何实现
1为什么要使用响应式布局
响应式布局可以让网站同时适配不同分辨率和不同的手机端,让客户有更好
的体验。
2如何实现
方案一:百分比布局
利用对属性设置百分比来适配不同屏幕,注意这里的百分比是相对于父元素; 能够设置的属性有width,、height、padding、margin,其他属性比如border、 font-size不能用百分比来设置的
由于没办法对font-size进行百分比设置,所以用的最多就是对图片和大块布局进行百分比设置。
方案二:使用媒体查询 (CSS3@media 查询)
利用媒体查询设置不同分辨率下的css样式,来适配不同屏幕。
媒体查询相对于百分比布局,可以对布局进行更细致的调整,但需要在每个分辨率下面都写一套css样式。
该布局的话适用于简单的网页,可以使移动端和pc端使用一套网址。从而节约成本。也方便后期的维护,bootcss就是用了CSS3的media来实现响应的 但是相对于复杂的网页就不适合了(如:淘宝,京东)等等
方案三.rem 响应式布局
rem布局的原理
rem:相对于根元素(即html元素)font-size计算值的倍数。
如 html的font-size为100px;那么2rem就为200px。
通俗的来讲的话rem就是通过JavaScript来获取页面的宽度,从而动态的计算rem。这样就会使不同分辨率下展现出相同的效果。
//代码展示
css面试题@响应式布局如何实现 1为什么要使用响应式布局 响应式布局可以让网站同时适配不同分辨率和不同的手机端,让客户有更好 的体验。 2如何实现 方案一:百分比布局 利用对属性设置百分比来适配不同屏幕,注意这里的百分比是相对于父元素; 能够设置的属性有width,、height、padding、margin,其他属性比如border、 font-size不能用百分比来设置的 由于没办法对font-size进行百分比设置,所以用的最多就是对图片和大块布局进行百分比设置。 方案二:使用媒体查询 (CSS3@media 查询) 利用媒体查询设置不同分辨率下的css样式,来适配不同屏幕。 媒体查询相对于百分比布局,可以对布局进行更细致的调整,但需要在每个分辨率下面都写一套css样式。 该布局的话适用于简单的网页,可以使移动端和pc端使用一套网址。从而节约成本。也方便后期的维护,bootcss就是用了CSS3的media来实现响应的 但是相对于复杂的网页就不适合了(如:淘宝,京东)等等 方案三.rem 响应式布局 rem布局的原理 rem:相对于根元素(即html元素)font-size计算值的倍数。 如 html的font-size为100px;那么2rem就为200px。 通俗的来讲的话rem就是通过JavaScript来获取页面的宽度,从而动态的计算rem。这样就会使不同分辨率下展现出相同的效果。 //代码展示上述代码中 7.5为动态的值,根据设计图的宽度进行动态的改变。window.onresize的作用是:当页面的宽度或高度发生改变时,再次进行调用rem函数。
如何在网页前端里可视化你的知识图谱
最近费尽千辛万苦构造了一份可以用(大概)的知识图谱,并且把要利用知识图谱做的领域命名实体识别和一些推荐的功能做成Web版的demo,顺带想实现一些可视化知识图谱的功能。
(凭啥知识图谱就只能在Neo4j里自嗨,不能来前端show一下,歧视吗(¬_¬))
找了做前端图表展示的开源库,D3.js和Echarts都能做,我拿Echarts实现了一下功能,先看一下在现在项目里一个基于知识图谱查询的实际效果:

接下里看看如何的实现:
<script src="/static/js/echarts.common.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@4.5.0/dist/echarts.min.js"></script>
给要展示的图准备一个Dom:
<!-- 为ECharts准备一个具备大小的Dom -->
<div class = "col-md-12">
<div class="panel panel-default ">
<header class="panel-heading">
关系图 :
</header>
<div class = "panel-body ">
<div id="graph" style="width: 100%;height:600px;"></div>
</div>
</div>
</div>
data = [
{name:'苹果',category:1,id:0},
{name:'梨子',catagory:1,id:1},
{name:'水果',category:2,id:2}
]
links = [
{source:0,target:2,category:0,value:'属于',symbolSize:10},
{source:1,target:2,category:0,value:'属于',symbolSize:10}
]
var myChart = echarts.init(document.getElementById('graph'));
option = {
title: {
text: ''
},
tooltip: {},
animationDurationUpdate: 1500,
animationEasingUpdate: 'quinticInOut',
label: {
normal: {
show: true,
textStyle: {
fontSize: 12
},
}
},
legend: {
x: "center",
show: false
},
series: [
{
type: 'graph',
layout: 'force',
symbolSize: 45,
focusNodeAdjacency: true,
roam: true,
edgeSymbol: ['none', 'arrow'],
categories: [{
name: '查询实体',
itemStyle: {
normal: {
color: "#009800",
}
}
}, {
name: 'instance',
itemStyle: {
normal: {
color: "#4592FF",
}
}
}, {
name: 'class',
itemStyle: {
normal: {
color: "#C71585",
}
}
}],
label: {
normal: {
show: true,
textStyle: {
fontSize: 12,
},
}
},
force: {
repulsion: 1000
},
edgeSymbolSize: [4, 50],
edgeLabel: {
normal: {
show: true,
textStyle: {
fontSize: 10
},
formatter: "{c}"
}
},
data: data,
links: links,
lineStyle: {
normal: {
opacity: 0.9,
width: 1.3,
curveness: 0,
color:"#262626",
}
}
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
这样就成功实现了一个简单的图谱可视化:
————————————————
版权声明:本文为CSDN博主「游离态GLZ不可能是金融技术宅」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37477357/article/details/104857495
1.实现注册与登录功能:
要求用到验证码,登录后该出不再显示登录与注册,而是显示用户名。
2.实现预约功能:
实现“运动”与“学习”两方面的邀约功能,并将邀约数据保存到数据库中,数据库使用Mysql。
运动可以邀约:篮球、足球、乒乓球等
学习可以邀约:自习、辅导等
在正式开始编码之前,需要先导入相关jar包并配置好相关的配置文件,同时也需要导入前端给的页面代码。接着是建立好相应的数据库结构,并初步构建好三层架构的想法思路。

web层
service层
dao层
然后在这里写一下对于验证码处理和点击图片更换验证码功能的一些细节。
如下图所示,为了保证servlet层能够正确接收到界面所展示的验证码信息,同时因为session对象是项目自生成的,所以我直接获取session对象并以类似于键值对的形式将页面验证码信息存入session中。
接下来是jsp页面对于验证码展示的一些处理
在这里,因为点击验证码更换操作需要用到js,所以我百度得到了上图这样一串代码。其中时间戳的作用是通过改变每次请求的时间参数来获得刷新效果,即改变url中的响应头信息。
2.注册功能
事实上,有了登录功能的实现,注册功能的代码与其都是大同小异的。最大的不同应该就是Dao层执行的操作不同

预约功能的实现更是与注册功能的实现基本一致。所不同的应该是jsp页面对单选框输入的设置。
首先在写代码之前我们需要理清如何穿插图片呢?
可以让所有图片都float:left,用一个大盒子装进所有图片,在用一个小盒子显示图片,溢出图片就hidden,之后以每张图片的宽度来scrollLeft.
可以给每张图片一个name/id,用循环遍历所有图片
可以用float:left,但是除了我要显示的图片外,其他图片都hidden,之后每当我需要某张图片时,我就把它制定到某位置
…
在这里,我将用第二种方法,因为它很直观明了,我要哪张图片我就调哪张图片。
HTML部分:在div里面我装了一张图片,name:0, 这是为了在刚打开的时候,我们的页面是有东西的而不是一片空白。其他部分都好理解,不理解的可在下方评论。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>轮播图</title>
<link rel="stylesheet" href="轮播图.css" />
<script src="轮播图.js">
</script>
</head>
<body>
<header>
<div id="oImg">
<!-- 轮流播放图片 -->
<img id="insert" src="img/轮1.jpg" name="0"/>
</div>
<!-- 左右切换图片 -->
<p id="left" οnclick="goBack()"></p>
<p id="right" οnclick="goForward()"></p>
<ul id="nav">
<!-- 指定某张图片 -->
<li id="1" οnclick="move(this)">1</li>
<li id="2" οnclick="move(this)">2</li>
<li id="3" οnclick="move(this)">3</li>
<li id="4" οnclick="move(this)">4</li>
<li id="5" οnclick="move(this)">5</li>
</ul>
</header>
</body>
</html>
CSS:
* { margin: 0 auto; padding: 0 auto; } header { width: 100%; height: 680px; position: relative; } img { width: 100%; height: 680px; } #nav { position: absolute; bottom: 5px; left: 30%; } #nav li { width: 30px; height: 30px; line-height: 30px; text-align: center; background: #ccc; font-size: 24px; border-radius: 9px; color: darkslategrey; font-family: 'Times New Roman', Times, serif; margin: 0 25px; float: left; cursor: pointer; list-style: none; } #nav li:hover { background: peru; } #left { width: 25px; height: 24px; left: 0; top: 50%; cursor: pointer; position: absolute; background: url(img/fx1.png); } #right { width: 25px; height: 24px; right: 0; top: 50%; cursor: pointer; position: absolute; background: url(img/fx2.png); }之后我们来看重中之重JS部分
JavaScript:
// 五张图片的url var oImg1 = "img/轮1.jpg"; var oImg2 = "img/轮2.jpg"; var oImg3 = "img/轮3.jpg"; var oImg4 = "img/轮4.jpg"; var oImg5 = "img/轮5.jpg"; // 把5张图片存入一个数组 var arr = [oImg1, oImg2, oImg3, oImg4, oImg5]; window.onload = function() { //刚加载时第一张图片1号背景颜色 document.getElementById("1").style.background = "peru"; run() } //轮播 function run() { timer = setInterval(function() { //随机点数字时能接着变化 var pic = document.getElementById("insert").name; var shade = document.getElementById("insert"); //如果为最后一张图片则重新循环 if (pic == 4) { pic = -1; } //点一个数字该数字背景颜色变化其余的不变 var aLi = document.getElementsByTagName("li"); for (var j = 0; j < aLi.length; j++) { aLi[j].style.backgroundColor = "#CCCCCC"; } var i = parseInt(pic); document.getElementById("insert").src = arr[i + 1]; document.getElementById("insert").name = i + 1; //数字随图片变化 switch (i) { case 0: var temp = '2'; break; case 1: var temp = '3'; break; case 2: var temp = '4'; break; case 3: var temp = '5'; break; case -1: var temp = '1'; break; } document.getElementById(temp).style.background = "peru" }, 5000) } //右箭头 function goForward() { var temp = document.getElementById("insert").name; var oBox = document.getElementById("insert"); var aLi = document.getElementsByTagName("li"); // 数字跟着图片一起变 for (var i = 0; i < aLi.length; i++) { aLi[i].style.backgroundColor = "#CCCCCC"; } switch (temp) { case "0": var n = '2'; break; case "1": var n = '3'; break; case "2": var n = '4'; break; case "3": var n = '5'; break; case "4": var n = '1'; break; } document.getElementById(n).style.background = "peru" // 向右移动图片 for (var j = 0; j < arr.length; j++) { if (j < 4) { if (temp == j) { oBox.src = arr[j + 1]; } } else { if (temp == 4) { oBox.src = arr[0]; } } } // 轮到最后一张图片时返回第一张 if (temp < 4) { oBox.name = parseInt(temp) + 1; } else { oBox.name = 0; } } //左箭头 function goBack() { var temp = document.getElementById("insert").name; var oBox = document.getElementById("insert") var aLi = document.getElementsByTagName("li"); // 图片移动时数字也跟着变 for (var i = 0; i < aLi.length; i++) { aLi[i].style.backgroundColor = "#CCCCCC"; } switch (temp) { case "0": var n = '5'; break; case "1": var n = '1'; break; case "2": var n = '2'; break; case "3": var n = '3'; break; case "4": var n = '4'; break; } document.getElementById(n).style.background = "peru" // 向左移动图片 for (var j = 0; j < arr.length; j++) { if (j > 0) { if (temp == j) { oBox.src = arr[j - 1]; } } else { if (temp == 0) { oBox.src = arr[4]; } } } // 轮到第一张图片时返回最后一张 if (temp > 0) { oBox.name = parseInt(temp) - 1; } else { oBox.name = 4; } } //指定图片 function move(num) { var oBox = document.getElementById("insert"); var temp = document.getElementById("insert").name; var aLi = document.getElementsByTagName("li"); for (var i = 0; i < aLi.length; i++) { aLi[i].style.backgroundColor = "#CCCCCC"; } document.getElementById(num.innerHTML).style.background = "peru" switch (num.innerHTML) { case "1": oBox.src = arr[0]; oBox.name = 0; break; case "2": oBox.src = arr[1]; oBox.name = 1; break; case "3": oBox.src = arr[2]; oBox.name = 2; break; case "4": oBox.src = arr[3]; oBox.name = 3; break; case "5": oBox.src = arr[4]; oBox.name = 4; break; } }JavaScript部分我写的很详细,仔细看的话是可以看懂的,主要分3个重要部分:
用src来调用每张图片并给每张图片一个name,这样方便后面的重复使用
为下方的数字按钮匹配图片,点击1跳到第1张图片,点击2跳到第2张图片…因为我把所有的图片都存在了一个数组里,所以在匹配的时候要注意数组0位置才是数字1指定的图片
可以来回翻页,当到达最后一张图片时,我再点击下一张图片又能返回到第一张图片了,亦或者当我点击到第一张图片时,再上一张图片又回到第五张图片了
效果如下:
大家有问题可以在下方评论哦,看到了会及时回复哒!
————————————————
版权声明:本文为CSDN博主「weixin_43964414」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43964414/article/details/104844041
CSS介绍
整理完了HTML的笔记,接下来就是CSS了。我们可以使用HTML构建稳定的结构基础,而页面的风格样式控制则交给CSS来完成。网页的样式包括各种元素的颜色、大小、线形、间距等等,这对于设计或维护一个数据较多的网站来说,工作量是巨大的。好在可以使用CSS来控制这些样式,这将大大提高网页设计和维护的效率,并且使网页的整体风格很容易做到统一。
CSS概述
CSS是英文Cascading Style Sheet的缩写,中文译为层叠样式表,也有人翻译为级联样式表,简称样式表。它是一种用来定义网页外观样式的技术,在网页中引入CSS规则,可以快捷地对页面进行布局设计,可以的控制HTML标记对象的宽度、高度、位置、字体、背景等外观效果。
CSS是一种标识性语言,不仅可以有效的控制网页的样式,更重要的是实现了网页内容与样式的分离,并允许将CSS规则单独存放于一个文档中, CSS文件的扩展名为“css”。
CSS3
CSS3标准早在1995年就开始制订, 2001年提上W3C研究议程,但是,10年来CSS3可以说是基本上没有什么很大的变化,一直到2011年6月才发布了全新版本的CSS3,目前,许多浏览器都广泛支持CSS3。
CSS3是CSS技术的一个升级版本,CSS3语言将CSS划分为更小的模块,在朝着模块化的方向发展。以前的版本是一个比较庞大而且比较复杂模块,所以,把它分解成为一个个小的简单的模块,同时也加入了更多新的模块。在CSS3中有字体、颜色、布局、背景、定位、边框、多列、动画、用户界面等等多个模块。
CSS的基本用法
CSS的使用规则由两部分组成:选择器和一条或多条声明。其基本基本语法如下:
选择器{ 属性1: 值; 属性2: 值; …
属性n: 值; }
CSS的使用规则由两部分组成:选择器和一条或多条声明。其基本基本语法如下:
选择器{ 属性1: 值; 属性2: 值; …
属性n: 值; }
CSS属性
CSS的属性按照相关功能进行了分组,包含了字体、文本、背景、列表、动画等多个分组,这些属性的具体使用方法和示例将会在后续中提到。
在HTML文档中使用CSS的方法
根据CSS在HTML文档中的使用方法和作用范围不同,CSS样式表的使用方法分为三大类:行内样式、内部样式表和外部样式表,而外部样式表又可分为链入外部样式表和导入外部样式表。本节我们从四个分类来认识在HTML中使用CSS的方法。
行内样式
内部样式表
外部样式表
链入外部样式表
导入外部样式表
行内样式
行内样式(inline style),也叫内联样式,它是CSS四种使用方法中最为直接的一种,它的实现借用HTML元素的全局属性style,把CSS代码直接写入其中即可。
严格意义上行内样式是一种不严谨的使用方式,它不需要选择器,这种方式下CSS代码和HTML代码混合在一起,因此不推荐使用行内样式。行内样式的基本语法如下:
<标记 style="属性:值; 属性:值; …">
当单个文档需要特殊的样式时,应该使用内部样式表。内部样式表是将样式放在页面的head区里,这样定义的样式就应用到本页面中了,内部样式表使用style标记进行声明,是较为常用的一种使用方法。其基本语法如下:
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
选择器1{属性:值;…}
选择器2{属性:值;…}
……
选择器n{属性:值;…}
</style>
</head>
<head>
<meta charset="utf-8" />
<title></title>
<link href="样式表路径" rel="stylesheet" type="text/css" />
</head>
其中:
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
@import url("样式表路径");
</style>
</head>
其中:
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
@import url("样式表路径");
</style>
</head>
记录仓促,遗漏之处日后补充,如有错误或不足之处,还望指正
————————————————
版权声明:本文为CSDN博主「狗狗狗狗狗乐啊」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44122062/article/details/104848745
文章目录
前言
一、白屏时间过长分析
二、针对性优化
针对animate.css
针对mint-ui的优化
针对图片的优化
三、webpack打包优化与分析
webpack-bundle-analyzer打包分析
打包优化
四、优化后线上测试速度提升
五、优化总结
前言
最近在做项目时,测试提出了在App端的H5项目入口加载时间过长,白屏等待过久,需要优化的问题,于是着手开始分析:
项目技术栈:基于Vue全家桶做的一个移动端类似WebApp的项目,使用到的第三方库有:mint-ui, echarts,momentjs。
项目痛点:白屏时间过长
一、白屏时间过长分析
通过访问线上环境,结合Chrome devtool中Network和Performance功能可以具体分析整个白屏的耗时主要在哪一块儿
Network耗时记录:
Performance性能面板
通过上面两张图分析,从浏览器发起请求到解析HTML完成这一过程中:
animate.css, mini-ui.css的请求耗时最长。
图片过大耗时。
二、针对性优化
针对animate.css
animate.css由于使用的是第三方CDN(国外服务器)所有请求时间相对较长,所以如果必须要用animate.css那么可以下载下来作为本地资源,也可以使用国内CDN,或者不用animate.css,而是针对使用到的几个CSS动画,直接自己造轮子
针对mint-ui的优化
由于mint-ui在原项目中使用的是全局引用的方式,这才导致打包资源过大,css单独请求耗时过长的问题,所以主要解决方案是按需引入mint-ui,借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
安装babel-plugin-component, 若已安装可忽略
修改 .babelrc (重点在plugins中):
{
"presets": [
["env", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime",["component", [
{
"libraryName": "mint-ui",
"style": true
}
]]],
"comments": false,
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul" ]
}
}
}
在main.js中引用使用到的插件
import Vue from 'vue'
import { Button, Cell } from 'mint-ui'
import 'mint-ui/lib/style.min.css' // 引用CSS
import App from './App.vue'
Vue.component(Button.name, Button)
Vue.component(Cell.name, Cell)
/* 或写为
* Vue.use(Button)
* Vue.use(Cell)
*/
new Vue({
el: '#app',
components: { App }
})
在使用的组件中改为按需引用组件
import Vue from 'vue'
var Popup = Vue.component('mt-popup')
var Swipe = Vue.component('mt-swipe')
var SwipeItem = Vue.component('mt-swipe-item')
export default {
name:'my-component',
components:{
Popup,
Swipe,
SwipeItem
}
}
此按需引入方案也同样适用于其他第三方UI组件库
图片小图通过webpack可以直接转为base64,而大图可以通过压缩或者换格式的方式来优化,这里推荐一个好用的图片压缩工具,工具:tinyPNG,如下是图片转换前后对比
在完成了上述优化以后,下面着重关注下webpack打包后生成的文件大小,看还有没有可以优化的余地。由于项目中已经通过路由按需加载的方式来做了功能拆分,所以通过webpack打包后生成了很多分散的js文件,如下图:
通过上图分析可以知道打包后有几个文件相对较大,vendor.js都知道是第三方库文件打包形成,之前通过mint-ui按需加载会有一定的变化,后面记录。这里着重看另两个带hash的js文件,这里并看不出来它为什么这么大,所以这里需要用到webpack打包分析工具来做进一步的打包文件分析:webpack-bundle-analyzer
它的作用如下图,即在打包后生成打包文件大小分析图,方便我们更加直观的看到文件大小和引用情况
npm intall -D webpack-bundle-analyzer
webpack.pro.conf.js. (这里由于只是用于生产打包分析且是通过vue-cli生成的项目框架)
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
// ...
plugins:[
new BundleAnalyzerPlugin()
]
}
运行npm run build,(webpack默认会在打包完成时生成分析图)
版权声明:本文为CSDN博主「Sophie_U」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Sophie_U/article/details/104840167
数据类型的转化(JavaScript)—自动转化和强制转化
这一周,我来分享一下在JavaScript中的数据类型转化。
首先我们要知道在JavaScript中的数据类型有什么?在这里我就不详细介绍了,帮你总结好了。
1.布尔类型-----Boolean---isNaN()
用来判断一个变量是否为非数字的类型,是数字返回false,不是数字返回true。
2.数值类型-----Number
存储时,是按照二进制数值存储,输出时,默认都是按照十进制数值输出。
在JavaScript中二进制前加0b/0B,八进制前面加0 ,十六进制前面加0x。
如果需要按照原始进制数值输出,用格式为:
变量名称.toString(进制) ;
注意的是:S必须大写,将数值转化为字符串形式输出
如:console.log( a.toString(2) );将a转换成2进制的形式输出。
3.字符串类型-----String
JavaScript可以用单引号嵌套双引号, 或者用双引号嵌套单引号(外双内单,外单内双)
字符串是由若干字符组成的,这些字符的数量就是字符串的长度。
通过字符串的length属性可以获取整个字符串的长度。
例子:var str = 'my name is xiaoming';
console.log(str.length);
输出的结果是19。可以知道空格也代表一个字符。
4.undefined
表示没有数值-----应该有数值,但是现在没有数值
5.null
表示数值为空-----表示有数值,但是数值是“空”
上面就是数据类型的五种形式。那么它是如何转化呢?听我详细给你讲解。
在 JavaScript 程序中 , 变量中存储的数据类型没有限制,也就是在变量中可以存储任何符合JavaScript语法规范的数据类型。但是在 JavaScript 程序的执行过程中,往往需要变量中存储的数值是某种特定的数据类型,别的数据类型不行,此时就需要进行数据类型的转化。
————————————————
版权声明:本文为CSDN博主「什什么都绘」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39406353/article/details/104864224上面就是数据类型的五种形式。那么它是如何转化呢?听我详细给你讲解。
在 JavaScript 程序中 , 变量中存储的数据类型没有限制,也就是在变量中可以存储任何符合JavaScript语法规范的数据类型。但是在 JavaScript 程序的执行过程中,往往需要变量中存储的数值是某种特定的数据类型,别的数据类型不行,此时就需要进行数据类型的转化。
JavaScript中数据类型的转化,分为自动转化和强制转化:
自动转化是计算机程序,自动完成的转化。
强制转化是程序员,强行完成的转化
1.布尔类型的自动转化:
在 执行 if 判断时 ,其他数据类型会自动转化为布尔类型
其他类型转化为布尔类型的原则
0 '' undefined null NaN 这五种情况转化为false
特别提醒 0.0 0.00000 都算是0
其他的所有都会转化为 true
2.字符串的自动转化:
执行字符串拼接, +号的两侧,应该都是字符串类型,会将其他数据类型转化为字符串类型
转化原则:
//基本数据类型 / 简单数据类型------将数据数值直接转化为字符串 , 然后执行拼接操作
布尔值 true ---> 字符串 'true'
布尔值 false ---> 字符串 'fasle'
undefined ---> 字符串 'undefined'
unll ---> 字符串 'null'
数值 ---> 将数值解析转化为'对应的纯数字的字符串'
// 引用数据类型 / 复杂数据类型
数组 ---> 将 [] 中的内容,转化为字符串的形式,执行拼接操作
对象 ---> 任何对象,任何内容,都会转化为 [object Object] 固定的内容形式,执行拼接操作
函数 ---> 将所有的程序代码,转化为字符串,执行拼接操作
3.数值的自动转化:
在执行运算时,会触发数据类型的自动转化。
转化原则:
布尔类型 : true ---> 1
false ---> 0
undefined : 转化为 NaN
null : 转化为 0
字符串 :
如果整个字符串,是纯数字字符串,或者符合科学计数法 ---> 转化为对应的数值
如果字符串内有不符合数字规范的内容 ---> 转化为 NaN
数组,对象,函数:
如果是+加号执行的是字符串拼接效果,按照这些数据类型转化为字符串的原则来转化
如果是其他形式的运算 执行结果都是 NaN
4.布尔类型的强制转化:
布尔类型的强制转化就是使用JavaScript中定义好的 方法/函数 Boolean( 数据/变量 )
Boolean() 这个方法 不会改变 变量中存储的原始数值
转化原则与自动转化原则相同
0 '' undefined null NaN --------> false
其他数据,都转化为true
5.字符串类型的强制转化:
方法1,变量.toString(进制类型)
将数值强制转化为字符串,并且可以设定转化的进制,.toString() 之前,不能直接写数值,必须是写成变量的形式
进制常用的数值是 2 8 16 ,可以设定的范围是 2 - 36 进制
方法2,String( 变量 / 数据 )
将变量或者数据,转化为字符串,原则按照自动转化的原则来执行,不会改变变量中存储的原始数值
但是在字符串拼接时,会将其他数据类型自动转化为字符串
6.数字类型的强制转化:
————————————————方法1 , Number(变量/数值)
console.log( Number(true) ); // 1
console.log( Number(false) ); // 0
console.log( Number(null) ); // 0
console.log( Number(undefined) ); // NaN
console.log( Number('100') ); // 对应的数值
console.log( Number('100.123') ); // 对应的数值
console.log( Number('2e4') ); // 对应的数值
console.log( Number('123abc') ); // NaN
console.log( Number( [1,2,3,4,5] ) ); // NaN
console.log( Number( {name:'zhangsan'} ) ); // NaN
console.log( Number( function fun(){console.log('abc')} ) ); // NaN
将其他类型强制转化为数值类型,转化原则与自动转化选择相同
方法2, parseInt(变量 / 数据) 是获取变量或者数据的整数部分
从数据的 左侧起 解析获取 整数内容
console.log( parseInt(true) ); // 都是 NaN
console.log( parseInt(false) );
console.log( parseInt(null) );
console.log( parseInt(undefined) );
console.log( parseInt( {name:'zhangsan'} ) );
console.log( parseInt( function fun(){console.log('abc')} ) );
数组执行,是获取 数值部分 也就是 没有 []的部分
1,2,3,4,5 整数部分是 1 1之后是逗号 逗号不是整数,之后的部分也就不算整数
获取第一个数值的整数部分,如果有就获取,如果没有,结果是NaN
console.log( parseInt( [1,2,3,4,5] ) ); // 结果是 1
console.log( parseInt( [null,2,3,4,5] ) ); // 结果是 NaN
如果是整数就直接获取,如果是浮点数,或者科学计数法,就获取整数部分
console.log( parseInt( 100 ) ); // 整数是直接获取
console.log( parseInt( 0.0123 ) ); // 浮点数是获取整数部分
console.log( parseInt( 3.123456e3 ) ); // 科学计数法是解析之后,获取整数部分
字符串不同了
如果是纯数字的字符串
console.log( parseInt( '100' ) ); // 与数字的结果相同
console.log( parseInt( '0.0123' ) ); // 与数字的结果相同
console.log( parseInt( '3.123456e3' ) ); //3
console.log( parseInt( '3abc' ) ); //3
console.log( parseInt( '3.123' ) ); //3
方法3 , parseFloat( 变量 / 数值 )
获取浮点数部分
console.log( parseFloat(true) ); // 都是 NaN
console.log( parseFloat(false) );
console.log( parseFloat(null) );
console.log( parseFloat(undefined) );
console.log( parseFloat( {name:'zhangsan'} ) );
console.log( parseFloat( function fun(){console.log('abc')} ) );
//数值, 整数,浮点数,都会完整获取
console.log( parseFloat(100) ); //100
console.log( parseFloat(100.1234) ); //100.1234
console.log( parseFloat(1.234567e3) ); //1234.567
// 关键是字符串
// 从字符串的左侧起 解析 符合浮点数的部分
console.log( parseFloat( '100' ) ); // 与数字的结果相同
console.log( parseFloat( '0.0123' ) ); // 与数字的结果相同
console.log( parseFloat( '3.123456e3' ) ); // 科学技术法会解析
console.log( parseFloat( '3.1223abc' ) );
console.log( parseFloat( '3.123' ) );
好了,这就是在JavaScript中数据类型的转化,希望可以帮助到你。
版权声明:本文为CSDN博主「什什么都绘」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39406353/article/details/104864224
我们总在期待 Next Big Thing,企盼下一次数字革命。喊了这么多年的物联网现在还没有明显起来的迹象,而 VR 也因为头戴设备的大型化和沉浸式场景的泛用性较差的原因,反倒是 AR 和 MR 依托智能手机、浴霸式镜头组和 APP 有一定起色,但是也没有到革命性改变的程度,只能算是一个小趋势。当然,人工智能/深度学习所带来的影响更加深远,但是短时间以内,它所带来的变化趋近于隐形。
而最近2年,各种双屏和柔性屏的发布,则可能是距离我们最近的硬件变革,可能和柔性屏/双屏设备有关。
也许现在说硬件交互设计到了类似 2007 年 iPhone 发布一样拐点有点夸张,但是对于现在几乎纯粹拼配置死水微澜一样的手机电脑市场而言,这种明显区别于以往的硬件设计,将会直接带来交互、设计和体验上的改变。
2019年是否算得上是双屏设备元年,现在下结论为时过早,但是去年三星 Galaxy Fold 和 Moto Razr 的发布,确实给广大硬件厂商好好打了一个样。
尽管Galaxy Fold 去年折戟沉沙了,但是高昂的沉没成本和大势所趋让三星肯定不能就这么算了, 回炉再造一番之后今年又带着船薪版本的 Galaxy Fold 2 杀将回来,顺带还兼顾女性市场整了一个对标 Moto Razr 的化妆盒手机 Galaxy Z flip。
图片来自 TheVerge
当然,华为的 Mate Xs 也是相当优秀的产品,这款明显对标三星 Galaxy Fold 2 的产品,并没有将柔性屏制作成为向内折叠,而是完全翻过来,将它作为外屏来进行设计,反向折叠,展开的时候,屏幕自然延展。
图片来自 TheVerge
不过思路最为清奇的并非是华为,而是 TCL。就在这几天,TCL 带来了两款全新的原型机,一款手机带有两个折叠轴,相当于是将传统手机屏幕延展到以往的3倍,彻底折叠开相当于是一个 10英寸的平板电脑(回过头来想,就像是将一个平板电脑反向折叠到手机的大小,但是重量不变……)。
图片来自 TheVerge
另外一款原型机则选择了抽拉式的设计,机身可以如同抽屉一样拉开,柔软的屏幕会被拉出,延展开来差不多和 iPad Mini 一个大小了。
图片来自 TheVerge
图片来自Engadget
除了这几款之外,在今年年初的 CES 消费电子展上,联想、戴尔、华硕,这些目前世界上最大的消费电子制造商,纷纷带来了各自的折叠屏和双屏设备。
联想带来的 ThinkPad X1 Fold,是一个价格昂贵的柔性折叠屏平板电脑,它额外附带了一个蓝牙键盘。
图片来自 TheVerge
考虑到联想在此之前已经发布过带有LEC+墨水屏的双屏设备 Yoga Book 2,可以说联想是已经具备了制造两种不同类型屏幕设备的能力。
作为对手的戴尔,带来了分别对标联想这两个系列的对应产品:Concept Ori 和 Concept Duet。
Concept Ori 采用的是两块传统硬屏,你既可以让一款屏幕作为屏幕,另一块作为虚拟输入键盘或者手绘板,也可以使用配备的蓝牙键盘,吸附在底下的屏幕上来进行输入,而且当键盘移动到靠近转轴的地方,还能让底下露出的半块屏幕作为触控板来使用:
图片来自 TheVerge
Concept Duet 在概念上则和 联想的 ThinkPad X1 Fold 类似,一块柔性可折叠的屏幕,便于收纳,一体连接。
图片来自 TheVerge
看了这么多硬件,是不是觉得信息量有点大?不过简单来说,所有的这些产品,都在说一件事情:屏幕要延展开,这是一个正在发生的趋势。
同时,我们还注意到一个很明显的特征,就是所有的这些柔性屏设备都非常的……骚,且贵。动辄两三千美元的起步价,如果可靠坚挺也就算了,不仅转轴易损,且屏幕也存在易损的问题。根据 ifanr 的上手评测,即使是在优化了转轴和屏幕折叠角度之后,三星所发布的 Galaxy Z Flip 的屏幕中段依然有着不可忽视的折痕,这一问题可能会是绝大多数折叠柔性屏设备的通病。
图片来自爱范儿
与之相反,采用硬质双屏设计的硬件设备,从生产成本、工艺成熟度、价格上,都更加有优势。
值得注意的是,柔性折叠屏和硬质双屏设备,在基本的使用体验和逻辑上是一致的,除了极少数特殊的设备之外(比如 TCL的双折叠式的概念机),多数情况下,两者是差不多的。
只不过存在一个问题,双屏设备的交互和体验,需要有对应操作系统支持,因为从单屏到双屏,其实交互逻辑已经发生了巨大的改变。
一直在创新且「稳健」地更新软硬件的苹果公司,应该不会在市场未曾成熟的情况下选择发布硬件,这意味着你不会很快看到双屏 iOS 硬件,而面向着大量 OEM 厂商的 Android 和 Windows 则截然不同。着两年厂商已经身体力行证明了一件事情:只要操作系统和设计跟上,硬件马上量产不是问题。
最近泄漏的 Android 11 的新特性已经出现了可折叠屏幕的影子,但是具体情况恐怕要到因为疫情跳票的 Google I/O 大会上会揭晓答案。但是另一边,贼心不死的微软,已经开始布局面向可双屏设备的新一代操作系统 Windows 10X了。
图片来自 TheVerge
去年微软发布的两款双屏设备 Surface Duo 和 Surface Neo 并不都是采用尚未发布的 Windows 10X 操作系统,但是两者都沿用了几乎相同的交互逻辑,较小的 Neo 采用的是 Android 操作系统。这两款硬件和系统交互设计,将会在未来一段时间以内,成为双屏硬件的软件交互的重要参考和主要标杆,联想和戴尔这波 OEM 厂商,无疑是参考着微软的风向标来搞硬件产品的。
图片来自 TheVerge
传统而臃肿的「开始」菜单栏在 Windows 10X 当中,被精简为我们更熟悉的模式,新的 Windows 10X 在原有的 Windows 10 的基础上,应该有对移动端(比如 ARM 架构的CPU)和小屏幕有更好的支持。
但是,更有价值的,是微软为双屏设备所制定的交互设计规范。
下面是基于微软官方文档,精简编译后的规范:
双屏交互概述
双屏设备可以基于不同的工业设计,有多种硬件样式。微软发布的 Surface Neo 和 Surface Duo 可以作为典型的双屏设备作为参考。双屏本身可以借由铰链、转轴来连接,也可以基于柔性屏来实现。
所有的双屏设备都具备有折叠、旋转、翻转的功能,两块屏幕都可以用来作为显示,也可以一个做屏幕一个承载虚拟键盘,当然也可以借由外设,构建、组合成为新的模式。所以,为这样的硬件设计的时候,需要考虑到各种不同的情况,并且适配硬件,帮助用户实现更多的目标。
图片来自 TheVerge
当用户打开应用的时候,它的主要界面窗口应该最大化,占据一块屏幕的全宽和全高。这样用户可以一次打开多个不同的应用,显示在双屏上。
图片来自 TheVerge
当然,你的APP 也可以完整铺满两个屏幕,这个界面布局被称为「跨屏布局」。在默认情况下,它应该像在大屏幕上一样,一个窗口跨屏幕显示。但是你可以修改这种模式,让它可以铺满两个屏幕的同时,还可以兼顾到中间有转轴和铰链的硬件。对于这个问题,我们随后有详细的讨论。
响应式布局
比起传统的响应式布局,对于双屏硬件,我们要讨论的「响应」模式要复杂得多。就像下面这张图中所说的,要为这样多样、复杂的情况进行设计:
我们默认用户在多数时候,是处于双屏展开的状态,当用户打开 APP 的时候,它的主要界面窗口,将会最大化占据一个屏幕,这个时候另一个屏幕处于空置状态,用户可以在这个屏幕上打开另外的应用,并且用户可以通过托拽窗口的方式,来重新整理窗口和APP的排布模式。
同时,单个应用程序也应该可以进行跨屏布局,既可以让单个应用分别在两块屏幕上各呈现一个窗口,也可以作为单个窗口完整铺满两块屏幕。不论是充分利用接缝的存在,还是说尽可能地利用全部屏幕区域来聚焦单个内容,应用程序应该都可以做到。当然,这些情况我们随后会单独说到。
首先,作为一个已有的应用程序,在双屏设备上应该能够继承原有的功能,并且尽可能地兼容双屏的体验。在开始讨论如何为双屏场景进行设计应用之前,我们先应该对双屏交互进行介绍。
双屏的响应式布局
首先,无论屏幕尺寸如何,方向如何,应用程序应该都可以保持良好的外观,善用 UI 平台的现有的布局技术,通过合理地缩放来自适应,填满屏幕。如果你的屏幕元素依赖屏幕长宽比,那么应该善用平台给的 API 来进行灵活的优化。
考虑到你的应用将会在很多不同尺寸、不同长宽比、不同类型的设备上运行,所以你的应用程序应该足以应对各种不同的情况。请记住,你的设计将会遭遇和以往截然不同的屏幕尺寸和长宽比,比如纵向(全景视图)、横向(较宽的全景视图)、纵向双屏分别显示等不同情况。
考虑所有的屏幕方向
用户在很多平台上有习惯的、常见的屏幕方向,比如在 Android 和 iOS 上,通常应用是竖屏显示的,在 Windows 上,多数情况下是横向全屏显示的。而在双屏设备上,这种情况会发生改变。
比如你的应用原本是为竖屏设计的,但是需要经常输入内容,那么你要考虑到双屏设备上,你的应用可能是会被横屏显示,用户会像用笔记本电脑那样来使用应用,也就是说两块屏幕都横向显示,靠下平摊在桌面的屏幕会显示虚拟键盘或者手写区域,作为输入窗口,而显示窗口也是横向的。
双屏为多任务提供更好的显示环境,你不会知道用户会在什么样的场合,以什么样的姿势来握持设备,但是考虑潜在的使用姿态,可以让你更好得对应用进行设计和优化。
根据我们的研究,如果你的应用是注重输入的应用,那么用户在平面上打字和输入将会是最舒服也最常见的姿势。那么在这种情况下,你应该针对横屏显示进行针对性的优化。
支持多种输入模式
对于新的双屏设备,通常都支持多种输入模式,包括打字输入,屏幕触摸和手写笔这样的截至。这意味着用户可以灵活地根据需求,选择不同的姿势和输入模式,并且快速切换,以适应不同的需求。
换句话来说,就是你在设计的时候,需要支持所有的输入方式,以便用户可以自由选择交互模式。
托拽交互
你的应用应该支持屏幕托拽,这不仅是为了兼容双屏设备,而是对于绝大多数的设备的使用情况而进行兼容,确保用户体验的一致和灵活。只不过相比于在屏幕单屏上进行托拽移动,在双屏上托拽移动,将会带来更多的可能性,并且这样也将会在双屏使用场景之下,最为重要的交互模式之一。
为了确保托拽操作的自然,你需要确保诸如文本、图像、视频等常见的交互对象和元素,可以在任何地方进行剪切、复制、粘贴,并且对于共享和放松之类的操作也启用托拽操作,这将最大化地利用双屏的优势。
应用的多屏呈现
用户会希望在两块屏幕上并排显示同一应用中的不同内容,因此你的你用应该支持多实例呈现和运行。
多媒体内容画中画体验
如果你的应用是一个多媒体应用,那么应该支持画中画模式,用户可以边看视频边执行别的操作。
上面提及的很多功能属于基础应用要求,并不是专门针对双屏设备而做,但是如果你的应用支持上面的功能,那么在双屏上将会明显拥有更好的用户体验。接下来,我们着重聊一下在双屏设备上进行设计的问题。
在双屏设备上,你的应用应当支持在单个屏幕上运行,也可以在双屏上运行,当一个应用在两个屏幕上显示的时候,我们称之为「跨屏」,而跨屏显示这个问题对于双屏设备而言,是至关重要的,如何显示将会带来巨大的影响。这种独特交互模式可能会解锁前所未有的使用方法。比如,有转轴和接缝的双屏设备,因为屏幕的特征而非常适合分隔并行式的生产力解决方案。
跨屏是用户的选择
用户有选择如何使用应用的方式的权力,包括何时跨屏显示。某些应用可能在单屏或者跨屏显示的时候,看起来不够好看,但是如何使用的权力,应该交给用户去选择。
尽管本文会针对如何处理多屏布局提供几种不同的方案和想法,但是请选择适合你的用户和应用的呈现方式。
考虑用户意图和设备方向
当你的两个屏幕都被利用起来的时候(横向双屏,纵向双屏),了解用户的意图至关重要。尽管还有更多的调研需要做,但是结合我们目前已有的观察,可以得出如下的趋势:
在为双屏设备设计应用的时候,有四种常见的布局方案是你需要考虑的。通常这取决于应用是单屏还是跨屏,是默认视图还是全屏视图:
1、单屏默认模式
2、跨屏默认模式
3、单屏全屏模式
4、跨屏全屏模式
当单个应用以单个窗口运行,并且跨越两个屏幕的时候,跨屏布局就出现了。如果你原有的应用从未针对双屏设备进行优化的话,那么系统会提示你「应用将会扩展并占据所有屏幕」,并且这个时候,应用界面会自行调整大小,适应新的尺寸。
这种情况下,界面中间的接缝会显得非常明显。这是双屏设备先天的副产物。要如何优雅地处理接缝?这就是下面这节内容将会探讨的问题,我们将会提供一些常见的处理方案yi。
是否总是要适应接缝?
如果你的应用不作任何优化就直接在双屏设备上投放使用,接缝并不总会给用户体验带来影响。比如地图类应用,用户可以随意移动地图内容,接缝带来的割裂并不会对使用体验造成实质性的影响。在后面「扩展画布」这一节,将会对这个问题进行深入讨论。
但是对于另外一部分应用,接缝带来的问题就非常严重了。比如在一个表格类应用当中,如果不作修改和调整,有的内容会直接被接缝给割裂开,你必须进行滚动才能正常查看。而对于某些相对更加固定无法移动的元素而言,接缝带来的体验是破坏性的。而这个时候,我们需要使用一些技术方案来处理这个问题。
规避接缝
将元素移到一边
由于两块屏幕之间有明显的接缝,因此当用户在使用应用的时候,某些 UI 元素可能会正好被穿过接缝,逻辑上这不会影响功能,但是如果将这些 UI 元素移动到屏幕的一边来显示,会提供更好的体验。最好避免在接缝处显示文本内容,这会影响可读性。
应用程序对话框应该移到屏幕的一边,尤其是需要点击按钮操作的时候。
底部菜单应该移动到屏幕一侧,而不是延伸到两个屏幕上。
用户调用上下文菜单的时候,应该将接缝视作为屏幕边界处理,尤其是在靠近屏幕边缘的地方触发菜单的时候。
应用内的下拉菜单或者可扩展容器如果可能会跨越接缝的话,应该改变扩展方向。
当整个应用界面扩展开来的时候,应该整个移动到屏幕的上侧,而不是在靠近中心的位置横跨接缝。
贴合接缝
使用偶数列并和接缝对齐
当界面中使用网格布局的时候,垂直或者水平方向尽量使用偶数行或者偶数列,这样可以让接缝和界面间隙正好重合,用户可以更加舒适地查看信息。
在网格中使用偶数列,尤其是对于容器、表单,并且考虑到接缝来控制间距。
除此之外,还有许多应用会考虑充分利用另外一个屏幕来显示弹出菜单或者下级页面的内容。这种使用逻辑确实会让应用更加易用,并且在视觉上会更加干净清爽。但是请记住,如果弹出的界面并不是全屏的,可能会暗示它是可折叠和可关闭的,因此,你需要根据实际的设计需求,来灵活的处理呈现样式。全覆盖另外一屏的弹出界面,更加适合小尺寸屏幕。
重新排列 UI 元素
移动到接缝的任一侧
还有一种用来优化响应式布局的方法是,当屏幕方向或者大小发生变化的时候,重新排列你的内容。这种方式让你可以在两个屏幕上随意扩展你的内容,你可以通过分组来重新排列,以更有目的的方式来适配屏幕和内容。
遮罩和分割
对于一些无法重新排列的元素,比如全屏图片和视频,这个时候只能使用遮罩和分割的方式来处理。
遮罩的思路是,将接缝视作为一个遮罩元素,而图片被它给遮挡了一部分,根据格式塔原理,我们的大脑会自动补足缺少的部分,遮罩遮罩处理方式适合处理多媒体(视频,图片等)这样的画布类型的场景,在这些场景下,保持图像的连续性比显示内容的完整性更加重要。
分割的思路是将内容均匀切割为两个部分,完整呈现,这对于包含有多个控件和元素的普通界面而言,是更加合理的处理方式,包括可能会出现在屏幕中间的按钮。
根据类型的不同,这两种处理方式各有优势,我们将继续跟进不同的用户行为特征,来寻求更优的解决方案。
文章来源:优设
蓝蓝设计的小编 http://www.lanlanwork.com