图片放大镜效果
原理
首先选择图片的一块区域,然后将这块区域放大,然后再绘制到原先的图片上,保证两块区域的中心点一致, 如下图所示:
初始化
1
2
3
4
|
< canvas id = "canvas" width = "500" height = "500" >
</ canvas >
< img src = "image.png" style = "display: none" id = "img" >
|
获得 canvas 和 image 对象,这里使用 <img> 标签预加载图片, 关于图片预加载可以看
1
2
3
|
var canvas = document.getElementById( "canvas" );
var context = canvas.getContext( "2d" );
var img = document.getElementById( "img" );
|
设置相关变量
1
2
3
4
5
6
7
8
9
10
|
var centerPoint = {};
var originalRadius = 100;
var originalRectangle = {};
var scale = 2;
var scaleGlassRectangle
|
画背景图片
1
2
3
|
function drawBackGround() {
context.drawImage(img, 0, 0);
}
|
计算图片被放大的区域的范围
这里我们使用鼠标的位置作为被放大区域的中心点(放大镜随着鼠标移动而移动),因为 canvas 在画图片的时候,需要知道左上角的坐标以及区域的宽高,所以这里我们计算区域的范围
1
2
3
4
5
6
|
function calOriginalRectangle(point) {
originalRectangle.x = point.x - originalRadius;
originalRectangle.y = point.y - originalRadius;
originalRectangle.width = originalRadius * 2;
originalRectangle.height = originalRadius * 2;
}
|
绘制放大镜区域
裁剪区域
放大镜一般是圆形的,这里我们使用 clip 函数裁剪出一个圆形区域,然后在该区域中绘制放大后的图。一旦裁减了某个区域,以后所有的绘图都会被限制的这个区域里,这里我们使用 save 和 restore 方法清除裁剪区域的影响。save 保存当前画布的一次状态,包含 canvas 的上下文属性,例如 style ,lineWidth 等,然后会将这个状态压入一个堆栈。restore 用来恢复上一次 save 的状态,从堆栈里弹出最顶层的状态。
1
2
3
4
5
6
|
context.save();
context.beginPath();
context.arc(centerPoint.x, centerPoint.y, originalRadius, 0, Math.PI * 2, false );
context.clip();
......
context.restore();
|
计算放大镜区域
通过中心点、被放大区域的宽高以及放大倍数,获得区域的左上角坐标以及区域的宽高。
1
2
3
4
5
6
|
scaleGlassRectangle = {
x: centerPoint.x - originalRectangle.width * scale / 2,
y: centerPoint.y - originalRectangle.height * scale / 2,
width: originalRectangle.width * scale,
height: originalRectangle.height * scale
}
|
绘制图片
在这里我们使用 context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); 方法,将 canvas 自身作为一副图片,然后取被放大区域的图像,将其绘制到放大镜区域里。
1
2
3
4
5
6
|
context.drawImage(canvas,
originalRectangle.x, originalRectangle.y,
originalRectangle.width, originalRectangle.height,
scaleGlassRectangle.x, scaleGlassRectangle.y,
scaleGlassRectangle.width, scaleGlassRectangle.height
);
|
绘制放大边缘
createRadialGradient 用来绘制渐变图像
1
2
3
4
5
6
7
8
9
10
11
12
13
|
context.beginPath();
var gradient = context.createRadialGradient(
centerPoint.x, centerPoint.y, originalRadius - 5,
centerPoint.x, centerPoint.y, originalRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0.2)' );
gradient.addColorStop(0.80, 'silver' );
gradient.addColorStop(0.90, 'silver' );
gradient.addColorStop(1.0, 'rgba(150,150,150,0.9)' );
context.strokeStyle = gradient;
context.lineWidth = 5;
context.arc(centerPoint.x, centerPoint.y, originalRadius, 0, Math.PI * 2, false );
context.stroke();
|
添加鼠标事件
为 canvas 添加鼠标移动事件
1
2
3
|
canvas.onmousemove = function (e) {
......
}
|
转换坐标
鼠标事件获得坐标一般为屏幕的或者 window 的坐标,我们需要将其装换为 canvas 的坐标。getBoundingClientRect 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
1
2
3
4
|
function windowToCanvas(x, y) {
var bbox = canvas.getBoundingClientRect();
return {x: x - bbox.left, y: y - bbox.top}
}
|
修改鼠标样式
我们可以通过 css 来修改鼠标样式
1
2
3
4
5
6
|
#canvas {
display : block ;
border : 1px solid red ;
margin : 0 auto ;
cursor : crosshair ;
}
|
图表放大镜
我们可能基于 canvas 绘制一些图表或者图像,如果两个元素的坐标离得比较近,就会给元素的选择带来一些影响,例如我们画两条线,一个线的坐标是(200.5, 400) -> (200.5, 200) ,另一个线的坐标为 (201.5, 400) -> (201.5, 20) ,那么这两条线几乎就会重叠在一起,如下图所示:
使用图表放大镜的效果
原理
类似于地图中的图例,放大镜使用较为精确的图例,如下图所示:
在放大镜坐标系统中,原始的区域会变大,如下图所示
绘制原始线段
首先创建一个线段对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function Line(xStart, yStart, xEnd, yEnd, index, color) {
this .xStart = xStart;
this .yStart = yStart;
this .xEnd = xEnd;
this .yEnd = yEnd;
this .index = index;
this .color = color;
}
|
初始化线段
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
|
var chartLines = new Array();
var glassLines;
var scaleGlassLines;
var glassLineSize;
function initLines() {
var line;
line = new Line(200.5, 400, 200.5, 200, 0, "#888" );
chartLines.push(line);
line = new Line(201.5, 400, 201.5, 20, 1, "#888" );
chartLines.push(line);
glassLineSize = chartLines.length;
glassLines = new Array(glassLineSize);
for ( var i = 0; i < glassLineSize; i++) {
line = new Line(0, 0, 0, 0, i);
glassLines[i] = line;
}
scaleGlassLines = new Array(glassLineSize);
for ( var i = 0; i < glassLineSize; i++) {
line = new Line(0, 0, 0, 0, i);
scaleGlassLines[i] = line;
}
}
|
绘制线段
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function drawLines() {
var line;
context.lineWidth = 1;
for ( var i = 0; i < chartLines.length; i++) {
line = chartLines[i];
context.beginPath();
context.strokeStyle = line.color;
context.moveTo(line.xStart, line.yStart);
context.lineTo(line.xEnd, line.yEnd);
context.stroke();
}
}
|
计算原始区域和放大镜区域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function calGlassRectangle(point) {
originalRectangle.x = point.x - originalRadius;
originalRectangle.y = point.y - originalRadius;
originalRectangle.width = originalRadius * 2;
originalRectangle.height = originalRadius * 2;
scaleGlassRectangle.width = originalRectangle.width * scale;
scaleGlassRectangle.height = originalRectangle.height * scale;
scaleGlassRectangle.x = originalRectangle.x + originalRectangle.width / 2 - scaleGlassRectangle.width / 2;
scaleGlassRectangle.y = originalRectangle.y + originalRectangle.height / 2 - scaleGlassRectangle.height / 2;
scaleGlassRectangle.width = parseInt(scaleGlassRectangle.width);
scaleGlassRectangle.height = parseInt(scaleGlassRectangle.height);
scaleGlassRectangle.x = parseInt(scaleGlassRectangle.x);
scaleGlassRectangle.y = parseInt(scaleGlassRectangle.y);
}
|
计算线段在新坐标系统的位置
由原理图我们知道,放大镜中使用坐标系的图例要比原始坐标系更加精确,比如原始坐标系使用 1:100 ,那么放大镜坐标系使用 1:10 ,因此我们需要重新计算线段在放大镜坐标系中的位置。同时为了简便,我们将线段的原始坐标进行了转化,减去原始区域起始的x值和y值,即将原始区域左上角的点看做为(0,0) 。
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
|
function calScaleLines() {
var xStart = originalRectangle.x;
var xEnd = originalRectangle.x + originalRectangle.width;
var yStart = originalRectangle.y;
var yEnd = originalRectangle.y + originalRectangle.height;
var line, gLine, sgLine;
var glassLineIndex = 0;
for ( var i = 0; i < chartLines.length; i++) {
line = chartLines[i];
if (line.xStart < xStart || line.xEnd > xEnd) {
continue ;
}
if (line.yEnd > yEnd || line.yStart < yStart) {
continue ;
}
gLine = glassLines[glassLineIndex];
sgLine = scaleGlassLines[glassLineIndex];
if (line.yEnd > yEnd) {
gLine.yEnd = yEnd;
}
if (line.yStart < yStart) {
gLine.yStart = yStart;
}
gLine.xStart = line.xStart - xStart;
gLine.yStart = line.yStart - yStart;
gLine.xEnd = line.xEnd - xStart;
gLine.yEnd = line.yEnd - yStart;
sgLine.xStart = parseInt(gLine.xStart * scale);
sgLine.yStart = parseInt(gLine.yStart * scale);
sgLine.xEnd = parseInt(gLine.xEnd * scale);
sgLine.yEnd = parseInt(gLine.yEnd * scale);
sgLine.color = line.color;
glassLineIndex++;
}
glassLineSize = glassLineIndex;
}
|
绘制放大镜中心点
绘制放大镜中心的瞄准器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function drawAnchor() {
context.beginPath();
context.lineWidth = 2;
context.fillStyle = "#fff" ;
context.strokeStyle = "#000" ;
context.arc(parseInt(centerPoint.x), parseInt(centerPoint.y), 10, 0, Math.PI * 2, false );
var radius = 15;
context.moveTo(parseInt(centerPoint.x - radius), parseInt(centerPoint.y));
context.lineTo(parseInt(centerPoint.x + radius), parseInt(centerPoint.y));
context.moveTo(parseInt(centerPoint.x), parseInt(centerPoint.y - radius));
context.lineTo(parseInt(centerPoint.x), parseInt(centerPoint.y + radius));
context.stroke();
}
|
绘制放大镜
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
|
function drawMagnifyingGlass() {
calScaleLines();
context.save();
context.beginPath();
context.arc(centerPoint.x, centerPoint.y, originalRadius, 0, Math.PI * 2, false );
context.clip();
context.beginPath();
context.fillStyle = "#fff" ;
context.arc(centerPoint.x, centerPoint.y, originalRadius, 0, Math.PI * 2, false );
context.fill();
context.lineWidth = 4;
for ( var i = 0; i < glassLineSize; i++) {
context.beginPath();
context.strokeStyle = scaleGlassLines[i].color;
context.moveTo(scaleGlassRectangle.x + scaleGlassLines[i].xStart, scaleGlassRectangle.y + scaleGlassLines[i].yStart);
context.lineTo(scaleGlassRectangle.x + scaleGlassLines[i].xEnd, scaleGlassRectangle.y + scaleGlassLines[i].yEnd);
context.stroke();
}
context.restore();
context.beginPath();
var gradient = context.createRadialGradient(
parseInt(centerPoint.x), parseInt(centerPoint.y), originalRadius - 5,
parseInt(centerPoint.x), parseInt(centerPoint.y), originalRadius);
gradient.addColorStop(0.50, 'silver' );
gradient.addColorStop(0.90, 'silver' );
gradient.addColorStop(1, 'black' );
context.strokeStyle = gradient;
context.lineWidth = 5;
context.arc(parseInt(centerPoint.x), parseInt(centerPoint.y), originalRadius, 0, Math.PI * 2, false );
context.stroke();
drawAnchor();
}
|
添加事件
鼠标拖动
鼠标移动到放大镜上,然后按下鼠标左键,可以拖动放大镜,不按鼠标左键或者不在放大镜区域都不可以拖动放大镜。
为了实现上面的效果,我们要实现3种事件 mousedown , mousemove , 'mouseup', 当鼠标按下时,检测是否在放大镜区域,如果在,设置放大镜可以移动。鼠标移动时更新放大镜中兴点的坐标。鼠标松开时,设置放大镜不可以被移动。
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
|
canvas.onmousedown = function (e) {
var point = windowToCanvas(e.clientX, e.clientY);
var x1, x2, y1, y2, dis;
x1 = point.x;
y1 = point.y;
x2 = centerPoint.x;
y2 = centerPoint.y;
dis = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2);
if (dis < Math.pow(originalRadius, 2)) {
lastPoint.x = point.x;
lastPoint.y = point.y;
moveGlass = true ;
}
}
canvas.onmousemove = function (e) {
if (moveGlass) {
var xDis, yDis;
var point = windowToCanvas(e.clientX, e.clientY);
xDis = point.x - lastPoint.x;
yDis = point.y - lastPoint.y;
centerPoint.x += xDis;
centerPoint.y += yDis;
lastPoint.x = point.x;
lastPoint.y = point.y;
draw();
}
}
canvas.onmouseup = function (e) {
moveGlass = false ;
}
|
鼠标双击
当移动到对应的线段上时,鼠标双击可以选择该线段,将该线段的颜色变为红色。
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
|
canvas.ondblclick = function (e) {
var xStart, xEnd, yStart, yEnd;
var clickPoint = {};
clickPoint.x = scaleGlassRectangle.x + scaleGlassRectangle.width / 2;
clickPoint.y = scaleGlassRectangle.y + scaleGlassRectangle.height / 2;
var index = -1;
for ( var i = 0; i < scaleGlassLines.length; i++) {
var scaleLine = scaleGlassLines[i];
xStart = scaleGlassRectangle.x + scaleLine.xStart - 3;
xEnd = scaleGlassRectangle.x + scaleLine.xStart + 3;
yStart = scaleGlassRectangle.y + scaleLine.yStart;
yEnd = scaleGlassRectangle.y + scaleLine.yEnd;
if (clickPoint.x > xStart && clickPoint.x < xEnd && clickPoint.y < yStart && clickPoint.y > yEnd) {
scaleLine.color = "#f00" ;
index = scaleLine.index;
break ;
}
}
for ( var i = 0; i < chartLines.length; i++) {
var line = chartLines[i];
if (line.index == index) {
line.color = "#f00" ;
} else {
line.color = "#888" ;
}
}
draw();
}
|
键盘事件
因为线段离得比较近,所以使用鼠标移动很难精确的选中线段,这里使用键盘的w , a , s , d 来进行精确移动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
document.onkeyup = function (e) {
if (e.key == 'w' ) {
centerPoint.y = intAdd(centerPoint.y, -0.2);
}
if (e.key == 'a' ) {
centerPoint.x = intAdd(centerPoint.x, -0.2);
}
if (e.key == 's' ) {
centerPoint.y = intAdd(centerPoint.y, 0.2);
}
if (e.key == 'd' ) {
centerPoint.x = intAdd(centerPoint.x, 0.2);
}
draw();
}
|
|