View.js - http://view-js.com
单页应用,是指将用户视觉上的多个页面在技术上使用一个载体来实现的应用。
换句话来讲,用户视觉效果,与技术实现的载体,并不是一定要一一对应的。采取哪种技术方案,取决于产品设计、技术组成以及方案之间的优劣平衡。
放到 Web 前端环境中,这个承载了多个视觉效果的载体,就是 html 文件(或 asp,jsp 等)。
为便于描述,本文将使用多个术语。其名称及对应的含义如下所示:
直观效果的单页应用,其实现过程其实并不复杂。
我们可以使用 div
或 section
等标签承载视图的展现,并通过脚本实现特定视图的呈现与隐藏。例如:
<html>
<head>...head>
<body>
page1div>
page2div>
body>
html>
.view{
display: none;
}
.view.active{
display: block;
}
var find = function(selector){
return document.querySelector(selector);
};
find(".view.active").classList.remove("active");
find("#view2.view").classList.add("active");
这可能是最简单的单页应用了。
面对较为复杂的需求,如:“界面数据需要根据用户操作而动态变化”等时,逻辑就要再复杂一些。
单页应用进阶
假设我们要实现这样的一个电商需求:
- 一个视图呈现商品列表;
- 一个视图呈现商品详情;
- 在列表视图触摸特定商品后,隐藏列表视图,显示触摸的商品的详情视图;
- 可以在详情视图返回列表视图;
- 可以在详情视图中购买商品。
那么代码逻辑可能就要这么实现:
- 视图DOM搭建
红富士苹果div>
砀山梨div>
div>
返回span>
商品名称div>
商品图文详情div>
立即购买span>
div>
- 撰写脚本:视图数据渲染
/**
* 渲染商品列表视图
* @param {Array#JsonObject} goodsList 商品列表
* @param {JsonObject} goodsList[n] 商品详情
* @param {String} goodsList[n].goodsDetail.name 商品名称
* @param {String} goodsList[n].goodsDetail.detail 商品图文详情
*/
var renderGoodsListView = function(goodsList){
var goodsListObj = find(".goods-list");
goodsListObj.innerHTML = "";
goodsList.forEach(function(goodsDetail){
var divObj = document.createElement("div");
divObj.data = goodsDetai;/* 附加数据以供后续使用 */
divObj.setAttribute("data-goods-id", goodsDetail.id);
divObj.innerHTML = goodsDetail.name;
});
};
/** 当前正在呈现的商品详情数据 */
var currentShowingGoodsDetailData = null;
/**
* 渲染商品详情视图
* @param {JsonObject} goodsDetail 商品详情
* @param {String} goodsDetail.name 商品名称
* @param {String} goodsDetail.detail 商品图文详情
*/
var renderGoodsDetailView = function(goodsDetail){
var nameObj = find(".goods-detail .name"),
detailObj = find(".goods-detail .detail");
nameObj.innerHTML = goodsDetail.name;
detailObj.innerHTML = goodsDetail.detail;
currentShowingGoodsDetailData = goodsDetail;
};
/**
* 获取当前正在渲染的商品详情数据
*/
renderGoodsDetailView.getCurrentShowingGoodsDetailData = function(){
return currentShowingGoodsDetailData;
};
- 撰写脚本:视图切换
/**
* 执行视图切换动作
* @param {String} viewId 目标视图ID
*/
var switchViewTo = function(viewId){
var activeView = find(".view.active");
if(null != activeView && activeView.id == viewId)
return;
if(null != activeView)
activeView.classList.remove("active");
find(".view#" + viewId).classList.add("active");
}
/** 当前正在呈现的商品详情数据 */
var currentShowingGoodsDetailData = null;
/* 列表视图 切换至 详情视图 */
var goodsListObj = find(".goods-list");
Hammer(goodsListObj).on("tap", function(e){/* hammer.js是一款处理触摸事件的,优秀的开源框架 */
var target = e.srcEvent.target;
if(!target.hasAttribute("data-goods-id"))
return;
currentShowingGoodsDetailData = target.data;/* data属性在渲染时附加,代表对应的商品详情数据 */
renderGoodsDetailView(target.data);/* data属性在渲染时附加,代表对应的商品详情数据 */
switchViewTo("goods-detail");
});
/* 详情视图 返回至 列表视图 */
var navBackObj = find(".goods-detail .nav-back");
Hammer(navBackObj).on("tap", function(){
switchViewTo("goods-list");
});
4) 撰写脚本:购买商品
var buyObj = find(".goods-detail .btn.buy");
Hammer(buyObj).on("tap", function(){
var goodsDetail = renderGoodsDetailView.getCurrentShowingGoodsDetailData();
if(null == goodsDetail)
return;
console.log("Buying goods of id: " + goodsDetail.id);
//TODO 通知服务端
});
- 撰写脚本:加载并呈现商品列表
document.addEventListener("DOMContentLoaded", function(){
var goodsList = [];
//TODO 从服务端获取商品列表
/* 渲染商品列表 */
renderGoodsListView(goodsList);
/* 默认呈现列表界面 */
switchViewTo("goods-list");
});
看上去确实没什么问题,应该可以正常工作。是的,确实是这样。
但程序开发,并不青睐一个人单独作战,而是需要多人协作的。这个例子只证明了这种方案的可行性,但可行性与量产还不是同一个问题。如何以更优雅地方式在实现功能的前提下提升多人协作的便捷性,也是项目管理过程中的一个重要议题。
就上面的例子而言,存在如下几个方面的问题:
- 商品列表的逻辑与商品详情的逻辑无法清晰的剥离开来;
- 视图之间的数据传递完全依赖带参数的渲染方法的主动调用;
- 特定视图的数据暂存无法得到有效处理。如,渲染商品详情视图所需要的商品详情数据;
- 特定视图打开后,无法借助URL传播。亦即用户A打开商品G的商品详情后,如果将URL发给B,B打开链接后看到的并不是商品G的商品详情,而是商品列表视图
上面描述的种种问题,都需要一个业务无关的底层框架给予解决,使得应用开发者基本上只考虑各个视图的业务逻辑与视图之间的参数传递问题即可。
单页应用的优点与缺点
相比传统的开发方式,单页应用有如下几方面的显著优点:
- 页面切换速度快。视觉上页面的切换,只是技术上同一页面两个区块之间的切换
- 页面之间的可传递所有js支持的数据类型,甚至是一个DOM元素
- 可为页面切换过程添加转场动画
与此同时,单页应用也存在如下几方面的缺点:
- 所有页面的样式、DOM和JS需要完全加载
- 页面打开速度稍慢
对于缺点1,开发者可借助如下手段解决或优化:
- 借助gulp.js等构建工具,将所有视图的样式、DOM结构和脚本分别合并之单独的文件中压缩,以降低影响。以复杂类型中等的电商为例,如果一个界面含有80个视图,那么通过构建工具合并压缩之后的脚本也只有1.1M左右
- 拆解客户端功能,仅将共属于同一操作范畴的视图构建至同一html中。不同操作范畴的视图隶属于不同html文件
对于缺点2,开发者可以通过配置web服务器,为页面加上缓存控制,从而降低影响,使得页面的第二次及其后的加载速度更快。
有哪些单页应用框架
单页应用框架有view.js、angular.js和vue.js等,不同框架的解决方式各有不同。笔者推荐使用View.js(官网:http://wzhsoft.com)。
View.js是什么样的单页框架
View.js是一个专门用于开发移动端H5单页应用(SPA,Single Page Application)的底层框架。其核心理念是"视图",并提供而且仅提供视图相关的API和事件监听等。
除基本的html、css和和js知识外,View.js并不需要什么模板、双向绑定等个人认为画蛇添足的技术。而且开发者能够以事件驱动的方式完成业务逻辑开发。
如何使用View.js
在html中引入view-{{version}}.js和view-{{version}}.css即可。(其中{{version}}为版本号)。
View.js的适用范围
View.js适用于偏交互性质的网页应用,如:商城、物业管理、股票分析等。对于宣传、广告性质等页面较少,动画较多的H5,使用View.js可能就显得略微笨重了。
View.js同样适用于开发混合应用的同学。
什么是视图
View.js将"视图"定义为:用户视觉上在设备上看到的一页内容。
视觉上的效果在技术实现上,有多种选择。传统的实现方式,是一个视图一个页面,视图之间的切换在技术上体现为页面之间的跳转。
但View.js并不这样做。
出于复杂类型数据(如:回调方法,json对象和DOM元素等)传递,以及动画效果的开发等需要,View.js将多个视图使用一个页面来存储。亦即,以"一个视图对应HTML中的一个片段,多个视图共存于同一个HTML"的方式来组织。如下所示:
<html>
<head>...head>
<body>
...
section>
...
section>
body>
html>
同一时刻只有一个视图处于显示状态,这个视图被称之为"活动视图"。
每个页面都有一个默认视图。默认视图是指页面打开后将要呈现的第一个视图。
由于View.js只专注于视图相关的操作,并不涉及其它领域,因此View.js能够很好地和其它优秀的框架一起协同工作,如Hammer、Swiper、 jQuery和zepto等。因为这些不同的框架或库解决的是不同范畴的问题,所以不会出现冲突,因而可以放心使用。(View.js会在window上附加名称为"View"的对象。如果您引用的其它框架或库也有这样的名称,那可能会有冲突出现。)
默认视图
使用View.js开发的页面会同时包含视图,但页面打开后只能有一个视图处于活动状态,亦即是活动视图。这个视图,View.js将其定义为"默认视图"。
开发者可以通过data-view-default
属性并将其赋值为true
,也可以通过API:View.setAsDefault(viewId)
来配置特定视图为默认视图。如果同时有多个视图为默认视图,则View.js将认定第一个视图为默认视图。如果没有指定默认视图,则自动采用DOM顺序上的第一个视图。
活动视图
使用View.js开发的页面会同时包含视图,但页面同时只能有一个视图处于活动状态,亦即被用户看到。这个视图,View.js将其定义为"活动视图"。
开发者通过API或DOM属性切换视图时,活动视图将同步发生变化,从而使得任何时刻活动视图都是用户看到的视图。
开发者可以通过API:View.getActiveView()
获取当前的活动视图。
伪视图
伪视图,是并不存在,但可以指导View.js进行视图切换的视图。
View.js支持如下几个伪视图:
视图群组
开发者可以借助视图群组将功能相近,但展现风格或功能表现略有差异的视图归为一类,以实现动态决定要导向到目标视图的目的。
开发者在使用View.js提供的视图导向功能时,可以使用视图群组名称代替视图编号。如:
View.navTo("~profile");
View.changeTo("~category");
首页span>
此时,View.js将根据提供的群组名称查找页面内属于该群组的所有视图。如果没有视图隶属于该群组,则在控制台上提示相关错误;如果有多个视图隶属于该群组,则认定通过View.listAll()
方法得到的第一个视图为要导向到的目标视图,并将视图切换过去。
其中,符号:"~"用于告诉View.js,该符号后边的字符串内容是视图群组的名称。
视图群组名称可以由开发者随意指定,并且定义至任意视图节点上。
视图群组名称使用DOM属性:data-view-group
来定义,如:
...
section>
视图容器
View.js建议开发者将所有的视图对应的DOM元素统一定义至特定元素下,以便于View.js完成整体级别的页面布局。该元素即为视图容器。
除非额外指定,否则View.js将使用document.body
作为视图容器。
开发者可以使用DOM属性:data-view-container
来定义视图容器所对应的DOM元素。
事件驱动
所有视图实例均具备事件驱动特性。
视图支持的事件包括:
- View事件:beforechange - 视图将要切换
监听样例:
View.on("beforechange", function(e){});
- View实例事件:ready - 视图就绪
监听样例:
View.ofId("myView").on("ready", function(e){});
注:特定视图的ready事件只会在视图第一次进入时触发一次。视图第二次进入后不会再触发
- View实例事件:beforeenter - 视图将要进入
监听样例:
View.ofId("myView").on("beforeenter", function(e){});
- View实例事件:enter - 视图进入
监听样例:
View.ofId("myView").on("enter", function(e){});
- View实例事件:afterenter - 视图进入完成
监听样例:
View.ofId("myView").on("afterenter", function(e){});
- View事件:afterchange - 视图切换完成
监听样例:
View.on("afterchange", function(e){});
除此之外,开发者还可以根据自己需要,发起自定义事件并为这些事件添加监听器。如:
var view = View.ofId("myView");
view.on("myevent", function(e){
view.logger.debug("Event name: {}, event data: {}", e.name, e.data);
});
//…
view.fire("myevent", {a: 1});//-> 0713 10:20:54 [View#myView]: Event name: null, event data: {"a":1}
开发者在创建自定义事件时,需注意事件名的可读性以及见名知意的直观性。虽然任何形式的命名都能驱动程序的正常工作,但处于工程的可维护性,并不建议这样做。
开发者在发起自定义事件时,可以为事件附加任意类型的数据。对应的事件监听器在捕获对应的事件时,可以通过data
属性获取附加的数据。如:
View.ofId("detail").fire("goodsDetail.obtained", {goodsName: "XXXX", price: 12});
View.ofId("detail").on("goodsDetail.obtained", function(e){
console.log(e.data);// -> {goodsName: "XXXX", price: 12}
});
除此之外,开发者还可以通过API:view.getLatestEventData(evtName)
获取指定名称的事件最后一次被触发时所附加的数据,如:
var goodsDetail = View.ofId("detail").getLatestEventData("goodsDetail.obtained");
console.log(goodsDetail);// -> {goodsName: "XXXX", price: 12}
设备操作系统检测
View组件在加载完成后,会自动识别当前的移动设备类型,并将识别结果以名称为:data-view-os
DOM属性的方式附加至DOM树中的html结点上。如:
<html data-view-os="android">html> <!—代表安卓设备 -->
<html data-view-os="ios">html> <!—代表苹果设备 -->
<html data-view-os="wp">html><!—代表windows phone设备 -->
开发者可以根据该属性为实现"视图在不同操作系统下表现出不同的效果"而撰写不同的CSS样式。
日志输出
View.js内置了格式化的日志输出组件,以供程序调测使用。所有视图实例均含有日志实例,如:
var view = View.ofId("myView");
view.logger.debug("Hello, view.js");//-> 0713 11:42:02 [View#myView]: Hello, view.js
view.logger.warn("Hello, {}, {}", 123, true, false); //-> 0713 11:42:44 [View#SC_home-page]: Hello, 123, true
console.log(view.logger.getName();)//->View#myView
其中"{}“为占位符,一一对应于第二个参数开始的多个参数。可以使用”\“转义占位符,以输出”{}"。如:
view.logger.log("\\{}");//-> 0713 11:43:54 [View#myView]: {}
View日志组件共支持4个日志级别:debug, info, warn, error,以及1个普通的log。
开发者可以通过调用API:View.Logger.ofName(loggerName)
来创建一个自定义的日志输出器。如:
var logger = View.Logger.ofName("WindowUtil");
logger.debug("test"); //-> 0713 11:56:24 [WindowUtil]: test
视图配置
通常情况下,一个视图的功能表现是固定的。但在部分场景下,视图可能需要同时包含多种功能表现,并需要以配置的方式指定要启用的功能表现。在这种情况下,就可以借助视图配置完成。
视图配置是一个集合,可以根据程序需要包含多个配置项。每个配置项由"配置项名称"和"配置项取值"两部分组成。除此之外,为方便开发者,视图配置项还提供有apply()
及reflectToDom()
等方法。如:
var view = View.ofId("myView");
var a = view.config.get("config-item");
var b = view.config.get("config-item");
console.log(a === b);//-> true
a.setValue(123);//-> set value to 123
console.log(b.getValue());//->123
b.apply();//-> nothing happes
b.setApplication(function(v){
console.log(123, v);
});
b.setValue("asdf");//-> nothing happens
console.log(b.getValue());//-> 123
b.setValue("asdf", true);//-> set value to ‘asdf’
b.apply();//-> 123, asdf
开发者可以通过reflectToDom()
方法将配置项取值体现到DOM中,从而可以在CSS层面控制元素的表现。reflectToDom()
方法被调用时,将配置项以data-viewconfig_[configName]=[configValue]
的形式添加至视图的DOM结点上。如:
View.ofId("myView").config.get("show-header").setValue(true).reflectToDom();
视图上下文
为了满足工程多个脚本文件之间共享变量的需要,以及降低全局环境下变量被污染的可能,View.js为每个视图提供了数据上下文,以供开发者存取数据。如:
a.js
var view = View.ofId("myView");
var getOrderId = function(){
return "ORD001";
};
view.context.set("getOrderId", getOrderId);
b.js
var view = View.ofId("myView");
var getOrderId = view.context.get("getOrderId");
console.log(getOrderId());//-> ORD001
view.context.clear();
console.log(view.context.get("getOrderId"));//->undefined
不同视图拥有不同的上下文,不同视图的上下文中可以存储相同名称的数据。
视图初始化器
默认情况下,引用了View.js的页面会在网页加载就绪(DOMContentLoaded
)后自动执行初始化动作。但开发者可以通过调用API: View.setInitializer(initializerFunc[, execTime])
提供自定义的初始化器延迟执行视图的初始化动作。如:
View.setInitializer(function(init){
//…
init();//->执行初始化动作
}, "rightnow");
视图的初始化动作包括:
- 调用视图初始化监听器
- 在DOM中标识识别的操作系统
- 扫描文档,遍历视图定义
- 确定默认视图
- 添加视图标题自动设置支持
- 使能
data-view-rel
属性
- 调用视图就绪监听器
- 呈现指定视图
视图布局
由于不同视图呈现的内容不同,因此所执行的布局动作也不同。大部分情况下,视图可以在没有脚本的情况下,通过CSS完成页面布局。但少数情况下,需要借助脚本完成页面的动态布局,如元素高度的动态计算等。
View.js假定所有视图都需要执行布局动作,且为简化开发,将在视图每次进入前(亦即,enter
事件触发前)自动执行开发者指定的布局动作(如果视图第一次进入,则在ready
事件触发前执行)。
开发者可以通过API:view.setLayoutAction(actionFunc, ifLayoutWhenLayoutChanges)
设定布局动作。如:
var viewId = "myView";
var view = View.ofId(viewId);
var headerObj = view.find("header");
var bodyObj = view.find(".body"),
btnObj = view.find(".btn");
view.setLayoutAction(function(){
var totalHeight = View.layout.getLayoutHeight();
var height = totalHeight - headerObj.offsetHeight - btnObj.offsetHeight;
bodyObj.style.height = height + "px";
在布局功能的设计上,View.js假定不同分辨率下所需要执行的布局动作是不同的。
View.js支持分别为:移动设备的竖屏模式、移动设备的横屏模式、平板设备的竖屏模式、平板设备的横屏模式、PC设备的竖屏模式,PC设备的横屏模式几种场景执行不同的布局动作。开发者只需为不同场景提供不同的布局动作即可,View.js自动完成设备类型及设备方向的识别并调用对应的布局动作。
默认情况下,View.js假定移动设备的竖屏模式、移动设备的横屏模式、平板设备的竖屏模式和平板设备的横屏模式表现一致,均为:“宽度渲染为浏览器宽度,高度自动”。
当在PC上浏览时,View.js默认将页面以iPhone5的320 * 568分辨率渲染。亦即,PC横屏浏览时,根据浏览器高度动态计算可用高度,并根据iPhone5的分辨率计算宽度,然后将界面水平居中呈现;PC纵屏浏览时,将其以移动设备的竖屏模式对待。
开发者可以通过调用API:View.layout.setExpectedWidthHeightRatio(ratio)
设定PC横屏浏览时,渲染的纵向效果的宽高比,如:
/* Layout as iPhone6+ */
View.layout.setExpectedWidthHeightRatio(414 / 736).init({
autoReLayoutWhenResize: true,
layoutAsPcLandscape: function(width, height){
document.body.style.cssText = "width: " + width + "px; height: " + height + "px; margin: 0 auto;";
},
layoutAsMobilePortrait: null,
layoutAsMobileLandscape: null,
layoutAsTabletLandscape: null,
layoutAsTabletPortrait: null,
layoutAsPcPortrait: null,
}).doLayout();
值得注意的时,在这种渲染模式下,如果界面含有position: fixed
绝对定位的样式表,表现结果可能与期望并不相符。
视图标题
开发者可以为视图的HTML结点设置单独的视图标题,从而达到"视图进入后,浏览器标题自动更新为当前视图标题;视图离开后,浏览器标题自动恢复为默认标题"的目的。如:
<header>header>
div>
<footer>footer>
section>
注:默认标题在View.js加载时通过自动检测浏览器标题获取。如果浏览器标题通过脚本延迟设置,则会出现默认标题为空的情况。此时开发者可以通过调用API:View.setDocumentTitle(title)
通知View使用给定的字符串赋值默认标题。
设定视图是否允许直接访问
在技术上,View.js利用HTML5的History API,借助地址栏hash完成视图之间的导向和路由。由此造就的现象,是在同一html下的不同视图之间进行切换时,浏览器地址栏的hash部分会发生变化。如:
切换前的地址栏:
http://www.mydomain.com/html/index.html#myView
执行如下切换动作后:
View.navTo("anotherView")
http://www.mydomain.com/html/index.html#anotherView
其中hash部分代表的是当前呈现的视图ID。
通常情况下,这没什么问题。但对于操作步骤有先后顺序要求的应用而言,当用户在操作时把地址分享给他人,或借助其它手段传播用户当前的URL地址时,就会出现"上一步内容尚未填写或校验就打开了后续步骤界面"的现象。
此时,开发者可以通过如下手段解决该问题:
使用data-view-directly-accessible
属性,并设置取值为false
设定依赖上一步骤界面的视图不能直接访问;
对应的JS API:
View.setIsDirectlyAccessible(isDirectlyAccessible);// 配置默认表现
View.ofId("myView").setAsDirectlyAccessible();// 配置单个视图
使用data-view-fallback
属性设定依赖上一步骤界面的视图的回退视图(可选);
对应的JS API:
View.ofId("myView").setFallbackViewId(viewId);
使用data-view-default
属性设置第一步的界面为默认视图
对应的JS API:
View.setAsDefault(viewId);
如:
<html>
<head>
head>
<body>
section>
section>
body>
html>
如此一来,当用户打开的地址中指定的视图不能直接访问时,View.js将自动查找该视图的回退视图(多层次查找,直到找到的视图是可以直接访问的)。如果回退视图不存在,则最终使用默认视图呈现界面,同时更新地址栏中的hash为最终呈现的视图的视图ID。
同时,为简化开发,View.js支持以"设定视图默认是否可以直接访问 + 设定单个视图是否可以直接访问"的方式设定视图的表现。如果特定视图没有设置是否可以直接访问,则使用默认配置代替。如果默认配置也没有设置,则以"视图不允许直接访问"方式对待。
无论是默认表现,还是特定视图的表现,均可以使用DOM属性:data-view-directly-accessible
进行标识。在配置特定视图时,需要将该属性声明在视图级别的DOM结点上;在配置默认表现时,需要将该属性声明在HTML节点上。如下所示:
<html>
<head>
head>
<body>
section>
section>
body>
html>
回退视图
当设置了特定视图不能直接访问时,开发者可以通过设定该视图的回退视图达到呈现该视图的入口视图等效果。例如:开发者可以设定"个人中心"视图:profile可以直接访问,而"个人中心"下的"账户设置":setting等视图不能直接访问,并设定"账户设置"视图的回退视图为"个人中心"。这样,即时用户打开的界面地址体现的是"账户设置"界面,如:
http://www.mydomain.com/html/index.html#setting
浏览器在页面加载完毕后呈现的也将是个人中心界面,且页面地址会更新为:
http://www.mydomain.com/html/index.html#profile
在技术实现上,当用户打开的地址中指定的视图不能直接访问时, View.js将自动查找该视图的回退视图(多层次查找,直到找到的视图是可以直接访问的)。如果回退视图不存在,则最终使用默认视图呈现界面。最后,View.js将更新地址栏中的hash为最终呈现的视图的视图ID。
开发者可以通过JS API:setFallbackViewId(viewId)
,也可以通过视图HTML节点上的data-view-fallback
属性设置回退视图ID。如:
View.ofId("setting").setFallbackViewId("profile")
section>
开发者通过API:view.setFallbackViewId(viewId)
或DOM属性:data-view-fallback
设置回退视图ID时,可以设定如下几种取值:
- 任一存在的确切的视图编号;
- 伪视图:
:default-view
(代表默认视图)
- 视图群组:
~groupName
(View.js将自动查找隶属于该群组的第一个视图)
视图跳转
View.js支持如下几种方式进行视图跳转:
-
A链接,如:
商品详情
此时,要求视图:goods-detail
为默认视图或可以直接访问。
-
data-view-rel属性,如:
商品详情span>
商品详情span>
个人中心div>
其中"@"符号用于告诉View.js进行页面跳转访问。
此时,要求视图:goods-detail
为默认视图或可以直接访问。
- JS API:
View.navTo
,如:
View.navTo("setting", {params: {a: 1}, options: {b: 2}});
- JS API:
View.changeTo
,如:
View.changeTo("setting", {params: {a: 1}, options: {b: 2}});
其中,1)和2)两种方式无法传参;3)和4)两种方式可以传参。
View.navTo
和View.changeTo
的差别之处,在于是否向浏览堆栈中添加记录。使用View.navTo
执行视图A向视图B的跳转时,用户可以在视图B中通过物理返回键返回A;使用View.changeTo
执行视图A向视图B的跳转时,A的浏览历史将被B所替代,用户在视图B中无法通过物理按键返回A。
识别浏览器的前进和后退
正如前文所说:“在技术上,View.js利用HTML5的History API,借助地址栏hash完成视图之间的导向和路由”。但视图跳转由于场景不同,所需要执行的操作也不相同。对于需要追溯操作的跳转,View.js将以"压入历史堆栈"的方式记录用户的浏览历史;而对于无需追溯操作的跳转,View.js则以"替换当前历史"的方式更改用户的浏览历史。
“压入历史堆栈"和"替换当前历史”,是HTML5新增的两项History操作接口,分别对应于原生API:history.pushState()
和history.replaceState()
。
当用户浏览使用View.js开发的网页时,View.js将为每一次的页面呈现动作(包括第一次)生成一个浏览状态。包括:浏览的视图编号,浏览视图时的客户端本地时间戳,以及视图的浏览选项(在地址栏中呈现的,与特定视图相关的视图级别的参数)。这些状态数据根据开发者执行视图跳转动作时使用的API不同,而动态"压入历史堆栈"或"替换当前历史"。
当用户通过物理按键,或借助浏览器提供的"前进",“回退"按钮功能浏览网页时,这些状态数据会借助浏览器自动触发popstate
事件通知到View.js。View.js通过比较当前的状态数据与弹出的状态数据的时间,可以判定页面是以"浏览器前进”,还是"浏览器"后退方式进入的,并将这一判定结果与其它相关数据以View事件(如:beforechange
, afterchange
等)的形式通知给开发者。如此一来,开发者可以通过监听相关事件,实现丰富的页面跳转动画。如:
var timer;
/* 浏览器支持前进后退判断 */
var historyPushPopSupported = ("pushState" in history) && (typeof history.pushState == "function");
View.setSwitchAnimation(function(srcElement, tarElement, type, render){
"hide2left, hide2right, show2left, show2right, fade-in, fade-out".split(/\s*,\s*/).forEach(function(className){
srcElement.classList.remove(className);
tarElement.classList.remove(className);
});
clearTimeout(timer);
timer = setTimeout(function(){
render();
var isNav = type == View.SWITCHTYPE_VIEWNAV,
isChange = type == View.SWITCHTYPE_VIEWCHANGE,
isHistoryBack = type == View.SWITCHTYPE_HISTORYBACK,
isHistoryForward = type == View.SWITCHTYPE_HISTORYFORWARD;
if(!historyPushPopSupported || isChange){
srcElement.classList.add("fade-out");
tarElement.classList.add("fade-in");
}else if(isHistoryForward || isNav){
srcElement.classList.add("hide2left");
tarElement.classList.add("show2left");
}else{
srcElement.classList.add("hide2right");
tarElement.classList.add("show2right");
}
}, 0);
视图当前的浏览状态,可以使用属性:View.currentState
访问,也可以使用浏览器支持的原生属性:history.state
。如:
console.log(View. currentState);// {"viewId":"SC_category","timestamp":1501228010961,"options":{"categoryId":"103"}}
视图参数
开发过网页程序的同学,尤其是纯前端开发的同学,差不多都会为复杂参数的传递苦恼过。毕竟地址栏参数的承载能力实在有限,基本上只适用于类型简单、尺寸较小的数据。
但使用View.js开发的单页应用并不存在该问题。
单页应用中,用户视觉效果上的页面切换,在技术上只是同一页面下不同区块的切换。因此,有足够大的空间和足够多的方式传递任意类型的数据,如:方法、数组、对象,以及DOM元素等。
View.js为所有视图切换API均加上了参数传递支持,包括:View.navTo()
, View.changeTo()
, View.back()
和View.forward()
。如:
View.navTo("detail", {params: {id: "001", count: 2, callback: function(){}});
View.back({params: {showHeader: true}});
目标视图则可以使用API:view.hasParameter()
及view.getParameter()
等方法检索相关参数,如:
View.ofId("detail").hasParameter("id");// -> true
View.ofId("detail").getParameter("count");// -> 2
需要注意的是,视图参数区分大小写,且在离开后均会被清空,并不会保留。如果开发者需要持久化使用相关参数,则可以将其手动放至上下文中。
视图选项
虽然视图参数能在很大程度上解决开发者传递数据的需要,但对于"目标视图支持页面刷新直接访问"这一应用场景就显得无能为力了。
通过视图选项传递的参数,目标视图刷新(视图需要可以被直接访问)后也仍然可以获取对应的参数。
视图选项以如下形态体现在浏览器的地址栏中:
http://domain/path/index.html#viewId!name1=value1&name2=value2[…]
由于视图选项是体现在地址栏中的参数,因而开发者传递参数时,应传递简单类型的参数,如:数字、字符串、布尔值等。但无论是哪一种简单类型,参数的接收方最终得到的,都将是字符串形态的参数。
视图选项仅与特定视图相关联,不会对不相关的视图可见,并且仅当关联的视图处于活动状态(亦即,是"活动视图")时才能通过API得到。如:
View.navTo("detail", {options: {a: 1}});
View.getActiveView().getId();// -> detail
console.log(location.hash);// -> #detail!a=1
View.getActiveViewOptions();// -> {a: 1}
需要注意的是,View.js将视图选项作为地址栏hash的一部分,并将其与视图名绑定在一起,从而使得浏览器前进后退时,视图选项都将再次呈现出来。
视图选项与视图参数相得益彰,互相补充。开发者可以根据自己需要选择合适的参数传递方式,甚至是两者的结合,如:
var goodsId = null;
if(view.hasParameter("goodsId"))
goodsId = view.getParameter("goodsId");
else{
var options = View.getActiveViewOptions();
if(null != options)
goodsId = options.goodsId;
}
if(null != goodsId){
//…
}
与此同时,View.js提供了简化的智能参数获取API:View.seekParameter(name)
以简化代码复杂度。该方法将优先从视图参数中查找相同名称的参数,如果参数不存在则从视图选项中查找;如果仍然不存在,则将从地址栏的queryString中查找。如果仍然不存在,则返回null
。因此,上面的代码可以简化为:
var goodsId = view.seekParameter("goodsId");
if(null != goodsId){
//…
}
视图切换动画
既然View.js属于单页应用框架,那么同一页面下的多个视图切换时,是完全有方式和空间实现页面的切换动画的。
是的,View.js支持这样做。
具体来讲,开发者在实现视图切换动画时,需要完成如下工作:
开发动画效果。动画效果以CSS动画为最佳。
任何情况下,活动视图对应的DOM元素都会含有active样式标记。开发者可以借助该标记实现动画的播放。
调用View.js的API:View. setSwitchAnimation(func)
来设置动画。
注:视图的切换动画只有这一种方式,如果开发者需要为不同视图之间的跳转呈现不同的动画,则需要在方法体中判定视图切换动作所关联的源视图和目标视图,然后执行不同的动作。 如果开发者没有额外定义或重载,View.js默认是没有动画的。此时,非活动视图将隐藏(display: none
),活动视图显示(display: block
)。
可以参考的视图切换动画代码,如下所示:
var timer;
/* 浏览器支持前进后退判断 */
var historyPushPopSupported = ("pushState" in history) && (typeof history.pushState == "function");
View.setSwitchAnimation(function(srcElement, tarElement, type, render){
"hide2left, hide2right, show2left, show2right, fade-in, fade-out".split(/\s*,\s*/).forEach(function(className){
srcElement.classList.remove(className);
tarElement.classList.remove(className);
});
clearTimeout(timer);
timer = setTimeout(function(){
render();
var isNav = type == View.SWITCHTYPE_VIEWNAV,
isChange = type == View.SWITCHTYPE_VIEWCHANGE,
isHistoryBack = type == View.SWITCHTYPE_HISTORYBACK,
isHistoryForward = type == View.SWITCHTYPE_HISTORYFORWARD;
if(!historyPushPopSupported || isChange){
srcElement.classList.add("fade-out");
tarElement.classList.add("fade-in");
}else if(isHistoryForward || isNav){
srcElement.classList.add("hide2left");
tarElement.classList.add("show2left");
}else{
srcElement.classList.add("hide2right");
tarElement.classList.add("show2right");
}
}, 0);
html{
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
body{
position: relative;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
-webkit-touch-callout: none;
-webkit-text-size-adjust: none;
-webkit-user-select: none;
}
*[data-view=true]{
position: absolute;
opacity: 0;
z-index: 0;
left: 0;
top: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
*[data-view=true].active{
opacity: 1;
z-index: 1;
}
@include keyframes(hide2left){
from{@include transform(translate3d(0, 0, 0) translate(0, 0)); opacity: 1}
to{@include transform(translate3d(0, 0, 0) translate(-100%, 0)); opacity: 1}
}
@include keyframes(show2left){
from{@include transform(translate3d(0, 0, 0) translate(100%, 0)); opacity: 1;}
to{@include transform(translate3d(0, 0, 0) translate(0, 0)); opacity: 1;}
}
@include keyframes(fadeIn){
from{opacity: 0.3;}to{opacity: 1;}
}
@include keyframes(fadeOut){
from{opacity: 1;}to{opacity: 0.3;}
}
DOM元素获取
每个View实例都一一关联着对应的DOM元素,开发者可以通过API:view.getDomElement()
来获取对应的DOM元素。
对于视图内的其它元素,View.js提供了简化版的获取API以减少编码量。包括:view.find(selector)
以及view.findAll(selector)
分别对应于原生API:domElement.querySelector(selector)
和domElement.querySelectorAll(selector)
。
注意事项
View.js当下只适用于移动端H5应用开发,并不适用PC端网页开发。
软件许可
MIT (https://opensource.org/licenses/mit-license.php)
最佳实践
View.js建议开发者在开发应用时,区分功能特性的隶属级别:视图级别或页面级别,以妥善安置相关脚本的工程位置。
如果开发者对视图的功能定义较为合理,则可以很方便的利用构建工具,如gulp.js等将视图动态构建至多个界面中以达到复用视图,减少开发工作量的目的。
对于单个视图的工程化组织,View.js建议开发者按照实际需要,分别建立config.xx.js,define.xx.js,init.xx.js,action.xx.js等文件,以提升工程的可维护程度。各个文件的前缀含义分别为:
-
config.xx.js
约定以"config."前缀开头定义的文件,用于存储视图的默认配置;
-
define.xx.js
约定以"define."前缀开头定义的文件,用于存储视图下多个脚本都会用到的方法或变量等,以达到复用的目的。
虽然开发者可以将复用的方法或变量定义至window
中,但这样无疑会提升变量污染的可能性。因此,View.js建议开发者将这些方法定义至相关视图的上下文中,以降低这样的风险;
开发者可以使用API:view.config.get(configItemName).setValue()
来设置配置取值,使用API:view.config.get(configItemName).getValue()
来获取取值;
-
init.xx.js
绝大多数视图需要在视图就绪,视图进入,或视图离开时执行特定的初始化动作,如:动态布局,查询接口或重置视图等。View.js建议开发者将此类动作以"init."前缀来定义;
-
action.xx.js
约定以"action."前缀开头定义的文件,用于置放用户参与的动作脚本。如:单次触摸、双击触摸以及其它手势或拖动等。
在加载次序上,View.js建议按照如下顺序加载视图的各个脚本:
- define.xx.js
- config.xx.js
- init.xx.js
- action.xx.js
此时,开发者需要确保文件之间的引用关系,谨防循环引用。
联系作者
如果您在使用View.js的过程中有不解之处,或发现View.js有不完善之处,以及其它建议或期望,请以电子邮件的方式联系作者。(主题包含前缀:"【View.js】")
作者:Billy, 电子邮箱地址:[email protected]。
FAQ
- FAQ:如何确定进入视图的源视图?
视图可以在ready及enter事件的监听句柄中获取进入视图的源视图。如:
View.ofId("detail").on("enter", function(e){
View sourceView = e.data.sourceView;
console.log(sourceView.getId());
});
- FAQ:如何获取要进入到的目标视图?
视图可以在leave事件的监听句柄中获取要进入到的目标视图。如:
View.ofId("detail").on("leave", function(){
console.log(e.data.targetView.getId());
});
- FAQ:为DOM元素添加的click监听为什么没有被触发?
这可能是因为添加click监听的DOM元素声明有data-view-rel属性。
之所以这样,是因为View.js在使能data-view-rel时,将其触摸事件preventDefault()了。对于这种情况,开发者可以去除data-view-rel属性,然后使用触摸编程借助View.js的API:View.navTo完成视图导向。触摸框架,推荐使用优秀的Hammer.js。
你可能感兴趣的:(HTML5 单页应用/框架 - View.js介绍)
- 理解Gunicorn:Python WSGI服务器的基石
范范0825
ipythonlinux运维
理解Gunicorn:PythonWSGI服务器的基石介绍Gunicorn,全称GreenUnicorn,是一个为PythonWSGI(WebServerGatewayInterface)应用设计的高效、轻量级HTTP服务器。作为PythonWeb应用部署的常用工具,Gunicorn以其高性能和易用性著称。本文将介绍Gunicorn的基本概念、安装和配置,帮助初学者快速上手。1.什么是Gunico
- Linux下QT开发的动态库界面弹出操作(SDL2)
13jjyao
QT类qt开发语言sdl2linux
需求:操作系统为linux,开发框架为qt,做成需带界面的qt动态库,调用方为java等非qt程序难点:调用方为java等非qt程序,也就是说调用方肯定不带QApplication::exec(),缺少了这个,QTimer等事件和QT创建的窗口将不能弹出(包括opencv也是不能弹出);这与qt调用本身qt库是有本质的区别的思路:1.调用方缺QApplication::exec(),那么我们在接口
- ArcGIS栅格计算器常见公式(赋值、0和空值的转换、补充栅格空值)
研学随笔
arcgis经验分享
我们在使用ArcGIS时通常经常用到栅格计算器,今天主要给大家介绍我日常中经常用到的几个公式,供大家参考学习。将特定值(-9999)赋值为0,例如-9999.Con("raster"==-9999,0,"raster")2.给空值赋予特定的值(如0)Con(IsNull("raster"),0,"raster")3.将特定的栅格值(如1)赋值为空值,其他保留原值SetNull("raster"==
- 网易严选官方旗舰店,优质商品,卓越服务
高省_飞智666600
网易严选官方旗舰店是网易旗下的一家电商平台,以提供优质商品和卓越服务而闻名。作为一名SEO优化师,我将为您详细介绍网易严选官方旗舰店,并重点强调其特点和优势。大家好!我是高省APP最大团队&联合创始人飞智导师。相较于其他返利app,高省APP的佣金更高,模式更好,最重要的是,终端用户不会流失!高省APP佣金更高,模式更好,终端用户不流失。【高省】是一个自用省钱佣金高,分享推广赚钱多的平台,百度有几
- Python中os.environ基本介绍及使用方法
鹤冲天Pro
#Pythonpython服务器开发语言
文章目录python中os.environos.environ简介os.environ进行环境变量的增删改查python中os.environ的使用详解1.简介2.key字段详解2.1常见key字段3.os.environ.get()用法4.环境变量的增删改查和判断是否存在4.1新增环境变量4.2更新环境变量4.3获取环境变量4.4删除环境变量4.5判断环境变量是否存在python中os.envi
- PHP环境搭建详细教程
好看资源平台
前端php
PHP是一个流行的服务器端脚本语言,广泛用于Web开发。为了使PHP能够在本地或服务器上运行,我们需要搭建一个合适的PHP环境。本教程将结合最新资料,介绍在不同操作系统上搭建PHP开发环境的多种方法,包括Windows、macOS和Linux系统的安装步骤,以及本地和Docker环境的配置。1.PHP环境搭建概述PHP环境的搭建主要分为以下几类:集成开发环境:例如XAMPP、WAMP、MAMP,这
- 基于社交网络算法优化的二维最大熵图像分割
智能算法研学社(Jack旭)
智能优化算法应用图像分割算法php开发语言
智能优化算法应用:基于社交网络优化的二维最大熵图像阈值分割-附代码文章目录智能优化算法应用:基于社交网络优化的二维最大熵图像阈值分割-附代码1.前言2.二维最大熵阈值分割原理3.基于社交网络优化的多阈值分割4.算法结果:5.参考文献:6.Matlab代码摘要:本文介绍基于最大熵的图像分割,并且应用社交网络算法进行阈值寻优。1.前言阅读此文章前,请阅读《图像分割:直方图区域划分及信息统计介绍》htt
- 直返最高等级与直返APP:无需邀请码的返利新体验
古楼
随着互联网的普及和电商的兴起,直返模式逐渐成为一种流行的商业模式。在这种模式下,消费者通过购买产品或服务,获得一定的返利,并可以分享给更多的人。其中,直返最高等级和直返APP是直返模式中的重要概念和工具。本文将详细介绍直返最高等级的概念、直返APP的使用以及与邀请码的关系。【高省】APP(高佣金领导者)是一个自用省钱佣金高,分享推广赚钱多的平台,百度有几百万篇报道,运行三年,稳定可靠。高省APP,
- DIV+CSS+JavaScript技术制作网页(旅游主题网页设计与制作)云南大理
STU学生网页设计
网页设计期末网页作业html静态网页html5期末大作业网页设计web大作业
️精彩专栏推荐作者主页:【进入主页—获取更多源码】web前端期末大作业:【HTML5网页期末作业(1000套)】程序员有趣的告白方式:【HTML七夕情人节表白网页制作(110套)】文章目录二、网站介绍三、网站效果▶️1.视频演示2.图片演示四、网站代码HTML结构代码CSS样式代码五、更多源码二、网站介绍网站布局方面:计划采用目前主流的、能兼容各大主流浏览器、显示效果稳定的浮动网页布局结构。网站程
- 【加密社】Solidity 中的事件机制及其应用
加密社
闲侃区块链智能合约区块链
加密社引言在Solidity合约开发过程中,事件(Events)是一种非常重要的机制。它们不仅能够让开发者记录智能合约的重要状态变更,还能够让外部系统(如前端应用)监听这些状态的变化。本文将详细介绍Solidity中的事件机制以及如何利用不同的手段来触发、监听和获取这些事件。事件存储的地方当我们在Solidity合约中使用emit关键字触发事件时,该事件会被记录在区块链的交易收据中。具体而言,事件
- 探索OpenAI和LangChain的适配器集成:轻松切换模型提供商
nseejrukjhad
langchaineasyui前端python
#探索OpenAI和LangChain的适配器集成:轻松切换模型提供商##引言在人工智能和自然语言处理的世界中,OpenAI的模型提供了强大的能力。然而,随着技术的发展,许多人开始探索其他模型以满足特定需求。LangChain作为一个强大的工具,集成了多种模型提供商,通过提供适配器,简化了不同模型之间的转换。本篇文章将介绍如何使用LangChain的适配器与OpenAI集成,以便轻松切换模型提供商
- 使用Faiss进行高效相似度搜索
llzwxh888
faisspython
在现代AI应用中,快速和高效的相似度搜索是至关重要的。Faiss(FacebookAISimilaritySearch)是一个专门用于快速相似度搜索和聚类的库,特别适用于高维向量。本文将介绍如何使用Faiss来进行相似度搜索,并结合Python代码演示其基本用法。什么是Faiss?Faiss是一个由FacebookAIResearch团队开发的开源库,主要用于高维向量的相似性搜索和聚类。Faiss
- 使用LLaVa和Ollama实现多模态RAG示例
llzwxh888
python人工智能开发语言
本文将详细介绍如何使用LLaVa和Ollama实现多模态RAG(检索增强生成),通过提取图像中的结构化数据、生成图像字幕等功能来展示这一技术的强大之处。安装环境首先,您需要安装以下依赖包:!pipinstallllama-index-multi-modal-llms-ollama!pipinstallllama-index-readers-file!pipinstallunstructured!p
- 使用Apify加载Twitter消息以进行微调的完整指南
nseejrukjhad
twittereasyui前端python
#使用Apify加载Twitter消息以进行微调的完整指南##引言在自然语言处理领域,微调模型以适应特定任务是提升模型性能的常见方法。本文将介绍如何使用Apify从Twitter导出聊天信息,以便进一步进行微调。##主要内容###使用Apify导出推文首先,我们需要从Twitter导出推文。Apify可以帮助我们做到这一点。通过Apify的强大功能,我们可以批量抓取和导出数据,适用于各类应用场景。
- 利用Requests Toolkit轻松完成HTTP请求
nseejrukjhad
http网络协议网络python
RequestsToolkit的力量:轻松构建HTTP请求Agent在现代软件开发中,API请求是与外部服务交互的核心。RequestsToolkit提供了一种便捷的方式,帮助开发者构建自动化的HTTP请求Agent。本文旨在详细介绍RequestsToolkit的设置、使用和潜在挑战。引言RequestsToolkit是一个强大的工具包,可用于构建执行HTTP请求的智能代理。这对于想要自动化与外
- 深入理解 MultiQueryRetriever:提升向量数据库检索效果的强大工具
nseejrukjhad
数据库python
深入理解MultiQueryRetriever:提升向量数据库检索效果的强大工具引言在人工智能和自然语言处理领域,高效准确的信息检索一直是一个关键挑战。传统的基于距离的向量数据库检索方法虽然广泛应用,但仍存在一些局限性。本文将介绍一种创新的解决方案:MultiQueryRetriever,它通过自动生成多个查询视角来增强检索效果,提高结果的相关性和多样性。MultiQueryRetriever的工
- 利用LangChain的StackExchange组件实现智能问答系统
nseejrukjhad
langchainmicrosoft数据库python
利用LangChain的StackExchange组件实现智能问答系统引言在当今的软件开发世界中,StackOverflow已经成为程序员解决问题的首选平台之一。而LangChain作为一个强大的AI应用开发框架,提供了StackExchange组件,使我们能够轻松地将StackOverflow的海量知识库集成到我们的应用中。本文将详细介绍如何使用LangChain的StackExchange组件
- 如何部分格式化提示模板:LangChain中的高级技巧
nseejrukjhad
langchainjava服务器python
标题:如何部分格式化提示模板:LangChain中的高级技巧内容:如何部分格式化提示模板:LangChain中的高级技巧引言在使用大型语言模型(LLM)时,提示工程是一个关键环节。LangChain提供了强大的提示模板功能,让我们能更灵活地构建和管理提示。本文将介绍LangChain中一个高级特性-部分格式化提示模板,这个技巧可以让你的提示管理更加高效和灵活。什么是部分格式化提示模板?部分格式化提
- 东南林氏之九牧林候选父系
祖缘树TheYtree
渊源介绍东晋初年晋安林始祖林禄公入闽,传十世隋右丞林茂,由晋安迁居莆田北螺村。又五世而至林万宠,唐开元间任高平太守,生三子:韬、披、昌。韬公之孙攒,唐德宗立双阙以旌表其孝,时号"阙下林家"。昌公字茂吉,乃万宠公第三子,官兵部司马,配宋氏,生一子名萍。萍于唐贞元间明经及第,官沣洲司马(后追赠中宪大夫)。唐太和年间归隐后,迁居仙游游洋,世称“游洋林”;其后裔居游洋后迁移漳州漳浦路下,由路下林第四房平和
- 关于城市旅游的HTML网页设计——(旅游风景云南 5页)HTML+CSS+JavaScript
二挡起步
web前端期末大作业javascripthtmlcss旅游风景
⛵源码获取文末联系✈Web前端开发技术描述网页设计题材,DIV+CSS布局制作,HTML+CSS网页设计期末课程大作业|游景点介绍|旅游风景区|家乡介绍|等网站的设计与制作|HTML期末大学生网页设计作业,Web大学生网页HTML:结构CSS:样式在操作方面上运用了html5和css3,采用了div+css结构、表单、超链接、浮动、绝对定位、相对定位、字体样式、引用视频等基础知识JavaScrip
- HTML网页设计制作大作业(div+css) 云南我的家乡旅游景点 带文字滚动
二挡起步
web前端期末大作业web设计网页规划与设计htmlcssjavascriptdreamweaver前端
Web前端开发技术描述网页设计题材,DIV+CSS布局制作,HTML+CSS网页设计期末课程大作业游景点介绍|旅游风景区|家乡介绍|等网站的设计与制作HTML期末大学生网页设计作业HTML:结构CSS:样式在操作方面上运用了html5和css3,采用了div+css结构、表单、超链接、浮动、绝对定位、相对定位、字体样式、引用视频等基础知识JavaScript:做与用户的交互行为文章目录前端学习路线
- 每日算法&面试题,大厂特训二十八天——第二十天(树)
肥学
⚡算法题⚡面试题每日精进java算法数据结构
目录标题导读算法特训二十八天面试题点击直接资料领取导读肥友们为了更好的去帮助新同学适应算法和面试题,最近我们开始进行专项突击一步一步来。上一期我们完成了动态规划二十一天现在我们进行下一项对各类算法进行二十八天的一个小总结。还在等什么快来一起肥学进行二十八天挑战吧!!特别介绍小白练手专栏,适合刚入手的新人欢迎订阅编程小白进阶python有趣练手项目里面包括了像《机器人尬聊》《恶搞程序》这样的有趣文章
- Faiss Tips:高效向量搜索与聚类的利器
焦习娜Samantha
FaissTips:高效向量搜索与聚类的利器faiss_tipsSomeusefultipsforfaiss项目地址:https://gitcode.com/gh_mirrors/fa/faiss_tips项目介绍Faiss是由FacebookAIResearch开发的一个用于高效相似性搜索和密集向量聚类的库。它支持多种硬件平台,包括CPU和GPU,能够在海量数据集上实现快速的近似最近邻搜索(AN
- node.js学习
小猿L
node.jsnode.js学习vim
node.js学习实操及笔记温故node.js,node.js学习实操过程及笔记~node.js学习视频node.js官网node.js中文网实操笔记githubcsdn笔记为什么学node.js可以让别人访问我们编写的网页为后续的框架学习打下基础,三大框架vuereactangular离不开node.jsnode.js是什么官网:node.js是一个开源的、跨平台的运行JavaScript的运行
- 自我意识
徐立华
----读帕克.帕尔默《教学勇气》(P18----19)5.铸造我们的学科帕克.帕尔默说学科知识对我们的自身认同和外部世界有启发意义。学科会铸造我们。“在我们与学科的命题概念和学科的生活框架相遇之前,自我意识知识处于潜伏状态,通过回想学科是怎样唤醒自我意识,我们就可以找回教学心灵。”《教学勇气》(P18)我们的自我意识像冰山表面下无限延伸的冰层,常常处于潜伏状态。但是在我们对所教授的学科进行深入思
- 高级 ECharts 技巧:自定义图表主题与样式
SnowMan1993
echarts信息可视化数据分析
ECharts是一个强大的数据可视化库,提供了多种内置主题和样式,但你也可以根据项目的设计需求,自定义图表的主题与样式。本文将介绍如何使用ECharts自定义图表主题,以提升数据可视化的吸引力和一致性。1.什么是ECharts主题?ECharts的主题是指定义图表样式的配置项,包括颜色、字体、线条样式等。通过预设主题,你可以快速更改图表的整体风格,而自定义主题则允许你在此基础上进行个性化设置。2.
- Redis系列:Geo 类型赋能亿级地图位置计算
Ly768768
redisbootstrap数据库
1前言我们在篇深刻理解高性能Redis的本质的时候就介绍过Redis的几种基本数据结构,它是基于不同业务场景而设计的:动态字符串(REDIS_STRING):整数(REDIS_ENCODING_INT)、字符串(REDIS_ENCODING_RAW)双端列表(REDIS_ENCODING_LINKEDLIST)压缩列表(REDIS_ENCODING_ZIPLIST)跳跃表(REDIS_ENCODI
- ARM驱动学习之基础小知识
JT灬新一
ARM嵌入式arm开发学习
ARM驱动学习之基础小知识•sch原理图工程师工作内容–方案–元器件选型–采购(能不能买到,价格)–原理图(涉及到稳定性)•layout画板工程师–layout(封装、布局,布线,log)(涉及到稳定性)–焊接的一部分工作(调试阶段板子的焊接)•驱动工程师–驱动,原理图,layout三部分的交集容易发生矛盾•PCB研发流程介绍–方案,原理图(网表)–layout工程师(gerber文件)–PCB板
- Low Power概念介绍-Voltage Area
飞奔的大虎
随着智能手机,以及物联网的普及,芯片功耗的问题最近几年得到了越来越多的重视。为了实现集成电路的低功耗设计目标,我们需要在系统设计阶段就采用低功耗设计的方案。而且,随着设计流程的逐步推进,到了芯片后端设计阶段,降低芯片功耗的方法已经很少了,节省的功耗百分比也不断下降。芯片的功耗主要由静态功耗(staticleakagepower)和动态功耗(dynamicpower)构成。静态功耗主要是指电路处于等
- 基于CODESYS的多轴运动控制程序框架:逻辑与运动控制分离,快速开发灵活操作
GPJnCrbBdl
python开发语言
基于codesys开发的多轴运动控制程序框架,将逻辑与运动控制分离,将单轴控制封装成功能块,对该功能块的操作包含了所有的单轴控制(归零、点动、相对定位、绝对定位、设置当前位置、伺服模式切换等等)。程序框架由主程序按照状态调用分归零模式、手动模式、自动模式、故障模式,程序状态的跳转都已完成,只需要根据不同的工艺要求完成所需的动作即可。变量的声明、地址的规划都严格按照C++的标准定义,能帮助开发者快速
- 解线性方程组
qiuwanchi
package gaodai.matrix;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scanner = new Sc
- 在mysql内部存储代码
annan211
性能mysql存储过程触发器
在mysql内部存储代码
在mysql内部存储代码,既有优点也有缺点,而且有人倡导有人反对。
先看优点:
1 她在服务器内部执行,离数据最近,另外在服务器上执行还可以节省带宽和网络延迟。
2 这是一种代码重用。可以方便的统一业务规则,保证某些行为的一致性,所以也可以提供一定的安全性。
3 可以简化代码的维护和版本更新。
4 可以帮助提升安全,比如提供更细
- Android使用Asynchronous Http Client完成登录保存cookie的问题
hotsunshine
android
Asynchronous Http Client是android中非常好的异步请求工具
除了异步之外还有很多封装比如json的处理,cookie的处理
引用
Persistent Cookie Storage with PersistentCookieStore
This library also includes a PersistentCookieStore whi
- java面试题
Array_06
java面试
java面试题
第一,谈谈final, finally, finalize的区别。
final-修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能
- 网站加速
oloz
网站加速
前序:本人菜鸟,此文研究总结来源于互联网上的资料,大牛请勿喷!本人虚心学习,多指教.
1、减小网页体积的大小,尽量采用div+css模式,尽量避免复杂的页面结构,能简约就简约。
2、采用Gzip对网页进行压缩;
GZIP最早由Jean-loup Gailly和Mark Adler创建,用于UNⅨ系统的文件压缩。我们在Linux中经常会用到后缀为.gz
- 正确书写单例模式
随意而生
java 设计模式 单例
单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问题,但如果你不知道如何创建一个线程安全的单例,不知道什么是双检锁,那这篇文章可能会帮助到你。
懒汉式,线程不安全
当被问到要实现一个单例模式时,很多人的第一反应是写出如下的代码,包括教科书上也是这样
- 单例模式
香水浓
java
懒汉 调用getInstance方法时实例化
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(null == ins
- 安装Apache问题:系统找不到指定的文件 No installed service named "Apache2"
AdyZhang
apachehttp server
安装Apache问题:系统找不到指定的文件 No installed service named "Apache2"
每次到这一步都很小心防它的端口冲突问题,结果,特意留出来的80端口就是不能用,烦。
解决方法确保几处:
1、停止IIS启动
2、把端口80改成其它 (譬如90,800,,,什么数字都好)
3、防火墙(关掉试试)
在运行处输入 cmd 回车,转到apa
- 如何在android 文件选择器中选择多个图片或者视频?
aijuans
android
我的android app有这样的需求,在进行照片和视频上传的时候,需要一次性的从照片/视频库选择多条进行上传
但是android原生态的sdk中,只能一个一个的进行选择和上传。
我想知道是否有其他的android上传库可以解决这个问题,提供一个多选的功能,可以使checkbox之类的,一次选择多个 处理方法
官方的图片选择器(但是不支持所有版本的androi,只支持API Level
- mysql中查询生日提醒的日期相关的sql
baalwolf
mysql
SELECT sysid,user_name,birthday,listid,userhead_50,CONCAT(YEAR(CURDATE()),DATE_FORMAT(birthday,'-%m-%d')),CURDATE(), dayofyear( CONCAT(YEAR(CURDATE()),DATE_FORMAT(birthday,'-%m-%d')))-dayofyear(
- MongoDB索引文件破坏后导致查询错误的问题
BigBird2012
mongodb
问题描述:
MongoDB在非正常情况下关闭时,可能会导致索引文件破坏,造成数据在更新时没有反映到索引上。
解决方案:
使用脚本,重建MongoDB所有表的索引。
var names = db.getCollectionNames();
for( var i in names ){
var name = names[i];
print(name);
- Javascript Promise
bijian1013
JavaScriptPromise
Parse JavaScript SDK现在提供了支持大多数异步方法的兼容jquery的Promises模式,那么这意味着什么呢,读完下文你就了解了。
一.认识Promises
“Promises”代表着在javascript程序里下一个伟大的范式,但是理解他们为什么如此伟大不是件简
- [Zookeeper学习笔记九]Zookeeper源代码分析之Zookeeper构造过程
bit1129
zookeeper
Zookeeper重载了几个构造函数,其中构造者可以提供参数最多,可定制性最多的构造函数是
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolea
- 【Java命令三】jstack
bit1129
jstack
jstack是用于获得当前运行的Java程序所有的线程的运行情况(thread dump),不同于jmap用于获得memory dump
[hadoop@hadoop sbin]$ jstack
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F
- jboss 5.1启停脚本 动静分离部署
ronin47
以前启动jboss,往各种xml配置文件,现只要运行一句脚本即可。start nohup sh /**/run.sh -c servicename -b ip -g clustername -u broatcast jboss.messaging.ServerPeerID=int -Djboss.service.binding.set=p
- UI之如何打磨设计能力?
brotherlamp
UIui教程ui自学ui资料ui视频
在越来越拥挤的初创企业世界里,视觉设计的重要性往往可以与杀手级用户体验比肩。在许多情况下,尤其对于 Web 初创企业而言,这两者都是不可或缺的。前不久我们在《右脑革命:别学编程了,学艺术吧》中也曾发出过重视设计的呼吁。如何才能提高初创企业的设计能力呢?以下是 9 位创始人的体会。
1.找到自己的方式
如果你是设计师,要想提高技能可以去设计博客和展示好设计的网站如D-lists或
- 三色旗算法
bylijinnan
java算法
import java.util.Arrays;
/**
问题:
假设有一条绳子,上面有红、白、蓝三种颜色的旗子,起初绳子上的旗子颜色并没有顺序,
您希望将之分类,并排列为蓝、白、红的顺序,要如何移动次数才会最少,注意您只能在绳
子上进行这个动作,而且一次只能调换两个旗子。
网上的解法大多类似:
在一条绳子上移动,在程式中也就意味只能使用一个阵列,而不使用其它的阵列来
- 警告:No configuration found for the specified action: \'s
chiangfai
configuration
1.index.jsp页面form标签未指定namespace属性。
<!--index.jsp代码-->
<%@taglib prefix="s" uri="/struts-tags"%>
...
<s:form action="submit" method="post"&g
- redis -- hash_max_zipmap_entries设置过大有问题
chenchao051
redishash
使用redis时为了使用hash追求更高的内存使用率,我们一般都用hash结构,并且有时候会把hash_max_zipmap_entries这个值设置的很大,很多资料也推荐设置到1000,默认设置为了512,但是这里有个坑
#define ZIPMAP_BIGLEN 254
#define ZIPMAP_END 255
/* Return th
- select into outfile access deny问题
daizj
mysqltxt导出数据到文件
本文转自:http://hatemysql.com/2010/06/29/select-into-outfile-access-deny%E9%97%AE%E9%A2%98/
为应用建立了rnd的帐号,专门为他们查询线上数据库用的,当然,只有他们上了生产网络以后才能连上数据库,安全方面我们还是很注意的,呵呵。
授权的语句如下:
grant select on armory.* to rn
- phpexcel导出excel表简单入门示例
dcj3sjt126com
PHPExcelphpexcel
<?php
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
if (PHP_SAPI == 'cli')
die('This example should only be run from a Web Brows
- 美国电影超短200句
dcj3sjt126com
电影
1. I see. 我明白了。2. I quit! 我不干了!3. Let go! 放手!4. Me too. 我也是。5. My god! 天哪!6. No way! 不行!7. Come on. 来吧(赶快)8. Hold on. 等一等。9. I agree。 我同意。10. Not bad. 还不错。11. Not yet. 还没。12. See you. 再见。13. Shut up!
- Java访问远程服务
dyy_gusi
httpclientwebservicegetpost
随着webService的崛起,我们开始中会越来越多的使用到访问远程webService服务。当然对于不同的webService框架一般都有自己的client包供使用,但是如果使用webService框架自己的client包,那么必然需要在自己的代码中引入它的包,如果同时调运了多个不同框架的webService,那么就需要同时引入多个不同的clien
- Maven的settings.xml配置
geeksun
settings.xml
settings.xml是Maven的配置文件,下面解释一下其中的配置含义:
settings.xml存在于两个地方:
1.安装的地方:$M2_HOME/conf/settings.xml
2.用户的目录:${user.home}/.m2/settings.xml
前者又被叫做全局配置,后者被称为用户配置。如果两者都存在,它们的内容将被合并,并且用户范围的settings.xml优先。
- ubuntu的init与系统服务设置
hongtoushizi
ubuntu
转载自:
http://iysm.net/?p=178 init
Init是位于/sbin/init的一个程序,它是在linux下,在系统启动过程中,初始化所有的设备驱动程序和数据结构等之后,由内核启动的一个用户级程序,并由此init程序进而完成系统的启动过程。
ubuntu与传统的linux略有不同,使用upstart完成系统的启动,但表面上仍维持init程序的形式。
运行
- 跟我学Nginx+Lua开发目录贴
jinnianshilongnian
nginxlua
使用Nginx+Lua开发近一年的时间,学习和实践了一些Nginx+Lua开发的架构,为了让更多人使用Nginx+Lua架构开发,利用春节期间总结了一份基本的学习教程,希望对大家有用。也欢迎谈探讨学习一些经验。
目录
第一章 安装Nginx+Lua开发环境
第二章 Nginx+Lua开发入门
第三章 Redis/SSDB+Twemproxy安装与使用
第四章 L
- php位运算符注意事项
home198979
位运算PHP&
$a = $b = $c = 0;
$a & $b = 1;
$b | $c = 1
问a,b,c最终为多少?
当看到这题时,我犯了一个低级错误,误 以为位运算符会改变变量的值。所以得出结果是1 1 0
但是位运算符是不会改变变量的值的,例如:
$a=1;$b=2;
$a&$b;
这样a,b的值不会有任何改变
- Linux shell数组建立和使用技巧
pda158
linux
1.数组定义 [chengmo@centos5 ~]$ a=(1 2 3 4 5) [chengmo@centos5 ~]$ echo $a 1 一对括号表示是数组,数组元素用“空格”符号分割开。
2.数组读取与赋值 得到长度: [chengmo@centos5 ~]$ echo ${#a[@]} 5 用${#数组名[@或
- hotspot源码(JDK7)
ol_beta
javaHotSpotjvm
源码结构图,方便理解:
├─agent Serviceab
- Oracle基本事务和ForAll执行批量DML练习
vipbooks
oraclesql
基本事务的使用:
从账户一的余额中转100到账户二的余额中去,如果账户二不存在或账户一中的余额不足100则整笔交易回滚
select * from account;
-- 创建一张账户表
create table account(
-- 账户ID
id number(3) not null,
-- 账户名称
nam