Saya ingin dapat memperbesar titik di bawah mouse dalam kanvas HTML 5, seperti memperbesar di Google Maps . Bagaimana saya bisa mencapainya?
Saya ingin dapat memperbesar titik di bawah mouse dalam kanvas HTML 5, seperti memperbesar di Google Maps . Bagaimana saya bisa mencapainya?
Jawaban:
Solusi yang lebih baik adalah dengan hanya memindahkan posisi viewport berdasarkan perubahan zoom. Titik zoom hanyalah titik di zoom lama dan zoom baru yang Anda ingin tetap sama. Yang mengatakan viewport pra-perbesar dan viewport pasca-diperbesar memiliki zoompoint relatif sama dengan viewport. Mengingat bahwa kami menskalakan relatif terhadap asal. Anda dapat menyesuaikan posisi viewport sesuai:
scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);
Jadi, Anda bisa menggeser ke bawah dan ke kanan saat memperbesar, dengan faktor seberapa besar Anda memperbesar, relatif terhadap titik yang Anda perbesar.
Akhirnya dipecahkan:
var zoomIntensity = 0.2;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = 600;
var height = 200;
var scale = 1;
var originx = 0;
var originy = 0;
var visibleWidth = width;
var visibleHeight = height;
function draw(){
// Clear screen to white.
context.fillStyle = "white";
context.fillRect(originx,originy,800/scale,600/scale);
// Draw the black square.
context.fillStyle = "black";
context.fillRect(50,50,100,100);
}
// Draw loop at 60FPS.
setInterval(draw, 1000/60);
canvas.onwheel = function (event){
event.preventDefault();
// Get mouse offset.
var mousex = event.clientX - canvas.offsetLeft;
var mousey = event.clientY - canvas.offsetTop;
// Normalize wheel to +1 or -1.
var wheel = event.deltaY < 0 ? 1 : -1;
// Compute zoom factor.
var zoom = Math.exp(wheel*zoomIntensity);
// Translate so the visible origin is at the context's origin.
context.translate(originx, originy);
// Compute the new visible origin. Originally the mouse is at a
// distance mouse/scale from the corner, we want the point under
// the mouse to remain in the same place after the zoom, but this
// is at mouse/new_scale away from the corner. Therefore we need to
// shift the origin (coordinates of the corner) to account for this.
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
// Scale it (centered around the origin due to the trasnslate above).
context.scale(zoom, zoom);
// Offset the visible origin to it's proper position.
context.translate(-originx, -originy);
// Update scale and others.
scale *= zoom;
visibleWidth = width / scale;
visibleHeight = height / scale;
}
<canvas id="canvas" width="600" height="200"></canvas>
Kuncinya, seperti yang ditunjukkan oleh @Tatarize , adalah untuk menghitung posisi sumbu sedemikian rupa sehingga titik zoom (penunjuk mouse) tetap berada di tempat yang sama setelah zoom.
Awalnya mouse berada pada jarak mouse/scale
dari sudut, kami ingin titik di bawah mouse tetap berada di tempat yang sama setelah zoom, tetapi ini berada mouse/new_scale
jauh dari sudut. Karena itu kita perlu menggeser origin
(koordinat sudut) untuk memperhitungkan ini.
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zoom
Kode yang tersisa kemudian perlu menerapkan penskalaan dan menerjemahkan ke konteks draw sehingga asalnya bertepatan dengan sudut kanvas.
Ini sebenarnya masalah yang sangat sulit (secara matematis), dan saya sedang mengerjakan hal yang hampir sama. Saya mengajukan pertanyaan serupa pada Stackoverflow tetapi tidak mendapat tanggapan, tetapi diposting di DocType (StackOverflow untuk HTML / CSS) dan mendapat tanggapan. Lihatlah http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example
Saya sedang membangun plugin jQuery yang melakukan ini (Google Style zoom menggunakan CSS3 Transforms). Saya memiliki bit kursor zoom ke mouse berfungsi dengan baik, masih mencoba mencari cara untuk memungkinkan pengguna untuk menyeret kanvas sekitar seperti yang dapat Anda lakukan di Google Maps. Ketika saya membuatnya berfungsi saya akan memposting kode di sini, tetapi periksa tautan di atas untuk bagian mouse-zoom-to-point.
Saya tidak menyadari ada skala dan menerjemahkan metode pada konteks Canvas, Anda dapat mencapai hal yang sama menggunakan misalnya CSS3. menggunakan jQuery:
$('div.canvasContainer > canvas')
.css('-moz-transform', 'scale(1) translate(0px, 0px)')
.css('-webkit-transform', 'scale(1) translate(0px, 0px)')
.css('-o-transform', 'scale(1) translate(0px, 0px)')
.css('transform', 'scale(1) translate(0px, 0px)');
Pastikan Anda mengatur CSS3 transform-origin menjadi 0, 0 (-moz-transform-origin: 0 0). Menggunakan transformasi CSS3 memungkinkan Anda memperbesar apa pun, pastikan wadah DIV diatur ke melimpah: disembunyikan untuk menghentikan tepian yang diperbesar keluar dari samping.
Apakah Anda menggunakan transformasi CSS3, atau skala sendiri dan metode menerjemahkannya terserah Anda, tetapi periksa tautan di atas untuk perhitungannya.
Pembaruan: Meh! Saya hanya akan memposting kode di sini daripada membuat Anda mengikuti tautan:
$(document).ready(function()
{
var scale = 1; // scale of the image
var xLast = 0; // last x location on the screen
var yLast = 0; // last y location on the screen
var xImage = 0; // last x location on the image
var yImage = 0; // last y location on the image
// if mousewheel is moved
$("#mosaicContainer").mousewheel(function(e, delta)
{
// find current location on screen
var xScreen = e.pageX - $(this).offset().left;
var yScreen = e.pageY - $(this).offset().top;
// find current location on the image at the current scale
xImage = xImage + ((xScreen - xLast) / scale);
yImage = yImage + ((yScreen - yLast) / scale);
// determine the new scale
if (delta > 0)
{
scale *= 2;
}
else
{
scale /= 2;
}
scale = scale < 1 ? 1 : (scale > 64 ? 64 : scale);
// determine the location on the screen at the new scale
var xNew = (xScreen - xImage) / scale;
var yNew = (yScreen - yImage) / scale;
// save the current screen location
xLast = xScreen;
yLast = yScreen;
// redraw
$(this).find('div').css('-moz-transform', 'scale(' + scale + ')' + 'translate(' + xNew + 'px, ' + yNew + 'px' + ')')
.css('-moz-transform-origin', xImage + 'px ' + yImage + 'px')
return false;
});
});
Anda tentu saja perlu menyesuaikannya untuk menggunakan skala kanvas dan menerjemahkan metode.
Pembaruan 2: Saya perhatikan saya menggunakan transform-origin bersama dengan translate. Saya telah berhasil mengimplementasikan versi yang hanya menggunakan skala dan menerjemahkan sendiri, periksa di sini http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Tunggu gambar untuk diunduh kemudian gunakan Anda roda mouse untuk memperbesar, juga mendukung panning dengan menyeret gambar di sekitar. Itu menggunakan CSS3 Transforms tetapi Anda harus dapat menggunakan perhitungan yang sama untuk Kanvas Anda.
Saya mengalami masalah ini menggunakan c ++, yang mungkin seharusnya saya tidak punya saya hanya menggunakan matriks OpenGL untuk memulai dengan ... anyways, jika Anda menggunakan kontrol yang asalnya adalah sudut kiri atas, dan Anda ingin pan / zoom seperti google maps, inilah tata letaknya (menggunakan allegro sebagai pengendali acara saya):
// initialize
double originx = 0; // or whatever its base offset is
double originy = 0; // or whatever its base offset is
double zoom = 1;
.
.
.
main(){
// ...set up your window with whatever
// tool you want, load resources, etc
.
.
.
while (running){
/* Pan */
/* Left button scrolls. */
if (mouse == 1) {
// get the translation (in window coordinates)
double scroll_x = event.mouse.dx; // (x2-x1)
double scroll_y = event.mouse.dy; // (y2-y1)
// Translate the origin of the element (in window coordinates)
originx += scroll_x;
originy += scroll_y;
}
/* Zoom */
/* Mouse wheel zooms */
if (event.mouse.dz!=0){
// Get the position of the mouse with respect to
// the origin of the map (or image or whatever).
// Let us call these the map coordinates
double mouse_x = event.mouse.x - originx;
double mouse_y = event.mouse.y - originy;
lastzoom = zoom;
// your zoom function
zoom += event.mouse.dz * 0.3 * zoom;
// Get the position of the mouse
// in map coordinates after scaling
double newx = mouse_x * (zoom/lastzoom);
double newy = mouse_y * (zoom/lastzoom);
// reverse the translation caused by scaling
originx += mouse_x - newx;
originy += mouse_y - newy;
}
}
}
.
.
.
draw(originx,originy,zoom){
// NOTE:The following is pseudocode
// the point is that this method applies so long as
// your object scales around its top-left corner
// when you multiply it by zoom without applying a translation.
// draw your object by first scaling...
object.width = object.width * zoom;
object.height = object.height * zoom;
// then translating...
object.X = originx;
object.Y = originy;
}
Saya suka jawaban Tatarize , tetapi saya akan memberikan alternatif. Ini adalah masalah aljabar linier sepele, dan metode yang saya sajikan bekerja dengan baik dengan pan, zoom, condong, dll. Artinya, ini berfungsi dengan baik jika gambar Anda sudah diubah.
Ketika sebuah matriks diskalakan, skala berada pada titik (0, 0). Jadi, jika Anda memiliki gambar dan menskalakannya dengan faktor 2, titik kanan bawah akan berlipat ganda dalam arah x dan y (menggunakan konvensi bahwa [0, 0] adalah kiri atas gambar).
Jika Anda ingin memperbesar gambar tentang pusat, maka solusinya adalah sebagai berikut: (1) menerjemahkan gambar sedemikian rupa sehingga pusatnya berada di (0, 0); (2) skala gambar dengan faktor x dan y; (3) menerjemahkan kembali gambar. yaitu
myMatrix
.translate(image.width / 2, image.height / 2) // 3
.scale(xFactor, yFactor) // 2
.translate(-image.width / 2, -image.height / 2); // 1
Secara lebih abstrak, strategi yang sama berfungsi untuk setiap titik. Jika, misalnya, Anda ingin skala gambar pada titik P:
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y);
Dan terakhir, jika gambar sudah ditransformasikan dengan cara tertentu (misalnya, jika diputar, miring, diterjemahkan, atau diskalakan), maka transformasi saat ini perlu dipertahankan. Secara khusus, transformasi yang didefinisikan di atas harus dikalikan setelahnya (atau dikalikan kanan) oleh transformasi saat ini.
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y)
.multiply(myMatrix);
Itu dia. Di sini ada bungkusan yang menunjukkan tindakan ini. Gulir dengan roda mouse pada titik-titik dan Anda akan melihat bahwa mereka secara konsisten tetap diam. (Diuji hanya di Chrome.) Http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
Inilah solusi saya untuk gambar berorientasi pusat:
var MIN_SCALE = 1;
var MAX_SCALE = 5;
var scale = MIN_SCALE;
var offsetX = 0;
var offsetY = 0;
var $image = $('#myImage');
var $container = $('#container');
var areaWidth = $container.width();
var areaHeight = $container.height();
$container.on('wheel', function(event) {
event.preventDefault();
var clientX = event.originalEvent.pageX - $container.offset().left;
var clientY = event.originalEvent.pageY - $container.offset().top;
var nextScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale - event.originalEvent.deltaY / 100));
var percentXInCurrentBox = clientX / areaWidth;
var percentYInCurrentBox = clientY / areaHeight;
var currentBoxWidth = areaWidth / scale;
var currentBoxHeight = areaHeight / scale;
var nextBoxWidth = areaWidth / nextScale;
var nextBoxHeight = areaHeight / nextScale;
var deltaX = (nextBoxWidth - currentBoxWidth) * (percentXInCurrentBox - 0.5);
var deltaY = (nextBoxHeight - currentBoxHeight) * (percentYInCurrentBox - 0.5);
var nextOffsetX = offsetX - deltaX;
var nextOffsetY = offsetY - deltaY;
$image.css({
transform : 'scale(' + nextScale + ')',
left : -1 * nextOffsetX * nextScale,
right : nextOffsetX * nextScale,
top : -1 * nextOffsetY * nextScale,
bottom : nextOffsetY * nextScale
});
offsetX = nextOffsetX;
offsetY = nextOffsetY;
scale = nextScale;
});
body {
background-color: orange;
}
#container {
margin: 30px;
width: 500px;
height: 500px;
background-color: white;
position: relative;
overflow: hidden;
}
img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
max-width: 100%;
max-height: 100%;
margin: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="container">
<img id="myImage" src="http://s18.postimg.org/eplac6dbd/mountain.jpg">
</div>
Berikut cara alternatif untuk melakukannya yang menggunakan setTransform () alih-alih skala () dan terjemahkan (). Semuanya disimpan di objek yang sama. Kanvas diasumsikan sebesar 0,0 pada halaman, jika tidak Anda harus mengurangi posisinya dari coords halaman.
this.zoomIn = function (pageX, pageY) {
var zoomFactor = 1.1;
this.scale = this.scale * zoomFactor;
this.lastTranslation = {
x: pageX - (pageX - this.lastTranslation.x) * zoomFactor,
y: pageY - (pageY - this.lastTranslation.y) * zoomFactor
};
this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
this.lastTranslation.x,
this.lastTranslation.y);
};
this.zoomOut = function (pageX, pageY) {
var zoomFactor = 1.1;
this.scale = this.scale / zoomFactor;
this.lastTranslation = {
x: pageX - (pageX - this.lastTranslation.x) / zoomFactor,
y: pageY - (pageY - this.lastTranslation.y) / zoomFactor
};
this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
this.lastTranslation.x,
this.lastTranslation.y);
};
Kode pendamping untuk menangani panning:
this.startPan = function (pageX, pageY) {
this.startTranslation = {
x: pageX - this.lastTranslation.x,
y: pageY - this.lastTranslation.y
};
};
this.continuePan = function (pageX, pageY) {
var newTranslation = {x: pageX - this.startTranslation.x,
y: pageY - this.startTranslation.y};
this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
newTranslation.x, newTranslation.y);
};
this.endPan = function (pageX, pageY) {
this.lastTranslation = {
x: pageX - this.startTranslation.x,
y: pageY - this.startTranslation.y
};
};
Untuk mendapatkan jawabannya sendiri, pertimbangkan bahwa koordinat halaman yang sama harus cocok dengan koordinat kanvas yang sama sebelum dan sesudah zoom. Maka Anda dapat melakukan beberapa aljabar mulai dari persamaan ini:
(pageCoords - terjemahan) / scale = canvasCoords
Saya ingin memberikan beberapa informasi kepada mereka di sini, yang secara terpisah menggambar dan memindahkannya.
Ini mungkin berguna ketika Anda ingin menyimpan zoom dan posisi viewport.
Di sini adalah laci:
function redraw_ctx(){
self.ctx.clearRect(0,0,canvas_width, canvas_height)
self.ctx.save()
self.ctx.scale(self.data.zoom, self.data.zoom) //
self.ctx.translate(self.data.position.left, self.data.position.top) // position second
// Here We draw useful scene My task - image:
self.ctx.drawImage(self.img ,0,0) // position 0,0 - we already prepared
self.ctx.restore(); // Restore!!!
}
Skala pemberitahuan HARUS menjadi yang pertama .
Dan di sini adalah zoomer:
function zoom(zf, px, py){
// zf - is a zoom factor, which in my case was one of (0.1, -0.1)
// px, py coordinates - is point within canvas
// eg. px = evt.clientX - canvas.offset().left
// py = evt.clientY - canvas.offset().top
var z = self.data.zoom;
var x = self.data.position.left;
var y = self.data.position.top;
var nz = z + zf; // getting new zoom
var K = (z*z + z*zf) // putting some magic
var nx = x - ( (px*zf) / K );
var ny = y - ( (py*zf) / K);
self.data.position.left = nx; // renew positions
self.data.position.top = ny;
self.data.zoom = nz; // ... and zoom
self.redraw_ctx(); // redraw context
}
dan, tentu saja, kita akan membutuhkan penyeret:
this.my_cont.mousemove(function(evt){
if (is_drag){
var cur_pos = {x: evt.clientX - off.left,
y: evt.clientY - off.top}
var diff = {x: cur_pos.x - old_pos.x,
y: cur_pos.y - old_pos.y}
self.data.position.left += (diff.x / self.data.zoom); // we want to move the point of cursor strictly
self.data.position.top += (diff.y / self.data.zoom);
old_pos = cur_pos;
self.redraw_ctx();
}
})
Berikut ini adalah implementasi kode dari jawaban @ tatarize, menggunakan PIXI.js. Saya memiliki viewport melihat bagian dari gambar yang sangat besar (misalnya gaya google maps).
$canvasContainer.on('wheel', function (ev) {
var scaleDelta = 0.02;
var currentScale = imageContainer.scale.x;
var nextScale = currentScale + scaleDelta;
var offsetX = -(mousePosOnImage.x * scaleDelta);
var offsetY = -(mousePosOnImage.y * scaleDelta);
imageContainer.position.x += offsetX;
imageContainer.position.y += offsetY;
imageContainer.scale.set(nextScale);
renderer.render(stage);
});
$canvasContainer
adalah wadah html saya.imageContainer
adalah wadah PIXI saya yang memiliki gambar di dalamnya.mousePosOnImage
adalah posisi mouse relatif terhadap seluruh gambar (bukan hanya port tampilan).Inilah cara saya mendapatkan posisi mouse:
imageContainer.on('mousemove', _.bind(function(ev) {
mousePosOnImage = ev.data.getLocalPosition(imageContainer);
mousePosOnViewport.x = ev.data.originalEvent.offsetX;
mousePosOnViewport.y = ev.data.originalEvent.offsetY;
},self));
Anda harus mendapatkan titik di ruang dunia (berlawanan dengan ruang layar) sebelum dan sesudah zoom, dan kemudian terjemahkan oleh delta.
mouse_world_position = to_world_position(mouse_screen_position);
zoom();
mouse_world_position_new = to_world_position(mouse_screen_position);
translation += mouse_world_position_new - mouse_world_position;
Posisi mouse ada di ruang layar, jadi Anda harus mengubahnya ke ruang dunia. Transformasi sederhana harus serupa dengan ini:
world_position = screen_position / scale - translation
Anda dapat menggunakan fungsi scrollto (x, y) untuk menangani posisi scrollbar langsung ke titik yang harus ditunjukkan setelah zooming. Untuk menemukan posisi mouse gunakan event.clientX dan event.clientY. ini akan membantu Anda
Satu hal penting ... jika Anda memiliki sesuatu seperti:
body {
zoom: 0.9;
}
Anda perlu membuat hal yang sama dalam kanvas:
canvas {
zoom: 1.1;
}