圖片放大鏡 效果 "線上演示" "源碼" 原理 首先選擇圖片的一塊區域,然後將這塊區域放大,然後再繪製到原先的圖片上,保證兩塊區域的中心點一致, 如下圖所示: <! more 初始化 獲得 canvas 和 image 對象,這裡使用 `` 標簽預載入圖片, 關於圖片預載入 ...
圖片放大鏡
效果
原理
首先選擇圖片的一塊區域,然後將這塊區域放大,然後再繪製到原先的圖片上,保證兩塊區域的中心點一致, 如下圖所示:
初始化
<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