Voglio essere in grado di ingrandire il punto sotto il mouse in una tela HTML 5, come lo zoom su Google Maps . Come posso raggiungerlo?
Voglio essere in grado di ingrandire il punto sotto il mouse in una tela HTML 5, come lo zoom su Google Maps . Come posso raggiungerlo?
Risposte:
La soluzione migliore è semplicemente spostare la posizione del viewport in base alla modifica dello zoom. Il punto di zoom è semplicemente il punto nel vecchio zoom e nel nuovo zoom che si desidera mantenere invariati. Vale a dire che il viewport pre-zoomato e il viewport post-zoom hanno lo stesso punto di zoom rispetto al viewport. Dato che stiamo ridimensionando rispetto all'origine. È possibile regolare la posizione della finestra di conseguenza:
scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);
Quindi davvero puoi semplicemente spostarti verso il basso e verso destra quando ingrandisci, in base a quanto hai ingrandito, rispetto al punto su cui hai ingrandito.
Finalmente risolto:
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>
La chiave, come ha sottolineato @Tatarize , è calcolare la posizione dell'asse in modo che il punto di zoom (puntatore del mouse) rimanga nello stesso posto dopo lo zoom.
Inizialmente il mouse si trova ad una distanza mouse/scale
dall'angolo, vogliamo che il punto sotto il mouse rimanga nello stesso posto dopo lo zoom, ma questo è mouse/new_scale
lontano dall'angolo. Pertanto è necessario spostare le origin
(coordinate dell'angolo) per tenere conto di ciò.
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zoom
Il codice rimanente deve quindi applicare il ridimensionamento e tradurre nel contesto di disegno in modo che l'origine coincida con l'angolo della tela.
Questo è in realtà un problema molto difficile (matematicamente), e sto lavorando quasi alla stessa cosa. Ho fatto una domanda simile su StackOverflow ma non ho ricevuto risposta, ma ho pubblicato in DocType (StackOverflow per HTML / CSS) e ho ottenuto una risposta. Dai un'occhiata http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example
Sono nel mezzo della creazione di un plug-in jQuery che esegue questa operazione (zoom in stile Google Maps con trasformazioni CSS3). Ho lo zoom sul bit del cursore del mouse che funziona bene, ancora cercando di capire come consentire all'utente di trascinare la tela come puoi fare su Google Maps. Quando avrò funzionato pubblicherò il codice qui, ma controlla il link sopra per la parte del mouse-zoom-to-point.
Non mi ero reso conto che esistessero metodi di ridimensionamento e traduzione nel contesto di Canvas, puoi ottenere la stessa cosa usando ad esempio CSS3. usando 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)');
Assicurati di impostare CSS3 transform-origin su 0, 0 (-moz-transform-origin: 0 0). L'uso della trasformazione CSS3 ti consente di ingrandire qualsiasi cosa, assicurati solo che il contenitore DIV sia impostato in modo da traboccare: nascosto per impedire ai bordi ingranditi di fuoriuscire dai lati.
Sia che tu usi trasformazioni CSS3, sia la scala di canvas e i metodi di traduzione, dipende da te, ma controlla il link sopra per i calcoli.
Aggiornamento: Meh! Pubblicherò semplicemente il codice qui invece di farti seguire un link:
$(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;
});
});
Ovviamente dovrai adattarlo per usare la scala della tela e tradurre i metodi.
Aggiornamento 2: Ho appena notato che sto usando Transform-Origin insieme a Translate. Sono riuscito a implementare una versione che utilizza solo la scala e la traduzione da sola, controlla qui http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Attendi il download delle immagini quindi usa il tuo la rotellina del mouse per lo zoom, supporta anche la panoramica trascinando l'immagine. Sta usando CSS3 Transforms ma dovresti essere in grado di usare gli stessi calcoli per il tuo Canvas.
Mi sono imbattuto in questo problema usando c ++, che probabilmente non avrei dovuto avere con le matrici OpenGL per cominciare ... comunque, se stai usando un controllo la cui origine è l'angolo in alto a sinistra, e vuoi pan / zoom come google maps, ecco il layout (usando allegro come gestore dell'evento):
// 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;
}
Mi piace la risposta di Tatarize , ma fornirò un'alternativa. Questo è un banale problema di algebra lineare e il metodo che presento funziona bene con pan, zoom, inclinazione, ecc. Cioè, funziona bene se la tua immagine è già trasformata.
Quando una matrice viene ridimensionata, la scala si trova nel punto (0, 0). Quindi, se hai un'immagine e la ridimensioni di un fattore 2, il punto in basso a destra raddoppierà in entrambe le direzioni xey (usando la convenzione che [0, 0] è la parte in alto a sinistra dell'immagine).
Se invece desideri ingrandire l'immagine attorno al centro, una soluzione è la seguente: (1) traduci l'immagine in modo che il suo centro sia su (0, 0); (2) ridimensiona l'immagine in base ai fattori xey; (3) traduci l'immagine indietro. vale a dire
myMatrix
.translate(image.width / 2, image.height / 2) // 3
.scale(xFactor, yFactor) // 2
.translate(-image.width / 2, -image.height / 2); // 1
Più astrattamente, la stessa strategia funziona per qualsiasi punto. Se, ad esempio, vuoi ridimensionare l'immagine in un punto P:
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y);
Infine, se l'immagine è già stata trasformata in qualche modo (ad esempio, se è ruotata, inclinata, tradotta o ridimensionata), è necessario conservare la trasformazione corrente. In particolare, la trasformazione sopra definita deve essere post-moltiplicata (o moltiplicata a destra) per la trasformazione corrente.
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y)
.multiply(myMatrix);
Ecco qua. Ecco un plunk che lo mostra in azione. Scorri con la rotellina del mouse sui punti e vedrai che rimangono costantemente posizionati. (Testato solo su Chrome.) Http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
Ecco la mia soluzione per un'immagine orientata al centro:
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>
Ecco un modo alternativo per farlo che utilizza setTransform () invece di scale () e translate (). Tutto è memorizzato nello stesso oggetto. Si presume che la tela sia a 0,0 sulla pagina, altrimenti dovrai sottrarre la sua posizione dai coord della pagina.
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);
};
Codice di accompagnamento per gestire il 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
};
};
Per ricavare tu stesso la risposta, considera che le stesse coordinate della pagina devono corrispondere alle stesse coordinate del canvas prima e dopo lo zoom. Quindi puoi fare un po 'di algebra a partire da questa equazione:
(pageCoords - traduzione) / scale = canvasCoords
Voglio mettere qui alcune informazioni per coloro che fanno separatamente il disegno dell'immagine e lo spostano - ingrandendolo.
Ciò può essere utile quando si desidera memorizzare gli zoom e la posizione della finestra.
Ecco il cassetto:
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!!!
}
Nota che la scala DEVE essere la prima .
Ed ecco lo 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
}
e, naturalmente, avremmo bisogno di un trascinatore:
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();
}
})
Ecco un'implementazione del codice della risposta di @ tatarize, usando PIXI.js. Ho una finestra che guarda parte di un'immagine molto grande (ad es. Stile 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
è il mio contenitore HTML.imageContainer
è il mio contenitore PIXI che contiene l'immagine.mousePosOnImage
è la posizione del mouse rispetto all'intera immagine (non solo alla porta di visualizzazione).Ecco come ho ottenuto la posizione del 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));
È necessario ottenere il punto nello spazio mondiale (in opposizione allo spazio dello schermo) prima e dopo lo zoom, quindi tradurre dal 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;
La posizione del mouse è nello spazio dello schermo, quindi è necessario trasformarlo nello spazio del mondo. La trasformazione semplice dovrebbe essere simile a questa:
world_position = screen_position / scale - translation
puoi usare la funzione scrollto (x, y) per gestire la posizione della barra di scorrimento fino al punto in cui devi essere mostrato dopo lo zoom. per trovare la posizione del mouse usa event.clientX ed event.clientY. questo ti aiuterà
Una cosa importante ... se hai qualcosa del tipo:
body {
zoom: 0.9;
}
Hai bisogno di rendere l'equivalente in tela:
canvas {
zoom: 1.1;
}