【视频】modern workflows for modern webapps 之 grunt 入门
In 未分类 on 2013年12月22日 by TAT.woshayawo view: 9,325
13
前端很多项目中,都有文件下载的需求,特别是 JS 生成文件内容,然后让浏览器执行下载操作(例如在线图片编辑、在线代码编辑、iPresst 等)。
但受限于浏览器,很多情况下我们都只能给出个链接,让用户点击打开-》另存为。如下面这个链接:
<a href="file.js">file.js</a>
用户点击这个链接的时候,浏览器会打开并显示链接指向的文件内容,显然,这并没有实现我们的需求。
废话不说,直接正题吧。
最近项目有个需求要用 js 计算一串字符串写入到 localStorage 里所占的内存,众所周知的,js 是使用 Unicode 编码的。而 Unicode 的实现有 N 种,其中用的最多的就是 UTF-8 和 UTF-16。因此本文只对这两种编码进行讨论。
下面这个定义摘自维基百科(http://zh.wikipedia.org/zh-cn/UTF-8),做了部分删减。
UTF-8(8-bit Unicode Transformation Format)是一种针对 Unicode 的可变长度字符编码,可以表示 Unicode 标准中的任何字符,且其编码中的第一个字节仍与 ASCII 相容,使用一至四个字节为每个字符编码
D3 全称 Data-Driven-Documents,这里说的不是暗黑 III,d3 是一款可视化 js 库,其主要用途是用 HTML 或者 SVG 生动地展现数据。
相信网站开发者大都接入过 ga 来分析各种数据,例如 pv 图。ga 的图都是基于 SVG 的,下面笔者就用 d3 来一步一步实现类似 ga 的 pv 线形图,并假设读者具有一定的 SVG 基础(没有?没关系,w3school 帮你快速上手)。
到 github d3 下载最新版 d3,然后在 html 代码增加标签
1 2 |
<script src="path/to/d3.js"></script> <!--more--> |
1 2 3 4 5 6 7 8 9 10 11 12 |
var margin = {top: 20, right: 20, bottom: 30, left: 50}, width = document.body.clientWidth - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var container = d3.select('body') .append('svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom); var svg = container.append('g') .attr('class', 'content') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); |
margin、width、height 定义了 svg 节点的位置和尺寸,后面会用到。d3.select 类似 jquery 的选择器,并且 d3 的语法也支持串联调用,append('svg') 将 svg 追加到 body 的尾部,同时为 svg 节点设置了宽度和高度值,attr 也有 get 和 set 两种用法。
svg 的 g 元素类似于 div,在这里作为一组元素的容器,后面加入的元素都放在 g 里面,g 可以设置统一的 css,里面的子元素会继承可继承 css 属性。margin 和 position 对 g 的定位不起作用,只能使用 translate 通过位移来定位。
既然 d3 是数据驱动的,那必须要有数据啊,没有数据肿么能搞呢。好吧,首先模拟一份数据,就模拟本月的 pv 数据吧,即 12 月每天的 pv 数据,日期采用 yy-mm-dd 的格式,pv 随机一个 100 以内的整数。
1 2 3 4 5 |
var data = Array.apply(0, Array(31)).map(function(item, i) { // 产生31条数据 i++; return {date: '2013-12-' + (i < 10 ? '0' + i : i), pv: parseInt(Math.random() * 100)} }); |
然后定义坐标轴的一些参数
1 2 3 4 5 6 7 |
var x = d3.time.scale() .domain(d3.extent(data, function(d) { return d.day; })) .range([0, width]); var y = d3.scale.linear() .domain([0, d3.max(data, function(d) { return d.value; })]) .range([height, 0]); |
横坐标是日期,这里使用 d3.time 自动帮我们在时间和字符串之间做转换。y 轴使用普通的线性缩放坐标轴。其实这里的 x 和 y 也是一个 function,后续会用到。
domain 规定了坐标轴上值的范围,d3.extent 从数组里选出最小值和最大值,d3.max 选数组里面最大值。range 规定了坐标轴端点的位置,svg 的坐标原点是左上角,向右为正,向下为正,而 y 轴正方向为由下向上,所以 (0, height) 才是图表的坐标原点。
然后使用 d3 的 axis 定制坐标轴
1 2 3 4 5 6 7 8 9 |
var xAxis = d3.svg.axis() .scale(x) .orient('bottom') .ticks(30); var yAxis = d3.svg.axis() .scale(y) .orient('left') .ticks(10); |
orient 有四个参数(left、right、top、bottom)定义了坐标轴的位置,这里很好理解。
ticks 定义了坐标轴上除最小值和最大值以外最多有多少个刻度,因为一个月最多有 31 天,ticks(30) 就足以展示每天的刻度了。
然后就可以把坐标轴加进 svg 容器了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 横坐标 svg.append('g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + height + ')') .call(xAxis) // 增加坐标值说明 .append('text') .text('日期') .attr('transform', 'translate(' + width + ', 0)'); // 纵坐标 svg.append('g') .attr('class', 'y axis') .call(yAxis) .append('text') .text('次/天'); 加上坐标轴之后的效果图应该是这样 |
有了坐标轴之后我们可以加上图表的主体部分了,pv 图应该是一条折线图。怎么加折线呢,d3 提供了丰富的图表元素,需要折线只需要 append('path') 即可,了解 svg 的都知道,path 的 d 属性是最重要的,决定了折线的 “路径”,这里就不详细讲解 path 了。
我们只有一个数组的数据,怎么转化成需要的 d 呢,别担心,d3 帮我们做了这部分工作。首先需要用 d3.svg.line 生成一个 “线条函数”,然后将数据传给该函数即可生成我们想要的 d,我们需要做的就是定制这个 “线条函数” 的两条坐标轴分别由数据的哪部分决定。
下面看代码
1 2 3 4 |
var line = d3.svg.line() .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.pv); }) .interpolate('monotone'); |
上面的代码很好理解,设置了 x 坐标轴由 date 属性决定,y 坐标轴由 pv 属性决定,最后还调用了 interpolate,该方法会改变线条相邻两点之间的链接方式以及是否闭合,接受的参数有 linear,step-before,step-after,basis,basis-open,basis-closed,bundle,cardinal,cardinal-open,cardinal-closed,monotone,读者可以一一尝试,看看线条有什么不一样。
“线条函数” 生成好了,可以应用到 path 上了
1 2 3 |
var path = svg.append('path') .attr('class', 'line') .attr('d', line(data)); |
此时的图应该是这样了
到这里其实基本的图形已经实现了,只用了应该不到 20 行代码,不过这也太丑了点吧,而且完全木有交互啊。
别急,ga 的 pv 图在每个数据点都会有一个小点来占位,其实本来我们的数据就是离散的,图上也应该是离散的一些点,不过为了图表好看,也为了方便查看数据的走势,折线图显然更形象一些。
下面就在折线上增加相应的点,点我们可以用 circle,要增加元素用 append 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var g = svg.selectAll('circle') .data(data) .enter() .append('g') .append('circle') .attr('class', 'linecircle') .attr('cx', line.x()) .attr('cy', line.y()) .attr('r', 3.5) .on('mouseover', function() { d3.select(this).transition().duration(500).attr('r', 5); }) .on('mouseout', function() { d3.select(this).transition().duration(500).attr('r', 3.5); }); |
这里的代码可能复杂一点,因为 circle 不止一个,需要使用 selectAll,而 circle 现在是还不存在的。selectAll('circle') 的作用可以理解成先预定若干个 circle 的位置,等有数据了再插入 svg 容器里。
enter 就表明有数据来了,将每个 circle 放到单独的 g 里面,这里没有特殊的用意,就像 html 里面习惯用 div 来装其他元素一样。
为 circle 设置一些属性,cx、cy 代表圆心 x、y 坐标,line.x() 和 line.y() 会返回折线上相应点的 x、y 坐标,这样添加的 circle 就依附在折线上了。r 表示圆半径,同时为 circle 添加了两个鼠标事件,这样鼠标在 circle 上移动和移出的时候增加了圆半径变化的一个动画。
效果图
现在看整体数据倒是可以了,不过看某天的具体数据还是太不方便了,如果在 circle 上直接标注出具体的数据又太挫了。
咋办?嘿嘿,参考 ga 呗。ga 在鼠标经过某点的纵坐标所在的直线的时候就会在改点附近出现具体的数据 tips,赞,既能清晰地看到整体的走势又能看到每天的具体数据。
先上效果图
图中用一个圆角矩形和两行文字组成了一个简单的 tips
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var tips = svg.append('g').attr('class', 'tips'); tips.append('rect') .attr('class', 'tips-border') .attr('width', 200) .attr('height', 50) .attr('rx', 10) .attr('ry', 10); var wording1 = tips.append('text') .attr('class', 'tips-text') .attr('x', 10) .attr('y', 20) .text(''); var wording2 = tips.append('text') .attr('class', 'tips-text') .attr('x', 10) .attr('y', 40) .text(''); |
为啥要用矩形呢,为啥不直接在 g 上设置圆角效果呢?实践证明对 g 设置的 width、height、border-radius 均无效,无赖只能使用 svg 的 rect 元素了。
rx、ry 是圆角两个方向的半径,原理同 border-radius。展示文字用 text 元素即可,这里的 x 和 y 还是坐标,不过是相对于父元素 g 的坐标。
最后的关键是怎么让 tips 出现在该出现的位置和展示对的数据,即鼠标经过某个点的纵坐标所在的直线是 tips 出现在改点附近,且展示改点的数据。
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 |
container .on('mousemove', function() { var m = d3.mouse(this), cx = m[0] - margin.left; var x0 = x.invert(cx); var i = (d3.bisector(function(d) { return d.date; }).left)(data, x0, 1); var d0 = data[i - 1], d1 = data[i] || {}, d = x0 - d0.date > d1.date - x0 ? d1 : d0; function formatWording(d) { return '日期:' + d3.time.format('%Y-%m-%d')(d.date); } wording1.text(formatWording(d)); wording2.text('PV:' + d.pv); var x1 = x(d.date), y1 = y(d.pv); // 处理超出边界的情况 var dx = x1 > width ? x1 - width + 200 : x1 + 200 > width ? 200 : 0; var dy = y1 > height ? y1 - height + 50 : y1 + 50 > height ? 50 : 0; x1 -= dx; y1 -= dy; d3.select('.tips') .attr('transform', 'translate(' + x1 + ',' + y1 + ')'); d3.select('.tips').style('display', 'block'); }) .on('mouseout', function() { d3.select('.tips').style('display', 'none'); }); |
这段长长的代码需要重点解释一下,首先是 d3.mouse(this),这个方法会返回当前鼠标的坐标,是一个数组,分别是 x 和 y 坐标。
下面这一步最重要的一点来了,x.invert(cx) 跟据传入的横坐标数值返回该横坐标的实际数据上的值,在本例中返回一个日期。
下面的 i 是根据返回的日期反向得到 data 数组中的元素位置。有了这个 i 一切都好办了,接下来的代码是为了判断鼠标在两个日期之间离哪个更近。
后面的代码都很简单了,拿到了 tips 应该出现的 x、y 坐标之后设置 tips 的 transform 即可,再控制 tips 的 display 属性就达到了最后的效果。
查看最后的代码请移步 http://jsfiddle.net/jarvisjiang/wh877/
That‘s all.
前端的发展越来越快, 项目越来越大,越来越复杂,如何简单高效的组织开发流程就显得尤为重要。
所以 alloyteam 决定推出介绍现代化 webapp 的开发流程的一系列视频教程,今天就为大家呈上第一部【grunt 入门】。
做为一个刚吃过肉的人,看过大海,思考过人生,决定还是要静下心做点总结。
最近在做 Web 工具,做为一个在 Web 中运行的工具,想要做强大的事情,在不依赖于 Server 端的支持、不依赖插件下,还要完成这些事情,有时候想想,确实是一件很难的事情。取决于
浏览器是服务端的代码运行于客户端,出于安全性的考虑,做了很多限制。浏览器是一个相对封闭的环境,说白了,能力有限。
mocha 是一个 javascript 的测试框架,chai 是一个断言库,两者搭配使用更佳,所以合称 “抹茶”(其实 mocha 是咖啡)。“抹茶” 特点是: 简单,Hour 级学习成本,node 和浏览器都可运行,有趣。
关于吹水,充字数的内容我放到了 blog 最后,因为 “抹茶” 上手非常的简单,看完 blog 再阅览一下官网就可以实际动手敲代码了,不足一个小时就可以基本掌握他们了。
一年前,我发过一篇关于跨文档通信方案的文章 《iframe 跨域通信的通用解决方案》,提供了一种基于创建 iframe 与轮询 window.name 的方案。
一年后,很高兴地带来彻底改造的新版本。实际上新方案已经用了很久了,一直没有时间抽象出来,最近终于挤时间分享出来了!~
最近在学习HTML5 和 CSS3,印象最深的是 CSS3 的动画功能,不仅有浏览器原生支持,执行效率高,而且免去在 js 中自己管理 timer。
本来想写一个图片轮播器练练手,结果在网上发现一个国人写的开源的图片轮播器。不仅效果很酷,而且是“女神级” 的。什么?你问是不是妹子写的?这个我不知道,我说的是这个 Demo 中的图片都是女神呀,性感火辣,丰满妩媚……咳咳,回正题。
Copyright © 2011-2021 AlloyTeam. All Rights Reserved. Powered By WordPress
粤ICP备15071938号-2