图片放大镜
效果
在线演示 源码
原理
首先选择图片的一块区域,然后将这块区域放大,然后再绘制到原先的图片上,保证两块区域的中心点一致, 如下图所示:
初始化
<canvas id="canvas" width="500" height="500">
</canvas>
<img src="image.png" style="display: none" id="img">
获得 canvas 和 image 对象,这里使用 标签预加载图片, 关于图片预加载可以看这里
var canvas = document.getelementbyid("canvas");
var context = canvas.getcontext("2d");
var img = document.getelementbyid("img");
设置相关变量
// 图片被放大区域的中心点,也是放大镜的中心点
var centerpoint = {};
// 图片被放大区域的半径
var originalradius = 100;
// 图片被放大区域
var originalrectangle = {};
// 放大倍数
var scale = 2;
// 放大后区域
var scaleglassrectangle
画背景图片
function drawbackground() {
context.drawimage(img, 0, 0);
}
计算图片被放大的区域的范围
这里我们使用鼠标的位置作为被放大区域的中心点(放大镜随着鼠标移动而移动),因为 canvas
在画图片的时候,需要知道左上角的坐标以及区域的宽高,所以这里我们计算区域的范围
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 的状态,从堆栈里弹出最顶层的状态。
context.save();
context.beginpath();
context.arc(centerpoint.x, centerpoint.y, originalradius, 0, math.pi * 2, false);
context.clip();
......
context.restore();
计算放大镜区域
通过中心点、被放大区域的宽高以及放大倍数,获得区域的左上角坐标以及区域的宽高。
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 自身作为一副图片,然后取被放大区域的图像,将其绘制到放大镜区域里。
context.drawimage(canvas,
originalrectangle.x, originalrectangle.y,
originalrectangle.width, originalrectangle.height,
scaleglassrectangle.x, scaleglassrectangle.y,
scaleglassrectangle.width, scaleglassrectangle.height
);
绘制放大边缘
createradialgradient 用来绘制渐变图像
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 添加鼠标移动事件
canvas.onmousemove = function (e) {
......
}
转换坐标
鼠标事件获得坐标一般为屏幕的或者 window 的坐标,我们需要将其装换为 canvas 的坐标。getboundingclientrect
用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
function windowtocanvas(x, y) {
var bbox = canvas.getboundingclientrect();
return {x: x - bbox.left, y: y - bbox.top}
}
修改鼠标样式
我们可以通过 css 来修改鼠标样式
#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),那么这两条线几乎就会重叠在一起,如下图所示:
使用图表放大镜的效果
在线演示 源码
原理
类似于地图中的图例,放大镜使用较为精确的图例,如下图所示:
在放大镜坐标系统中,原始的区域会变大,如下图所示
绘制原始线段
首先创建一个线段对象
function line(xstart, ystart, xend, yend, index, color) {
// 起点x坐标
this.xstart = xstart;
// 起点y坐标
this.ystart = ystart;
// 终点x坐标
this.xend = xend;
// 终点y坐标
this.yend = yend;
// 用来标记是哪条线段
this.index = index;
// 线段颜色
this.color = color;
}
初始化线段
// 原始线段
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;
}
}
绘制线段
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();
}
}
计算原始区域和放大镜区域
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)。
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;
}
绘制放大镜中心点
绘制放大镜中心的瞄准器
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.fill();
context.stroke();
}
绘制放大镜
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',
当鼠标按下时,检测是否在放大镜区域,如果在,设置放大镜可以移动。鼠标移动时更新放大镜中兴点的坐标。鼠标松开时,设置放大镜不可以被移动。
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;
}
鼠标双击
当移动到对应的线段上时,鼠标双击可以选择该线段,将该线段的颜色变为红色。
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 来进行精确移动
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();
}
以上就是canvas实现放大镜效果的详细内容。