前端很多项目中,都有文件下载的需求,特别是 JS 生成文件内容,然后让浏览器执行下载操作(例如在线图片编辑、在线代码编辑、iPresst 等)。
但受限于浏览器,很多情况下我们都只能给出个链接,让用户点击打开-》另存为。如下面这个链接:
<a href="file.js">file.js</a>
用户点击这个链接的时候,浏览器会打开并显示链接指向的文件内容,显然,这并没有实现我们的需求。
本篇介绍关于 Node.js 事件代理的一个解决方案。
项目实践中,有遇到使用 nodejs 解决实际问题场景。Node.js 以 Javascript 作为语言,解决问题时,采用了 EventProxy 做事件代理,避免事件嵌套回调。
EventProxy 做为一个组件,包含如下特点:
- 内部包含事件代理机制,能避免多重回调嵌套问题
- 符合 CMD,AMD 及 CommonJS 等其它的模块设计标准
- 包装友好的回调处理监听器,包含标准的 Node.js 错误处理方法
- 兼容多平台,能够被应用到 Node.js 和各种浏览器环境中
废话不说,直接正题吧。
最近项目有个需求要用 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 相容,使用一至四个字节为每个字符编码
本文 github 地址
本篇谈谈 Node.js 捕获异常的一些探索。
D3 全称 Data-Driven-Documents,这里说的不是暗黑 III,d3 是一款可视化 js 库,其主要用途是用 HTML 或者 SVG 生动地展现数据。
相信网站开发者大都接入过 ga 来分析各种数据,例如 pv 图。ga 的图都是基于 SVG 的,下面笔者就用 d3 来一步一步实现类似 ga 的 pv 线形图,并假设读者具有一定的 SVG 基础(没有?没关系,w3school 帮你快速上手)。
step1:引入 d3.js
到 github d3 下载最新版 d3,然后在 html 代码增加标签
1 2 |
<script src="path/to/d3.js"></script> <!--more--> |
step2:创建 SVG 容器
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 通过位移来定位。
step3:定位坐标轴
既然 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('次/天'); 加上坐标轴之后的效果图应该是这样 |
step4:画线
有了坐标轴之后我们可以加上图表的主体部分了,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)); |
此时的图应该是这样了
step5:打点
到这里其实基本的图形已经实现了,只用了应该不到 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 上移动和移出的时候增加了圆半径变化的一个动画。
效果图
step6:增加 tips
现在看整体数据倒是可以了,不过看某天的具体数据还是太不方便了,如果在 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.
兼容:IE7 8 9 10 chrome firefox
demo 地址
之所以说这是 CSS 的障眼法,是因为这种效果并不是使用 CSS 原生的属性进行实现的,并不仅仅是使用一张图片,然后通过特定属性使其翻转,因为我们知道 CSS 并不提供另图片翻转的接口。要实现这种效果,我们需要的是通过图片和外层 div 的 border 的配合,使图片看起来翻转了一定角度。
小米又做了一个成功的营销。在首页上放了 7 个三色的小米随身 WIFI 图片,每个图片是一个琴键,各代表一个音节,按照提示可以弹出《铃儿响叮当》。然后就出现了小米 WIFI 的预订页面。
这个页面估计又要在微博、微信中疯转了。小米的互联网营销技巧实在是无法望其项背。所以这里就谈谈技术实现吧。
序:设计对于我们技术人员来说也是一门不可或缺的知识,学习设计就像学习一门新的语言一样,它也有遵从自己的一套规则。知道越多关于设计的知识,你就越能理解设计师的工作和意义,和设计师的交流也会更加畅通无阻。下面主要围绕 3 个方面给大家介绍设计的基础知识:
1、What is design?
2、What to learn?
3、How to design?
总结起来就是:2W1H。
由于文章篇幅较长,所以分成 3 篇来介绍,分别是:
Web 设计基础知识(一)
Web 设计基础知识(二)
Web 设计基础知识(三)
/***************************接上文 《Web 设计基础知识 (二)》*****************************/
2) layOUT
1) Layout-布局四原则(principles)
1、contrast 对比
序:设计对于我们技术人员来说也是一门不可或缺的知识,学习设计就像学习一门新的语言一样,它也有遵从自己的一套规则。知道越多关于设计的知识,你就越能理解设计师的工作和意义,和设计师的交流也会更加畅通无阻。下面主要围绕 3 个方面给大家介绍设计的基础知识:
1、What is design?
2、What to learn?
3、How to design?
总结起来就是:2W1H。
由于文章篇幅较长,所以分成 3 篇来介绍,分别是:
Web 设计基础知识(一)
Web 设计基础知识(二)
Web 设计基础知识(三)
/****************************接上文 《Web 设计基础知识 (一)》**************************/
2、What to learn?
在做好设计之前,首先你需要学习一些基本的知识:Colors(颜色) + layOut(布局)
1) Color
序:设计对于我们技术人员来说也是一门不可或缺的知识,学习设计就像学习一门新的语言一样,它也有遵从自己的一套规则。知道越多关于设计的知识,你就越能理解设计师的工作和意义,和设计师的交流也会更加畅通无阻。下面主要围绕 3 个方面给大家介绍设计的基础知识:
1、What is design?
2、What to learn?
3、How to design?
总结起来就是:2W1H。
由于文章篇幅较长,所以分成 3 篇来介绍,分别是:
Web 设计基础知识(一)
Web 设计基础知识(二)
Web 设计基础知识(三)
/************************** 文章华丽开场*******************************/