【HTML5】Canvas 实现放大镜效果
图片放大镜
效果
原理
首先选择图片的一块区域,然后将这块区域放大,然后再绘制到原先的图片上,保证两块区域的中心点一致, 如下图所示:
初始化
<canvas id="canvas" width="500" height="500">
</canvas>
<img src="image.png" style="display: none" id="img">
获得 canvas 和 image 对象,这里使用 <img>
标签预加载图片, 关于图片预加载可以看这里
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();
}
** 参考资料 **
HTML5-MagnifyingGlass
【HTML5】Canvas 实现放大镜效果的更多相关文章
- 基于HTML5 Canvas生成粒子效果的人物头像
前面我们分享过一个HTML5 Canvas实现的图像马赛克模糊效果,HTML5处理图片真的非常简单.今天我们要再利用HTML5 Canvas实现一个粒子效果的人物头像,你可以任意选择一张头像图片,接下 ...
- Canvas实现放大镜效果完整案例
本文主要记录 canvas 在图像.文字处理.离屏技术和放大镜特效的实现过程中使用到的API.先看下效果吧: 一张模糊的图片: 鼠标点击任意位置,产生放大效果: 哇塞~ 一个帅哥,哈哈哈哈~ 1.显示 ...
- jq商品展示图放大镜 and 原生js和html5写的放大镜效果 ~~效果不错
<!DOCTYPE HTML><html lang="en-US"><head> <meta charset="UTF-8&qu ...
- 7个惊艳的HTML5 Canvas动画效果及源码
HTML5非常强大,尤其是现在大部分浏览器都支持HTML5和CSS3,用HTML5制作的动画也多了起来.另外,Canvas上绘制图形非常简单,本文就分享了一些强大的HTML5 Cnavas动画,一起来 ...
- HTML5 canvas炫酷棱镜效果的幻灯片特效
这是一款效果很炫酷华丽的HTML5 canvas带棱镜效果的幻灯片特效. 这个特效在每个幻灯片的前面放置一个图形.并将图形制作为三棱镜效果.它底下的幻灯片图片会被"折射"到棱镜上面 ...
- 8个经典炫酷的HTML5 Canvas动画欣赏
HTML5非常强大,尤其是Canvas技术的应用,让HTML5几乎可以完成所有Flash能完成的效果.本文精选了8个经典炫酷的HTML5 Canvas动画欣赏,每一个都提供全部的源代码,希望对你有所帮 ...
- HTML5 canvas生成图片马赛克特效插件
HTML5 canvas生成图片马赛克特效插件 简要教程 这是一款使用html5 canvas来将图片制作成马赛克效果的js插件.该插件的灵感来自于美国肖像画家Chuck Close.已经有人使用这个 ...
- 16个富有创意的HTML5 Canvas动画特效集合
HTML5技术正在不断的发展和更新,越来越多的开发者也正在加入HTML5阵营,甚至在移动开发上HTML5的地位也是越来越重要了.HTML5中的大部分动画都是通过Canvas实现,因为Canvas就像一 ...
- 16个非常有趣的HTML5 Canvas动画特效集合
HTML5技术正在不断的发展和更新,越来越多的开发者也正在加入HTML5阵营,甚至在移动开发上HTML5的地位也是越来越重要了.HTML5中的大部分动画都是通过Canvas实现,因为Canvas就像一 ...
随机推荐
- 发布EWM RF ITS Mobile 相关服务
发布EWM RF ITS Mobile 相关服务 TCODE:SIAC_PUBLISH_ALL_INT ~XSRFCHECK = 0 SICF 参数: ~THEME 99 ~TRANSACTI ...
- 【Android开发坑系列】如何让Service尽可能存活
流行的思路如下[2015-11-20更新]: 1.让Service杀不死.Service的onStartCommand返回START_STICKY,同时onDestroy里面调用startServic ...
- find the peak value
A peak element is an element that is greater than its neighbors. Given an input array where num[i] ≠ ...
- 一个3D的多人在线游戏, 服务端 + 客户端 【转】
最近学院组织了一个实训,要求是利用Socket通信和D3D的知识, 写一个多人在线的游戏, 服务端是在linux下, 客户是在Windows下: 写这个的目的是想让大家给我找错, 欢迎大家的意见.我的 ...
- String和包装类Integer\Double\Long\Float\Character 都是final类型
String和包装类Integer\Double\Long\Float\Character\Boolean 都是final类型 不可以改变
- .net微信公众号开发——群发消息
作者:王先荣 本文将介绍微信公众号开发中用于群发消息的类MassMessage,包括:(1)MassMessage类:(2)群发:(3)删除:(4)预览:(5)查询发送状态:(6)接收推送群发结 ...
- [Compose] 21. Apply Natural Transformations in everyday work
We see three varied examples of where natural transformations come in handy. const Right = x => ( ...
- [译] 二、开始iOS编程之前,你还需要做什么?
声明:本文翻译自AppCoda网站的文章:What You Need to Begin iOS Programming?,作者是创建者Simon Ng.如有异议,请联系博主. 更新:帖子已经重新被 ...
- Oracle存储过程,以逗号分隔字符串传参的处理
Oracle存储过程,经常会遇见传入的参数是逗号分隔. 处理需要3步: 第一步,创建Type类型 第二部,创建函数 第三部,创建存储过程 代码如下: 第一步: create or replace ty ...
- Xcode报错:“Your build settings specify a provisioning profile with the UUID..... however, no such provisioning profile was found”
运行环境: Xcode5 & 5.0及以上版本 对工程进行Archive打包的时候出现如下错误 问题描述: Code Sign error: No matching provisionin ...