分类: JavaScript
vue中template的三种写法
第一种(字符串模板写法):
直接写在vue构造器里,这种写法比较直观,适用于html代码不多的场景,但是如果模板里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 |
<!DOCTYPE html> <html> <!-- WARNING! Make sure that you match all Quasar related tags to the same version! (Below it's "@1.7.4") --> <head> <!-- <link href="https://cdn.jsdelivr.net/npm/quasar@1.7.4/dist/quasar.min.css" rel="stylesheet" type="text/css"> --> <link href="https://www.mobibrw.com/wp-content/uploads/2020/06/quasar@1.7.4.css" rel="stylesheet" type="text/css"> </head> <body> <div id="q-app"> </div> <!-- Add the following at the end of your body tag --> <!-- <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/quasar@1.7.4/dist/quasar.umd.min.js"></script> --> <script src="https://www.mobibrw.com/wp-content/uploads/2020/06/vue@2.0.0.js"></script> <script src="https://www.mobibrw.com/wp-content/uploads/2020/06/quasar.umd@1.7.4.js"></script> <script> /* Example kicking off the UI. Obviously, adapt this to your specific needs. Assumes you have a <div id="q-app"></div> in your <body> above */ new Vue({ el: '#q-app', data: function () { return { version: Quasar.version } }, template: `<div class="q-ma-md"> Running Quasar v{{ version }}</div>` // ...etc }) </script> </body> </html> |
第二种:
直接写在template标签里,这种写法跟写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 |
<!DOCTYPE html> <html> <!-- WARNING! Make sure that you match all Quasar related tags to the same version! (Below it's "@1.7.4") --> <head> <!-- <link href="https://cdn.jsdelivr.net/npm/quasar@1.7.4/dist/quasar.min.css" rel="stylesheet" type="text/css"> --> <link href="https://www.mobibrw.com/wp-content/uploads/2020/06/quasar@1.7.4.css" rel="stylesheet" type="text/css"> </head> <body> <div id="q-app"> <template id='template1'> <div class="q-ma-md"> Running Quasar v{{ version }} </div> </template> </div> <!-- Add the following at the end of your body tag --> <!-- <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/quasar@1.7.4/dist/quasar.umd.min.js"></script> --> <script src="https://www.mobibrw.com/wp-content/uploads/2020/06/vue@2.0.0.js"></script> <script src="https://www.mobibrw.com/wp-content/uploads/2020/06/quasar.umd@1.7.4.js"></script> <script> /* Example kicking off the UI. Obviously, adapt this to your specific needs. Assumes you have a <div id="q-app"></div> in your <body> above */ new Vue({ el: '#q-app', data: function () { return { version: Quasar.version } }, template: '#template1' // ...etc }) </script> </body> </html> |
第三种:
写在script标签里,这种写法官方推荐,vue官方推荐script中type属性加上"x-template",即:
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 |
<!DOCTYPE html> <html> <!-- WARNING! Make sure that you match all Quasar related tags to the same version! (Below it's "@1.7.4") --> <head> <!-- <link href="https://cdn.jsdelivr.net/npm/quasar@1.7.4/dist/quasar.min.css" rel="stylesheet" type="text/css"> --> <link href="https://www.mobibrw.com/wp-content/uploads/2020/06/quasar@1.7.4.css" rel="stylesheet" type="text/css"> </head> <body> <div id="q-app"></div> <script type="x-template" id="template1"> <div class="q-ma-md"> Running Quasar v{{ version }} </div> </script> <!-- Add the following at the end of your body tag --> <!-- <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/quasar@1.7.4/dist/quasar.umd.min.js"></script> --> <script src="https://www.mobibrw.com/wp-content/uploads/2020/06/vue@2.0.0.js"></script> <script src="https://www.mobibrw.com/wp-content/uploads/2020/06/quasar.umd@1.7.4.js"></script> <script> /* Example kicking off the UI. Obviously, adapt this to your specific needs. Assumes you have a <div id="q-app"></div> in your <body> above */ new Vue({ el: '#q-app', data: function () { return { version: Quasar.version } }, template: '#template1' // ...etc }) </script> </body> </html> |
推荐这个写法的主要原因就可以把模版文件写到另外的一个JS文件里面,然后动态加载,实现模版复用以及文件分离。不至于一个页面里面实现太多的东西,导致页面代码混乱。
参考链接
16款优秀的Vue UI组件库推荐
Vue 是一个轻巧、高性能、可组件化的MVVM库,API简洁明了,上手快。从Vue推出以来,得到众多Web开发者的认可。
在公司的Web前端项目开发中,多个项目采用基于Vue的UI组件框架开发,并投入正式使用。
开发团队在使用Vue.js框架和UI组件库以后,开发效率大大提高,自己写的代码也少了,很多界面效果组件已经封装好了。
在选择Vue UI组件库的过程中,通过GitHub上根据star数量、文档丰富程度、更新的频率以及维护等因素,也收集整理了一些优秀的Vue UI组件库。
PS:国内的UI组件大部分都只有一部分的,常用的头部导航,底部导航,listview,grid表格很多都是没有的。
后面才发现,基于Vue的Quasar Framework 介绍 这个框架UI组件很全面,准备下次使用这个框架了
基于Vue的Quasar Framework 中文网
http://www.quasarchs.com/
quasarframework/quasar: Quasar Framework
https://github.com/quasarframework/quasar
Quasar(发音为/kweɪ.zɑɹ/)是MIT许可的开源框架(基于Vue),可帮助Web开发人员创建:
响应式网站
PWA(Progressive Web App)
通过Apache Cordova构建移动APP(Android,iOS,…)
多平台桌面应用程序(使用Electron)
Quasar允许开发人员编写一次代码,然后使用相同的代码库同时部署为网站、PWA、Mobile App和Electron App。使用最先进的CLI设计应用程序,并提供精心编写,速度非常快的Quasar Web组件。
当使用Quasar时,你不需要像Hammerjs,Momentjs或Bootstrap这样的额外重型库。它拥有这些功能,而且体积很小!
==============
1、 iView UI组件库
iView 是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品。iView的组件还是比较齐全的,更新也很快,文档写得很详细。有公司团队维护,比较可靠的Vue UI组件框架。iView生态也做得很好,还有开源了一个iView Admin,做后台非常方便。官网上介绍,iView已经应用在TalkingData、阿里巴巴、百度、腾讯、今日头条、京东、滴滴出行、美团、新浪、联想等大型公司的产品中。
iView官网:https://www.iviewui.com/
2、Vux UI组件库
Vux是基于WeUI和Vue2.x开发的移动端UI组件库,主要服务于微信页面。Vux的定位已经很明确了,一是:Vue移动端UI组件库,二是:WeUI的基础样式库。Vux的组件涵盖了所有的WeUI的内容,还扩展了一些常用的组件。比如:Sticky、timeline、v-chart、XCircle。Vux是个人维护的。但是GitHub上star还是很高的,达到13k。在GitHub上看到对issue的关闭还是很迅速的。Vux文档基本的组件用法和效果都讲解到位了。在vux官网上也展示了很多Vux的使用案例。在微信页面开发中,基本没有太多的bug,开发还是比较顺手的。
Vux官网:https://vux.li/
3、Element UI组件库
Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。Element是饿了么前端开源维护的Vue UI组件库,更新频率还是很高的,基本一周到半个月都会发布一个新版本。组件齐全,基本涵盖后台所需的所有组件,文档讲解详细,例子也很丰富。没有实际使用过,网上的Element教程和文章比较多。Element应该是一个质量比较高的Vue UI组件库。
Element官网:http://element.eleme.io/#/zh-CN
4、Mint UI组件库
Mint UI基于 Vue.js 的移动端组件库,同样出自饿了么前端的项目。Mint UI是真正意义上的按需加载组件。可以只加载声明过的组件及其样式文件。Mint UI 采用 CSS3 处理各种动效,避免浏览器进行不必要的重绘和重排,从而使用户获得流畅顺滑的体验。网上的视频教程很多都是基于Mint UI来讲的,开发移动端web项目还是很方便,文档也很简介明了。很多页面Mint UI组件都已经封装好,基本可以照着例子写,简单的调整一下就可以实现。不过,在GitHub上看最后一次代码提交在2018年1月16日。不知道是项目比较稳定没有更新,还是项目有被废弃的可能。
Mint UI官网:http://mint-ui.github.io/#!/zh-cn
5、Bootstrap-Vue UI组件库
Bootstrap-VUE提供了基于vue2的Bootstrap V4组件和网格系统的实现,完成了广泛和自动化的WAI ARA可访问性标记。Bootstrap 4是最新发布的版本,与 Bootstrap3 相比拥有了更多的具体的类以及把一些有关的部分变成了相关的组件。同时 Bootstrap.min.css 的体积减少了40%以上。Bootstrap4 放弃了对 IE8 以及 iOS 6 的支持,现在仅仅支持 IE9 以上 以及 iOS 7 以上版本的浏览器。想当初刚流行响应式网站的时候,Bootstrap是世界上最受欢迎的建立移动优先网站的框架,Bootstrap可以说风靡全球。就算放在现在很多企业网站都是采用Bootstrap做的响应式。Bootstrap-Vue可以让你在Vue中也实现Bootstrap的效果。
Bootstrap-Vue官网:https://bootstrap-vue.js.org/
6、Ant Design Vue UI组件库
Ant Design Vue是 Ant Design 3.X 的 Vue 实现,开发和服务于企业级后台产品。在GitHub上可以找到几个Ant Design的Vue组件。不过相比较而言,Ant Design Vue更胜一筹。Ant Design Vue共享Ant Design of React设计工具体系,实现了所有Ant Design of React的组件,支持现代浏览器和 IE9 及以上(需要 polyfills)。可以让熟悉Ant Design的在使用Vue时,很容易的上手。
Ant Design Vue官网:https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/
7、AT-UI UI组件库
AT-UI 是一款基于 Vue.js 2.0 的前端 UI 组件库,主要用于快速开发 PC 网站中后台产品,支持现代浏览器和 IE9 及以上。AT-UI 更加精简,实现了后台常用的组件。
AT_UI官网:https://at-ui.github.io/at-ui/#/zh
8、Vant UI组件库
Vant是一个轻量、可靠的移动端 Vue 组件库。Vant是有赞团队开源的,主要维护也是有赞团队。Vant Weapp 是有赞移动端组件库 Vant 的小程序版本,两者基于相同的视觉规范,提供一致的 API 接口,助力开发者快速搭建小程序应用。截止到目前,Vant已经开源了50+ 个经过有赞线上业务检验的组件。比如:、AddressEdit 地址编辑、AddressList 地址列表、Area 省市区选择、Card 卡片、Contact 联系人、Coupon 优惠券、GoodsAction 商品页行动点、SubmitBar 提交订单栏、Sku 商品规格弹层。如果做商城的,不太在意界面,实现业务逻辑的话,用Vant组件库开发还是很快的。
Vant官网:https://youzan.github.io/vant/#/zh-CN/intro
9、cube-ui UI组件库
cube-ui 是基于 Vue.js 实现的精致移动端组件库。由滴滴内部组件库精简提炼而来,经历了业务一年多的考验,并且每个组件都有充分单元测试,为后续集成提供保障。在交互体验方面追求极致。遵循统一的设计交互标准,高度还原设计效果;接口标准化,统一规范使用方式,开发更加简单高效。支持按需引入和后编译,轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。
cube-ui官网:https://didi.github.io/cube-ui/#/zh-CN
10、Muse-UI UI组件库
Muse-UI基于 Vue 2.0 优雅的 Material Design UI 组件库。Muse UI 拥有40多个UI 组件,用于适应不同业务环境。Muse UI 仅需少量代码即可完成主题样式替换。Muse UI 可用于开发的复杂单页应用
Muse-UI官网:https://muse-ui.org/#/zh-CN
11、N3-components UI组件库
N3组件库是基于Vue.js构建的,让前端工程师和全栈工程师能快速构建页面和应用。N3-components超过60个组件 组件列表、自定义样式、支持多种模化范式(UMD)、使用ES6进行开发。
N3官网:https://n3-components.github.io/N3-components/component.html
12、Mand Mobile
Mand Mobile是面向金融场景的Vue移动端UI组件库,丰富、灵活、实用,快速搭建优质的金融类产品,让复杂的金融场景变简单。Mand Mobile含有丰富的组件30+的基础组件,覆盖金融场景,极高的易用性组件均有详细说明文档、案例演示,汲取最前沿技术,组件化轻量化实现,兼顾稳定和品质,努力实现金融场景的全覆盖。
Mand Mobile官网:https://didi.github.io/mand-mobile/#/zh-CN/home
下面是1.x的文档和演示地址:(文档地址已经迁移了)
https://mand-mobile.github.io/1x-doc/
https://mand-mobile.github.io/2x-doc/
之前的地址打不开了
13、we-vue UI组件库
we-vue 是一套基于 Vue.js 的移动关组件库,结合 weui.css 样式库,封装了一系列组件,非常适于微信公众号等移动端开发。we-vue 包含35+ 个组件,单元测试覆盖率超 98%,支持 babel-plugin-import,完善的在线文档,详细的在线示例。
we-vue官网:https://wevue.org/
14、veui UI组件库
veui是一个由百度EFE team开发的Vue企业级UI组件库。目前文档还没有,只有demo。
GitHub上说是正在进行的一项工作。那我们就耐心等待吧。
veui官网:https://ecomfe.github.io/veui/components/#/
15、Semantic-UI-Vue UI组件库
Semantic-UI-Vue是基于 Vue.js对Semantic-UI 框架的实现。
Semantic作为一款开发框架,帮助开发者使用对人类友好的HTML语言构建优雅的响应式布局。Semantic-UI-Vue提供了一个类似于 Semantic-UI 的 API 以及一组可定制的主题。
Semantic-UI-Vue官网:https://semantic-ui-vue.github.io/#/
在选择框架的时候一定要根据实际Web开发情况和团队的熟悉程度来选择。一个好的UI组件库对一个Web项目来说很重要
16.Vue.js Material Component Framework — Vuetify.js
Vuetify完全根据Material Design规范开发。每个组件都是手工制作的,为您的下一个伟大的应用程序带来最好的UI工具。开发并没有停留在Google规范中的核心组件上。通过社区成员和赞助商的支持,更多的组件将被设计并提供给大家享受。
这个主要是国外比较流行的vue ui组件,各种功能都有,有中文翻译的,但有些还是英文的,翻译得不是很好
PS:国内的UI组件大部分都只有一部分的,常用的头部导航,底部导航,listview,grid表格很多都是没有的。
参考链接
Javascript:如何循环遍历页面上的所有DOM元素?
传递一个*
到getElementsByTagName()
,以便它将返回页面中的所有元素:
1 2 3 4 5 6 7 8 9 10 |
var all = document.getElementsByTagName("*"); for (var i=0, max=all.length; i < max; i++) { // Do something with the element here var node = all[i]; /*以获取所有label标签的内容为例*/ if(node.tagName && (node.tagName.toLowerCase() == "label")){ console.log("innerText:" + node.innerText); } } |
参考链接
原生JS写Ajax的请求函数
一般我们写网页的时候,如果用到Ajax
请求服务器,都是使用JQuery
等已经封装好的库来调用,比较简单。
但是一般这些库的功能很多,引入了太多我们用不到的东西,如果我们需要写一个功能单一,简单的页面,完全用不到引用如此庞大的库文件。
我们可以简单实现一个自己的Ajax
请求功能,具体的代码如下:
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 |
var ajax = {}; ajax.x = function () { if (typeof XMLHttpRequest !== 'undefined') { return new XMLHttpRequest(); } var versions = [ "MSXML2.XmlHttp.6.0", "MSXML2.XmlHttp.5.0", "MSXML2.XmlHttp.4.0", "MSXML2.XmlHttp.3.0", "MSXML2.XmlHttp.2.0", "Microsoft.XmlHttp" ]; var xhr; for (var i = 0; i < versions.length; i++) { try { xhr = new ActiveXObject(versions[i]); break; } catch (e) { } } return xhr; }; ajax.send = function (url, method, data, success,fail,async) { if (async === undefined) { async = true; } var x = ajax.x(); x.open(method, url, async); x.onreadystatechange = function () { if (x.readyState == 4) { var status = x.status; if (status >= 200 && status < 300) { success && success(x.responseText,x.responseXML) } else { fail && fail(status); } } }; if (method == 'POST') { x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); } x.send(data) }; ajax.get = function (url, data, callback, fail, async) { var query = []; for (var key in data) { query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); } ajax.send(url + (query.length ? '?' + query.join('&') : ''), 'GET', null, callback, fail, async) }; ajax.post = function (url, data, callback, fail, async) { var query = []; for (var key in data) { query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); } ajax.send(url,'POST', query.join('&'), callback, fail, async) }; |
使用方法:GET
1 2 3 4 5 6 |
ajax.get('/test.php', {foo: 'bar'}, function(response,xml) { //success }, function(status){ //fail }); |
POST
1 2 3 4 5 6 7 |
ajax.post('/test.php', {foo: 'bar'}, function(response,xml) { //succcess },function(status){ //fail }); |
这里需要注意一个问题,如果我们想要发送类似
1 |
http://www.mobibrw.com/control?op_code=code&op_cmd=cmd |
的URL
,是不能通过上面的发送方式(字段填写在data
参数中)发送的,上面的发送方式是属于表单的上传方式。
我们需要用下面的方式进行处理(自己拼凑URL
)
1 2 3 4 5 6 7 |
var sendCmd = "?op_code=" + code + "&op_cmd=" + cmd; ajax.post('/control' + sendCmd,'',function(response,xml) { console.log('success'); }, function(status){ console.log('failed:' + status); }); |
参考链接
JavaScript修改伪元素样式(pseudo element styles)
类似如下的CSS
声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#progress { background: #333; border-radius: 13px; height: 10px; width: 300px; padding: 3px; } #progress:after { content: ''; display: block; background: orange; width: 0%; height: 100%; border-radius: 9px; } |
HTML中的声明如下:
1 |
<div id="progress" style="text-align: center; margin-left: auto; margin-right: auto;"></div> |
需要动态修改CSS
的width
属性。
由于是伪元素样式,并不属于DOM
对象,因此,我们没有办法直接通过JQuery
来修改。
比较完美的解决方法如下:
定义如下函数,对样式表遍历,根据名称获取我们指定的样式对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function getRuleWithSelector(selector) { var numSheets = document.styleSheets.length; var numRules; var sheetIndex; var ruleIndex; // Search through the style sheets. for (sheetIndex = 0; sheetIndex < numSheets; sheetIndex += 1) { numRules = document.styleSheets[sheetIndex].cssRules.length; for (ruleIndex = 0; ruleIndex < numRules; ruleIndex += 1) { if (document.styleSheets[sheetIndex].cssRules[ruleIndex].selectorText === selector) { return document.styleSheets[sheetIndex].cssRules[ruleIndex]; } } } return null; } function isNull(arg) { return ((undefined == arg) || (null == arg) ||('' == arg)); } |
使用时候的代码如下:
1 2 3 4 |
var afterRule = getRuleWithSelector("#progress::after"); if (true != isNull(afterRule)){ afterRule.style.width = '20%'; } |
参考链接
d3总结
项目原因,需要使用web绘制图表,比较靠谱的有echart和d3。
echart:应该是百度的项目,首页做的很漂亮,文档也比较全。个人感觉是一个入手较易,主要在学习怎么配参数,地图是echart的一个亮点。缺点就明显了,入手容易的肯定高度封装过,很难根据自己的需求定制化。而且使用了cavas绘图,在移动端性能不如svg。个人感觉echart适合一些时间比较紧迫,设计没那么明确的场合,直接套上echart,就已经很漂亮了。
d3:大名鼎鼎,受欢迎程度超过jquery,首页的绚丽特效让人眼花,教程的话强烈推荐http://www.ourd3js.com/wordpress/,这个教程好像出了书,叫精通D3.js,基本上把教程撸一遍就没啥大问题了。
d3的优点:用过一段d3后,感觉d3就是svg的jquery,把svg的操作封装为更易用的接口,并提供各种数据可视化的接口。所以d3很灵活,因为你可以用d3在svg中添加一个点、线、饼等等,svg性能不错。链式写法。
缺点:入手稍微慢点,不过撸一遍教程也就一下午的时间。svg的接口网上文档比较少,有些用法stackoverflow都找不到。
如果是学习d3的,推荐前面的教程网站,这里只是个人备忘录,想到哪里说到哪里的流水账,记录那些让人眼前一亮或者眼前一黑的点,以及几个开发中反复查询的用法。
好了,出发
1.
1 2 3 4 |
var svg = d3.select("body") .append("svg") .attr("width", svgWidth) .attr("height", svgHeight); |
首先,你要在一个dom中添加svg,需要两个参数,width和height,一般我是这么给的
1 |
this.svgContent = document.getElementById("id"); |
1 2 |
this.svgWidth = this.svgContent.clientWidth; this.svgHeight = this.svgContent.clientHeight; |
踩坑:需求在指定操作后才绘制该图标,dom display:none的时候,clientWidth和clientHeight是0,所以svg等dom display正常后再添加。
接下来最好设置一下svg的padding,用来应付后面出现的各种文字绘制超出不显示,UI修改等灵细操作。
1 |
this.padding = {left: Ruler.size_1920(200), right: Ruler.size_1920(220), top: Ruler.size_1920(66), bottom: Ruler.size_1920(28)}; |
因为svg直接传的都是px,所以需要根绝屏幕做一下自适应,size_1920函数如下:
1 2 |
let deviceWidth = document.documentElement.clientWidth; return (px * deviceWidth) / 1920; |
坐标轴:
图标中用到最多的是坐标轴,使用坐标轴首先要设定比例尺,目前共用到了4种比例尺
线性比例尺
1 2 3 |
this.yScale = d3.scale.linear() .domain([0, this.threshold]) .range([this.svgHeight - this.padding.top - this.padding.bottom, 0]); |
domain中设置数据的min/max,range中设置svg实际位置。
备注:svg是以左上角为原点的,而常规视角中原点是左下角,所以这里的range起止位置是 svgHeight-0,即起始位置是下边缘y=svgHeight,终止位置是上边缘y=0。这一点需要谨记,后面有许多计算x、y位置的,y的位置总要反着想。其他比例尺也是一样的。
离散比例尺
1 2 3 |
this.xScale = d3.scale.ordinal() .domain(d3.range(data.length)) .rangeRoundBands([0, this.svgWidth - this.padding.left - this.padding.right]); |
or
1 2 3 |
this.xScale = d3.scale.ordinal() .domain(["1","2","3","4"]) .rangeRoundBands([0, this.svgWidth - this.padding.left - this.padding.right]); |
时间比例尺
时间比例尺适用于一些以时间为坐标轴的情况
1 2 3 4 5 6 7 8 9 |
this.now = new Date(); let nextDay = new Date(this.now.getTime() + 1000 * 3600 * 24); // 这里要搞成UTC时间 this.nowUTC = this.now.getUTCFullYear() + "-" + (this.now.getUTCMonth() + 1) + "-" + (this.now.getUTCDate() < 10 ? "0" : "") + this.now.getUTCDate() + "T00:00"; this.nextUTC = nextDay.getUTCFullYear() + "-" + (nextDay.getUTCMonth() + 1) + "-" + (nextDay.getUTCDate() < 10 ? "0" : "") + nextDay.getUTCDate() + "T00:00"; this.xScale = d3.time.scale() .domain([new Date(this.nowUTC), new Date(this.nextUTC)]) .range([0, this.svgWidth - this.padding.left - this.padding.right]); |
备注:比如这里是今天到明天,第二天nextDay的算法,就用秒,其他都不行。UTC时间一定要注意,所有时间都改成UTC时间,所有get一律要加UTC。
颜色比例尺
1 2 |
// 颜色比例尺 var color = d3.scale.category10() // 20 30 都有 |
这是个比较特殊的比例尺,不是用来画坐标轴的,而是把颜色均匀分割,category10,category20等等都有,填一个index,输出一个色值。在有些d3的版本中,颜色比例尺没用放在scale里面。
坐标轴
可能还有更多种类的比例尺,目前还没有接触,下面就开始生成坐标轴数据。
1 2 3 4 5 6 7 8 9 10 |
let gridXAxis = d3.svg.axis() .scale(this.xScale) .orient("top") .innerTickSize(this.svgHeight - this.padding.top - this.padding.bottom) .outerTickSize(0) .ticks(d3.time.hour, 1) .tickFormat(function (d, i) { return ""; }) .tickPadding(10); |
几个常用参数
scale,必要参数,把比例尺填进去
orient,非必填,刻度相对坐标轴的位置 top bottom left right
ticks,非必填,刻度个数 注:ticks只是参考,最终生成刻度个数以数据为准,比如数据需要25个刻度,但ticks填了17,那d3就没办法了,会生成一个d3生成的刻度个数中选跟17最接近的。
innerTickSize,非必填,内刻度高,默认是6。 注:这里设置了innerTickSize是整个svg的高度,和y轴innerTickSize设置整个svg的宽配合行程网格效果,正常的坐标轴就再画一个,注意高度计算时错开
1 2 3 4 5 6 7 |
let xAxis = d3.svg.axis() .scale(this.xScale) .orient("bottom") .ticks(d3.time.hour, 1) .outerTickSize(0) .tickFormat(function (d, i) { }); |
outerTickSize,非必填,外刻度高
tickFormat,非必填,刻度值,默认为空 注:这是一个数据绑定参数,即根据数据生成不同的值
tickPadding,非必填,刻度文字与坐标轴的间距
真正的绘制
1 2 3 4 5 6 7 8 |
this.svg.append("g") .attr("class", "x axis") .attr("transform", "translate(" + this.padding.left + "," + (this.svgHeight - this.padding.top + this.xPadding + this.doubleLinePadding) + ")") .call(xAxis); this.svg.append("g") .attr("class", "x axis grid") .attr("transform", "translate(" + this.padding.left + "," + (this.svgHeight - this.padding.top + this.xPadding) + ")") .call(gridXAxis); |
给svg中添加g元素,call绑定坐标轴数据,attr就是给g元素添加各种属性,class(是不是很熟悉),transform。 注:svg中没有top left margin等属性,大的位移使用transform。
曲线
坐标轴绘制完后,就是曲线的绘制,先生成曲线绘制函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let _line = d3.svg.line() .x(function (d) { let x = self.xScale(d.time); if (x < 0) { return 0; } return self.xScale(d.time); }) .y(function (d) { let x = self.xScale(d.time); if (x < 0) { return 0; } return self.yScale(d.uv); }) // .interpolate("cardinal"); .interpolate("monotone"); |
曲线绘制函数参数
x、y就是根据数据来计算坐标,这里是比例尺大显身手的地方。
interpolate,线段怎么弯曲,讲的最好的是这个地址
http://www.oxxostudio.tw/articles/201411/svg-d3-02-line.html
注:basis曲线保证了优美,没保证曲线与数据的一致,像我这种人是完全无法接受的。
绘制曲线
1 |
let paths = this.svg.selectAll(".data-line").data(data); |
这一行代码解读一下就是选择所有class是data-line的元素,然后给它绑定数据。data-line哪里来的?这里涉及到d3中一个概念,update enter exit
selectAll得到的元素跟数据相比,有三种可能,元素个数 大于/小于/等于数据个数。
数据超出元素个数的部分叫enter,比如第一次绘制时,所有元素都属于paths.enter
1 2 3 4 5 6 7 8 9 10 11 |
paths.enter() .append("path") .attr("class", "data-line gaussian-shadow") .attr("d", function (d) { return _line(d.data); }) .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")") .style("stroke", function (d, i) { return d3.scale.category10(i); }) .attr("fill", "none"); |
paths元素比较简单直接,基本上只需要给一个属性d就能画出来了,d可以用前面的曲线函数直接生成。
stroke,线条颜色,这里可以用颜色比例尺 d3.scale.category10(i)
fill就是填充颜色,paths填充的是一个面。
不超出的部分叫update,即需要更新的部分,paths.update可以看到,也可以直接更新数据,这里是数据驱动的最直观表现
1 2 3 4 5 |
paths.attr("d", function (d) { if (d && d.data) { return _line(d.data); } }); |
数据少于元素个数的部分叫exit,一般exit只有一个用法
1 2 |
// 去掉多余的曲线 paths.exit().remove(); |
到这里,一个坐标轴差不多出来了,但,是不是少了点啥,对,UED最喜欢的灵魂一击,动画。
曲线进入/更新的时候要动画怎么办
d3的动画很好写
1 2 3 4 5 6 7 8 |
paths.transition() .duration(2000) .ease("sin") .attr("d", function (d) { if (d && d.data) { return _line(d.data); } }); |
增加 transition ease 和 delay即可,transition前后是动画的起始和结束状态。
绘制一个圆
1 2 |
let circles = this.svg.selectAll("circle") .data(this.axisData); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
circles.attr("cx", this.xScale(this.now)) .attr("cy", function (d) { if (d && d.data) { return this.yScale(d.data) } }) } circles.exit().remove(); circles.enter() .append("circle") .attr("fill", function (d) { return d.color; }) .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")") // .attr("fill-opacity", "0.4") .attr("cx", this.xScale(this.now)) .attr("cy", function (d) { if (d && d.data) { return this.yScale(d.data) } }) .attr("r", 10); |
矩形
1 2 3 4 5 6 |
let rectContainers = this.svg.selectAll("item-title") .data(data) .enter() .append("g") .attr("class", "item-title") .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")"); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
rectContainers.append("rect") .attr("fill", function (d) { return d.color; }) .attr("x", this.xScale(this.now) + 30) .attr("y", function (d) { if (d && d.data) { return yScale(d.data); } }) .attr("width", function (d) { return 20; }) .attr("height", rectHeight) .attr("rx", rectRadius) .attr("ry", rectRadius) .attr("fill-opacity", 0.8); |
半圆角矩形是用path生成的,顶部圆角矩形
1 2 3 4 5 6 7 8 9 |
var topRoundedRect = function(x, y, width, height, radius) { return "M" + x + "," + y + "v" + ( radius - height) + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + -radius + "h" + (width - 2 * radius) + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius + "v" + (height - radius) + "z"; }; |
1 2 3 4 5 6 7 8 9 |
var rightRoundedRect = function(x, y, width, height, radius) { return "M" + x + "," + y + "h" + (width - radius) + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius + "v" + (height - 2 * radius) + "a" + radius + "," + radius + " 0 0 1 " + -radius + "," + radius + "h" + (radius - width) + "z"; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
this.svg.selectAll(".data-rect") .data(data) .enter() .append("path") .attr("class", "data-rect") .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")") .attr("fill", function(d, i) { return this.colors[i]; }.bind(this)) .attr("d", function (d, i) { let x = this.xScale(i) + this.rectPadding/2; let y = this.yScale(this.yScale.domain()[0]); let radius = 0; let width = this.xScale.rangeBand() - this.rectPadding; if (d.albm_cnt != 0) { radius = this.rectRadius; } let height = this.yScale(this.yScale.domain()[0]) - (this.yScale(d.albm_cnt)) + this.extraHeight; return topRoundedRect(x, y, width, height, radius); }.bind(this)); |
topRoundRect和rightRoundRect乍一看完全搞不懂,这里讲的比较详细https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths,仔细观察下,就是
M 起始x y坐标
h 纵移
v 横移
a 比较复杂 前两个参数是圆角的x y radius
中间三个参数 001是三个flag,只能是0/1 圆角矩形都是001
第一个代表弧线角度是否要大于180
第二个代表正角度还是负角度
第三个代表正方向还是反方向
z是合拢
最后两个参数是终止的x y位置
当前了,还有更多的L C S等等,这里不研究了
文字
1 2 3 4 5 6 7 8 9 10 11 12 |
rectContainers.append("text") .attr("class", "item-text") .attr("fill", "white") .attr("x", this.xScale(this.now) + 30 + 14) .attr("y", function (d) { if (d && d.dataSet) { return self.yScale(AxisView.getPvByTime(d.dataSet, self.now)) + 6; } }) .text(function (d) { return d.type; }); |
到这里,d3绘制的套路差不多都熟悉了,append data enter transform attr,拿来画个饼图吧!
生成饼图数据
1 2 |
let pie = d3.layout.pie().sort(null); this.pieData = pie(percentArray); |
查看一下this.pieData,你会发现数据增加了 startEngle 和 endEngle
注:饼图默认会按大小排序,如果不需要排序,增加一个sort(null)即可
弧线函数
1 2 3 |
this.arc = d3.svg.arc() .innerRadius(0) .outerRadius(self.innerRadius); |
1 2 3 4 5 6 |
let arcs = this.svg.selectAll(".time-pie") .data(this.pieData) .enter() .append("g") .attr("class", "time-pie") .attr("transform", "translate(" + this.centerX + "," + this.centerY + ")"); |
1 2 3 4 5 6 7 |
arcs.append("path") .attr("fill", function (d, i) { return this.colors[i]; }.bind(this)) .attr('d', function(d) { return this.arc(d); }.bind(this)); |
饼图动画不能使用d了,要使用attrTween
简单的饼图动画
1 2 3 4 5 6 7 8 9 10 11 12 |
arcs.append("path") .attr("fill", "#fff") .attr("fill-opacity", 0.2) .transition() .duration(this.fadeInTime) .attrTween('d', function(d) { var i = d3.interpolate(d.startAngle+0.1, d.endAngle); return function(t) { d.endAngle = i(t); return arc(d); }; }.bind(this)); |
高级饼图动画,自己计算delay的时间,让饼一点一点出来
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 |
arcs.append("path") .attr("fill", function (d, i) { return this.colors[i]; }.bind(this)) .transition() .delay(function (d, i) { if (data) { let _percent = 0; for (let j = 0; j < i; j++) { try { _percent += parseInt(data[j].percent) } catch (e) { _percent += 100 / data.length; } } return this.pieAnimTime * _percent / 100; } }.bind(this)) .duration(function (d) { let _time = this.pieAnimTime / data.length; if (d && d.data) { let percent = parseInt(d.data); _time = this.pieAnimTime * percent / 100; } this.lastAnimTime+=_time; return _time; }.bind(this)) .ease("linear") .attrTween('d', function(d) { var i = d3.interpolate(d.startAngle+0.1, d.endAngle); return function(t) { d.endAngle = i(t); return this.arc(d); }.bind(this); }.bind(this)); |
往饼图中间添加一个圆点
1 2 3 4 5 6 7 8 |
this.centriodData = Array.apply([0,0], this.pieData).map(function (val, index) { // return this.arc.centroid(val); return { right: (val.startAngle+val.endAngle)/2<Math.PI, centroidPos: this.arc.centroid(val) }; return this.arc.centroid(val); }.bind(this)); |
注:centriodData和Angle是钝/锐角的计算提出来,是因为在某些低配机子上,偶现绑定计算错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
this.svg.selectAll(".time-circle") .data(this.pieData) .enter().append("circle") .attr("class", "time-circle") .attr("transform", "translate(" + this.centerX + "," + this.centerY + ")") .attr("cx", function(d, i) { // var pos= this.arc.centroid(d); var pos= this.centriodData[i] && this.centriodData[i].centroidPos; return pos && pos[0] * 1.65; }.bind(this)) .attr("cy", function(d, i) { // var pos= this.arc.centroid(d); var pos= this.centriodData[i] && this.centriodData[i].centroidPos; return pos && pos[1] * 1.65; }.bind(this)) .attr("fill", "#fff") // .attr("fill-opacity", 0.1) // .transition() // .duration(this.fadeInTime) .attr("fill-opacity", 1) .attr("r", Ruler.size_1920(6)); |
画一个折线
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 |
let line = this.svg.selectAll(".line") //添加文字和弧之间的连线 .data(this.pieData) //返回是pie(data0) .enter().append("g") .attr("class", "line") .attr("transform", "translate(" + this.centerX + "," + this.centerY + ")") .append("polyline") .attr('points', function(d, i) { // var pos1= this.arc.centroid(d),pos2= this.arc.centroid(d),pos3= this.arc.centroid(d); var pos1=[this.centriodData[i].centroidPos[0], this.centriodData[i].centroidPos[1]] ,pos2= [this.centriodData[i].centroidPos[0], this.centriodData[i].centroidPos[1]],pos3= [this.centriodData[i].centroidPos[0], this.centriodData[i].centroidPos[1]]; pos1[0]*=1.65,pos1[1]*=1.65; pos2[0]*=2,pos2[1]= pos2[1] *2; pos3[0]=(this.centriodData[i].right?this.lineLen:-this.lineLen); pos3[1]= pos3[1]*2; //pos1表示圆弧的中心往上,pos2是圆弧边,pos3就是将pos2平移后得到的位置 //三点链接在一起就成了线段。 return [pos1,pos2,pos3]; }.bind(this)) .style('fill', 'none') .style('stroke', "#fff") .style('stroke-opacity', 0.6) .style('stroke-width', Ruler.size_1920(3) + "px"); // 这里是动画,直线慢慢生成的动画 // .attr("stroke-dasharray", totalLen + " " + totalLen) // .attr("stroke-dashoffset", totalLen) // .transition() // .duration(this.lineAnimTime) // .ease("linear") // .attr("stroke-dashoffset", 0); |
到这里,一般的坐标轴、饼图已经难不倒了,而且自己想添加什么就添加什么,无非就是计算x、坐标,d3就是一堆图形函数,帮你计算坐标而已,那还有什么新东西吗?假如UED说想要阴影,光效,这里介绍一下svg里面一个很牛叉的东西,filter
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 |
let defs = svg.append("defs"); let filter = defs.append("filter") .attr("id", "gaussianShadowFilter"); // append gaussian blur to filter // filter.append("feMorphology") // .attr("operator", "dilate") // .attr("radius", 3); filter.append( 'feGaussianBlur' ) .attr( 'in', 'SourceAlpha' ) .attr( 'stdDeviation', 2 ) // !!! important parameter - blur .attr( 'result', 'blur' ); // append offset filter to result of gaussion blur filter filter.append( 'feOffset' ) .attr( 'in', 'blur' ) .attr( 'dx', 10 ) // !!! important parameter - x-offset .attr( 'dy', 1 ) // !!! important parameter - y-offset .attr( 'result', 'offsetBlur' ); // merge result with original image let feMerge = filter.append( 'feMerge' ); // first layer result of blur and offset feMerge.append( 'feMergeNode' ) .attr( 'in", "offsetBlur' ); // original image on top feMerge.append( 'feMergeNode' ) .attr( 'in', 'SourceGraphic' ); |
实话说,这个东西我也只是到抄过来调参的地步,比如dx dy。理解上只知道特效输出为result,下一个特效的in是这个result,其他也是一问三不知,而且看了之后着实没有深入了解下去的想法,简直就是手写Photoshop。
下面是几个介绍filter的地址,留下以备不时之需
1.https://www.w3.org/TR/SVG/filters.html
2.https://www.smashingmagazine.com/2015/05/why-the-svg-filter-is-awesome/
3.https://jorgeatgu.github.io/svg-filters/
被优化为无排版和缩进的JavaScript代码,如何调试?
有时候网页上的JavaScript
代码被优化器优化过后,会丢失原来的换行,导致整个的代码蜷缩成一行,完全没办法调试,此时我们就需要借助Chrome
或者FireFox
自带的代码调整功能来实现代码的调试了,如下图:
参考链接
d3['default'] is undefined
使用Babel引入d3,即import d3 from 'd3',使用时出现undefined的错误,调试后发现
var _d3 = __webpack_require__(164);
var _d32 = _interopRequireDefault(_d3);
var fill = _d32['default'].schemeCategory20;
_d3有schemeCategory20,d32['default']是undefined,
_interopRequireDefault是什么鬼?
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
就是根据__esModule 的一个if else,__esModule又是什么?
这里有一篇比较具体的解释 http://ryerh.com/javascript/2016/03/27/babel-module-implementation.html
简单来说,default就是我们在class后面写的 export default xxx
翻一翻源码,d3的default是true,但没有export default
1 |
Object.defineProperty(exports, '__esModule', { value: true }); |
相关链接:__esModule true in build/d3.js?
Improve docs: ES6 usage example
从这两个链接中,找到了d3的正确用法
import * as d3 from 'd3'
就是说,把d3的所有模块导出,给d3这个作用域
调试来看,就是
var _d3 = __webpack_require__(164);
var d3 = _interopRequireWildcard(_d3);
可以看到,用了import as后
从_interopRequireDefault转变为_interopRequireWildcard
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } |
也就是*替换了default
另外,必须再说一下,d3没有default,但d3的插件不一定没有
比如我使用的d3-cloud,还是要用import cloud from 'd3-cloud'
IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE,sockjs-1.1.1,stomp-1.2搭建基于Tomcat-7.0.68的WebSocket应用
接着上文IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE搭建基于Tomcat-7.0.68的WebSocket应用
上文的最后我们说到,WebSocket
是需要定时心跳的,否则会在一段时间后自动断开连接,而更重要的是,不是所有的浏览器都支持WebSocket
,早期的IE 10
之前的版本就是不支持的,而这一部分的设备其实是不算少的,而sockjs
的出现,恰恰好来解决了这个问题。对于不支持WebSocket
的浏览器,sockjs
使用了多种方式来兼容这种情况,包括使用长轮询等方式,Spring
更是内建支持这种方式。
下面我们看如何在上篇文章的基础上,增加对于sockjs
的支持。
首先是STOMP
的文档官网地址 http://stomp.github.io/
代码的地址为https://github.com/jmesnil/stomp-websocket,项目下面的lib/stomp.js
就是我们想要的文件。也可以本站下载stomp.js.zip
接下来sockjs
的代码地址https://github.com/sockjs/sockjs-client,项目下面的dist/sockjs-1.1.1.js
就是我们想要的文件。也可以本站下载sockjs-1.1.1.js.zip
接下来我们把下载到的文件放到我们工程目录下面的web
->resources
->javascript
目录下面,如下图:
接下来,添加我们需要的com.fasterxml.jackson.core:jackson-annotations:2.8.1
,com.fasterxml.jackson.core:jackson-core:2.8.1
,com.fasterxml.jackson.core:jackson-databind:2.8.1
这三个jar
包,增加的方式参照上一篇中对于javax.servlet:javax.servlet-api:3.1.0
的操作方法。与上一篇的操作不同的是,这次添加的三个jar
包,都要放到编译完成后的War
包中。最后的结果如下图:
下面,我们开始进行代码的操作,我们在上篇文章中的src
->Tools
->WebSocket
中新增两个源代码文件SockJsController.java
,WebJsSocketConfig.java
.如下图:
其中的代码如下:
SockJsController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package Tools.WebSocket; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; @Controller public class SockJsController { @MessageMapping("/hello") @SendTo("/hello/subscribe") /*貌似这个名字可以随意的,主要用在stomp.subscribe时候的名字*/ public String Hello(String message) throws Exception { return new String("Hello"); } } |
WebJsSocketConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package Tools.WebSocket; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @Configuration @EnableWebSocketMessageBroker public class WebJsSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.setApplicationDestinationPrefixes("/webSocketServer"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/hello").setAllowedOrigins("*").withSockJS(); } } |
然后修改WebSocket.jsp
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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>WebSocket/SockJS Echo Sample (Adapted from Tomcat's echo sample)</title> <style type="text/css"> #connect-container { float: left; width: 400px } #connect-container div { padding: 5px; } #console-container { float: left; margin-left: 15px; width: 400px; } #console { border: 1px solid #CCCCCC; border-right-color: #999999; border-bottom-color: #999999; height: 170px; overflow-y: scroll; padding: 5px; width: 100%; } #console p { padding: 0; margin: 0; } </style> <script src="./resources/javascript/sockjs-1.1.1.js"></script> <script src="./resources/javascript/stomp.js"></script> <script type="text/javascript"> var ws = null; var url = null; var transports = []; var stompClient = null; function setConnected(connected) { document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('echo').disabled = !connected; } function connect() { if (!url) { alert('Select whether to use W3C WebSocket or SockJS'); return; } ws = (url.indexOf('sockjs') != -1) ? new SockJS(url,undefined, {transports: transports}) : new WebSocket(url); if((url.indexOf('sockjs') != -1)) { stompClient = Stomp.over(ws); stompClient.connect({}, function(frame) { setConnected(true); log('Connected: ' + frame); stompClient.subscribe('/hello/subscribe', function(message){ log(message.body); }); }); }else { ws.onopen = function () { setConnected(true); log('Info: connection opened.'); }; ws.onmessage = function (event) { log('Received: ' + event.data); }; ws.onclose = function (event) { setConnected(false); log('Info: connection closed.'); log(event); }; } } function disconnect() { if (ws != null) { ws.close(); ws = null; } stompClient = null; setConnected(false); } function echo() { if(stompClient != null){ var message = document.getElementById('message').value; stompClient.send("/webSocketServer/hello", {}, message); log('Sent: ' + message); }else { if (ws != null) { var message = document.getElementById('message').value; log('Sent: ' + message); ws.send(message); } else { alert('connection not established, please connect.'); } } } function updateUrl(urlPath) { if (urlPath.indexOf('sockjs') != -1) { url = urlPath; document.getElementById('sockJsTransportSelect').style.visibility = 'visible'; } else { if (window.location.protocol == 'http:') { url = 'ws://' + window.location.host + urlPath; } else { url = 'wss://' + window.location.host + urlPath; } document.getElementById('sockJsTransportSelect').style.visibility = 'hidden'; } } function updateTransport(transport) { transports = (transport == 'all') ? [] : [transport]; } function log(message) { var console = document.getElementById('console'); var p = document.createElement('p'); p.style.wordWrap = 'break-word'; p.appendChild(document.createTextNode(message)); console.appendChild(p); while (console.childNodes.length > 25) { console.removeChild(console.firstChild); } console.scrollTop = console.scrollHeight; } </script> </head> <body> <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable Javascript and reload this page!</h2></noscript> <div> <div id="connect-container"> <input id="radio1" type="radio" name="group1" onclick="updateUrl('/webSocketServer');"> <label for="radio1">W3C WebSocket</label> <br> <input id="radio2" type="radio" name="group1" onclick="updateUrl('/webSocketServer/sockjs/hello');"> <label for="radio2">SockJS</label> <div id="sockJsTransportSelect" style="visibility:hidden;"> <span>SockJS transport:</span> <select onchange="updateTransport(this.value)"> <option value="all">all</option> <option value="websocket">websocket</option> <option value="xhr-polling">xhr-polling</option> <option value="jsonp-polling">jsonp-polling</option> <option value="xhr-streaming">xhr-streaming</option> <option value="iframe-eventsource">iframe-eventsource</option> <option value="iframe-htmlfile">iframe-htmlfile</option> </select> </div> <div> <button id="connect" onclick="connect();">Connect</button> <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button> </div> <div> <textarea id="message" style="width: 350px">Here is a message!</textarea> </div> <div> <button id="echo" onclick="echo();" disabled="disabled">Echo message</button> </div> </div> <div id="console-container"> <div id="console"></div> </div> </div> </body> </html> |
最后,我们修改web
->WEB-INF
->web.xml
,在其中增加
1 2 3 4 |
<servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/webSocketServer/sockjs/*</url-pattern> </servlet-mapping> |
修改后的最终结果如下:
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 |
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <filter> <filter-name>struts2</filter-name> <filter-class>Tools.Filter.StrutsPrepareAndExecuteFilterEx</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/webSocketServer/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/webSocketServer/sockjs/*</url-pattern> </servlet-mapping> </web-app> |
参考链接