Google Haritalar'da yakınlaştırma gibi bir HTML 5 tuvalinde farenin altındaki noktayı yakınlaştırabilmek istiyorum . Bunu nasıl başarabilirim?
Google Haritalar'da yakınlaştırma gibi bir HTML 5 tuvalinde farenin altındaki noktayı yakınlaştırabilmek istiyorum . Bunu nasıl başarabilirim?
Yanıtlar:
Daha iyi çözüm, zumdaki değişime göre görünümün konumunu basitçe hareket ettirmektir. Zum noktası basitçe eski zumdaki ve yeni zumdaki aynı kalmak istediğiniz noktadır. Yani önceden yakınlaştırılmış görüntü alanı ve yakınlaştırılmış görüntü alanı görüntü alanına göre aynı yakınlaştırma noktasına sahiptir. Kökeni göre ölçeklendirdiğimiz göz önüne alındığında. Görüntü alanı konumunu buna göre ayarlayabilirsiniz:
scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);
Böylece, yakınlaştırdığınızda, yakınlaştırdığınız noktaya göre ne kadar yakınlaştırdığınıza bağlı olarak aşağı ve sağa kaydırabilirsiniz.
Sonunda çözdü:
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>
@Tatarize'ın işaret ettiği gibi, anahtar, zum noktası (fare işaretçisi) zumdan sonra aynı yerde kalacak şekilde eksen konumunu hesaplamaktır.
Başlangıçta fare mouse/scale
köşeden uzakta , farenin altındaki noktanın zumdan sonra aynı yerde kalmasını istiyoruz, ancak bu mouse/new_scale
köşeden uzakta. Bu nedenle, bunu origin
hesaba katmak için (köşenin koordinatları) kaydırmamız gerekir .
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zoom
Kalan kodun daha sonra ölçeklendirmeyi uygulaması ve çizimin köşeye tuval köşesine denk gelmesi için çizim bağlamına çevirmesi gerekir.
Bu aslında (matematik olarak) çok zor bir problem ve neredeyse aynı şey üzerinde çalışıyorum. Stackoverflow benzer bir soru sordum ama hiçbir yanıt aldım, ama DocType (HTML / CSS için StackOverflow) gönderildi ve bir yanıt aldım. Bir göz atın http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example
Bunu yapan bir jQuery eklentisi oluşturmanın ortasındayım (CSS3 Dönüşümlerini kullanarak Google Haritalar tarzı zum). Fare imlecine biraz yakınlaştım, hala kullanıcının tuvali Google Haritalar'da yapabileceğiniz gibi sürüklemesine izin vermeyi anlamaya çalışıyorum. Çalıştığımda kodu buraya göndereceğim, ancak fare-zoom-to-point kısmı için yukarıdaki bağlantıya göz atın.
Canvas bağlamında ölçek ve çeviri yöntemleri olduğunu fark etmedim, CSS3 kullanarak aynı şeyi elde edebilirsiniz örn. jQuery kullanarak:
$('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)');
CSS3 dönüşüm başlangıç noktasını 0, 0 (-moz-dönüşüm başlangıç noktası: 0 0) olarak ayarladığınızdan emin olun. CSS3 dönüşümünü kullanmak, herhangi bir şeyi yakınlaştırmanıza olanak tanır, sadece konteyner DIV'nin taşmaya ayarlandığından emin olun: yakınlaştırılmış kenarların kenarlardan dökülmesini durdurmak için gizlidir.
CSS3 dönüşümlerini veya tuvalin kendi ölçek ve çeviri yöntemlerini kullanmak size kalmış, ancak hesaplamalar için yukarıdaki bağlantıyı kontrol edin.
Güncelleme: Meh! Bir bağlantıyı izlemenizi sağlamak yerine kodu buraya göndereceğim:
$(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;
});
});
Tabii ki tuval ölçeğini ve çeviri yöntemlerini kullanmak için uyarlamanız gerekir.
Güncelleme 2: Çeviri ile birlikte dönüştürme-orijini kullandığımı fark ettim. Sadece ölçeği kullanan ve kendi çevirisini kullanan bir sürümü uygulamayı başardım, buradan kontrol edin http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Görüntülerin indirilmesini bekleyin, ardından kullanın yakınlaştırmak için fare tekerleği, görüntüyü sürükleyerek de kaydırma destekler. CSS3 Dönüşümleri kullanıyor, ancak Tuvaliniz için aynı hesaplamaları kullanabilmeniz gerekir.
Ben muhtemelen başlamak için sadece OpenGL matrisleri kullanmış olmalıydı c ++ kullanarak bu sorunla karşılaştım ... neyse, eğer kaynağı sol üst köşesi olan bir kontrol kullanıyorsanız ve pan / zoom istiyorum google maps gibi, düzen (olay işleyicim olarak allegro kullanarak):
// 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;
}
Tatarize'nin cevabını seviyorum , ama bir alternatif sunacağım. Bu önemsiz bir doğrusal cebir problemidir ve sunduğum yöntem pan, zoom, eğim vb. İle iyi çalışır. Yani, görüntünüz zaten dönüştürülmüşse iyi çalışır.
Bir matris ölçeklendiğinde ölçek nokta (0, 0) konumundadır. Dolayısıyla, bir görüntünüz varsa ve bunu 2 katına kadar ölçeklerseniz, sağ alt nokta hem x hem de y yönlerinde iki katına çıkar ([0, 0] görüntünün sol üst köşesi olduğu kuralını kullanarak).
Bunun yerine görüntüyü merkeze yakınlaştırmak isterseniz, bir çözüm aşağıdaki gibidir: (1) görüntüyü, merkezi (0, 0) olacak şekilde çevirin; (2) görüntüyü x ve y faktörlerine göre ölçeklendirir; (3) görüntüyü geri çevirir. yani
myMatrix
.translate(image.width / 2, image.height / 2) // 3
.scale(xFactor, yFactor) // 2
.translate(-image.width / 2, -image.height / 2); // 1
Daha soyut olarak, aynı strateji her nokta için geçerlidir. Örneğin, görüntüyü P noktasından ölçeklemek istiyorsanız:
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y);
Ve son olarak, görüntü zaten bir şekilde dönüştürülmüşse (örneğin, döndürülmüş, eğrilmiş, çevrilmiş veya ölçeklenmişse), o zaman mevcut dönüşümün korunması gerekir. Özellikle, yukarıda tanımlanan dönüşümün akım dönüşümü ile çarpımı (veya sağ çarpımı) gerekir.
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y)
.multiply(myMatrix);
İşte aldın. İşte bunu gösteren bir kütle. Fare tekerleğini noktalara kaydırın ve sürekli olarak kaldıklarını göreceksiniz. (Yalnızca Chrome'da test edilmiştir.) Http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
İşte merkez odaklı bir görüntü için çözümüm:
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>
Scale () ve translate () yerine setTransform () kullanan bunu yapmanın alternatif bir yolu. Her şey aynı nesnede saklanır. Kanvasın sayfada 0,0 olduğu varsayılır, aksi takdirde konumunu sayfa kablolarından çıkarmanız gerekir.
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);
};
Kaydırmayı işlemek için eşlik eden kod:
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
};
};
Cevabı kendiniz türetmek için, aynı sayfa koordinatlarının yakınlaştırmadan önce ve sonra aynı tuval koordinatlarıyla eşleşmesi gerektiğini düşünün. O zaman bu denklemden başlayarak bazı cebirler yapabilirsiniz:
(pageCoords - çeviri) / scale = canvasCoords
Burada ayrı ayrı resim çizme ve hareket ettirme - için bazı bilgiler koymak istiyorum.
Bu, yakınlaştırmaları ve görünüm penceresinin konumunu saklamak istediğinizde yararlı olabilir.
İşte çekmece:
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!!!
}
Dikkat ölçeği öncelikle OLMALIDIR .
Ve işte yakınlaştırıcı:
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
}
ve elbette bir tetikleyiciye ihtiyacımız olacak:
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();
}
})
İşte PIXI.js kullanarak @ tatarize yanıtının kod uygulaması. Çok büyük bir görüntünün bir parçası (örneğin google haritalar tarzı) bakarak bir görünüm var.
$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
benim html konteynırım.imageContainer
içinde resim bulunan PIXI kabım.mousePosOnImage
yalnızca görüntü bağlantı noktasına değil, tüm görüntüye göre fare konumudur.Fare pozisyonunu şu şekilde aldım:
imageContainer.on('mousemove', _.bind(function(ev) {
mousePosOnImage = ev.data.getLocalPosition(imageContainer);
mousePosOnViewport.x = ev.data.originalEvent.offsetX;
mousePosOnViewport.y = ev.data.originalEvent.offsetY;
},self));
Yakınlaştırmadan önce ve sonra dünya uzayında (ekran uzayının aksine) noktayı almanız ve ardından delta ile çevirmeniz gerekir.
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;
Fare konumu ekran uzayında olduğundan, onu dünya uzayına dönüştürmeniz gerekir. Basit dönüştürme buna benzer olmalıdır:
world_position = screen_position / scale - translation
kaydırma çubuğunun konumunu yakınlaştırdıktan sonra göstermeniz gereken noktaya doğru işlemek için scrollto (x, y) işlevini kullanabilirsiniz. fare konumunu bulmak için event.clientX ve event.clientY öğesini kullanın. Bu sana yardım edecek
Önemli bir şey ... eğer böyle bir şeyiniz varsa:
body {
zoom: 0.9;
}
Tuvaldeki eşdeğer şeyi yapmanız gerekir:
canvas {
zoom: 1.1;
}