图片放大镜 效果 在线演示     源码 
原理 首先选择图片的一块区域,然后将这块区域放大,然后再绘制到原先的图片上,保证两块区域的中心点一致, 如下图所示:
初始化 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 (); } 
 ** 参考资料 **HTML5-MagnifyingGlass