如何在谷歌地图中平滑地放大标记?

56
我希望能够在谷歌地图上平滑地放大标记。如果仅在双击时设置缩放比例,地图会突然处于该缩放级别,没有任何平稳过渡。
只有比当前级别多放大一个级别时,谷歌地图才显示出漂亮的平滑过渡。因此,应该可以平滑地放大超过一个级别,但是如何做到呢?
7个回答

68

碰巧最近我也想实现同样的效果,并找到了一个解决方案,我写了一篇文章介绍。基本上,只为每个过渡设置延时不够,因为如果Google的缩放效果还没有完成或者已经完成很久,它很容易导致“开始-停止”类型的缩放。

正如Martin提到的,这种方法有一些缺点,我就不再赘述了。最终是否使用此方法取决于您的选择,主要取决于用户的CPU性能和/或浏览器。但是,当明智使用时,这确实是一个不错的效果,可以给人留下深刻印象。

我的解决方案如下:

// example marker:
var marker = new google.maps.Marker({
    map: map, 
    position: new google.maps.LatLng(-20.3,30.3)
});


// add the double-click event listener
google.maps.event.addListener(marker, 'dblclick', function(event){
    map = marker.getMap();    
    map.setCenter(overlay.getPosition()); // set map center to marker position
    smoothZoom(map, 12, map.getZoom()); // call smoothZoom, parameters map, final zoomLevel, and starting zoom level
});


// the smooth zoom function
function smoothZoom (map, max, cnt) {
    if (cnt >= max) {
        return;
    }
    else {
        z = google.maps.event.addListener(map, 'zoom_changed', function(event){
            google.maps.event.removeListener(z);
            smoothZoom(map, max, cnt + 1);
        });
        setTimeout(function(){map.setZoom(cnt)}, 80); // 80ms is what I found to work well on my system -- it might not work well on all systems
    }
}  
基本上来说,这个问题是通过每次将缩放级别调整一个单位并监听zoom_changed事件,等待80毫秒后再次将缩放级别调整一个单位等方式解决的。这样做的好处在于zoom_changed事件似乎在谷歌地图提供的平滑过渡后被调用,但在实际图像加载之前,因此不会浪费太多带宽。
在超时中的80毫秒也是我想出来的一个"魔法数字" - 建议您进行更全面的测试,并查看不同系统和浏览器上的工作情况,根据您的发现或为不同系统稍微改变算法。
也许每次添加和删除监听器都不是必需的,但如果您愿意,可以自己进行这个小改进。

7
非常好用!如果你使用panTo而不是setCenter,效果会更好... ;) - Osman.Gerwas
最好将那个 self. 删除以避免混淆。 - Fagner Brack
@FagnerBrack 谢谢,已修复。 - Herman Schaaf
4
很不幸,这种肮脏的方法尽管非常接近,但并不可靠。网络延迟可能会使其看起来很愚蠢。不过,还是有所努力 :/ - Sharky
3
жӮЁеҸҜд»ҘдҪҝз”ЁaddListenerOnceжқҘйҒҝе…ҚеҲ йҷӨзӣ‘еҗ¬еҷЁзҡ„ж“ҚдҪңгҖӮ - Chris
博客文章的链接不再可用。 - David

13

这个对我非常有效:

function animateMapZoomTo(map, targetZoom) {
    var currentZoom = arguments[2] || map.getZoom();
    if (currentZoom != targetZoom) {
        google.maps.event.addListenerOnce(map, 'zoom_changed', function (event) {
            animateMapZoomTo(map, targetZoom, currentZoom + (targetZoom > currentZoom ? 1 : -1));
        });
        setTimeout(function(){ map.setZoom(currentZoom) }, 80);
    }
}

4
你可以尝试使用setInterval逐级放大,当达到所需级别时清除它。但问题在于,使其正常工作的间隔完全取决于用户机器的CPU和带宽(它可以加载和呈现新的图像瓦片的速度)。说实话,我不确定是否可以在每种情况下都使其正常工作,但缩放级别之间的小间隔可能会有所帮助。不过需要记住以下几点:
  1. 这将比直接转到所选缩放级别更加耗费用户的CPU和带宽。
  2. 用户必须等待此过程完成才能开始与地图进行交互,这可能会导致非常糟糕的用户体验。
这两个原因以及可能还有其他原因是Google没有在地图中构建您所需的缩放类型的原因 - 因为这是一个坏主意...

是的,我明白你的意思 - 我会记住的。不过这是在内部环境中使用的,所以CPU和带宽是已知变量,不应该成为问题。至于转换需要多长时间 - 只要用户不需要经常使用它,那就没问题了。事实上,在双击标记后不完全迷失方向会很好。 - PeanutButterJelly

3

终于有一篇来自谷歌的示例,教你如何实现它。 对于一个简单的js实现,你需要包含tween.js并使用以下代码:

/* Setup */

const cameraOptions = {
    center: { lat: /* latitude value */, lng: /* longitude value */ },
    zoom: /* zoom value */
};

const mapOptions = Object.assign(Object.assign({}, cameraOptions));

let map = new google.maps.Map(document.getElementById("map"), 
    mapOptions
);


/* Animation */

new TWEEN.Tween(cameraOptions)
    .to({ zoom: /* new zoom value */ }, 1000)
    .easing(TWEEN.Easing.Quadratic.InOut) /* https://sole.github.io/tween.js/examples/03_graphs.html */
    .onUpdate(() => {
        map.moveCamera(cameraOptions);
    })
    .start();

// Setup the animation loop.
function animate(time) {
    requestAnimationFrame(animate);
    TWEEN.update(time);
}

requestAnimationFrame(animate);

您还可以同时动画化缩放中心点,例如通过声明新的中心点:

.to({ zoom: /* new zoom value */, center: { lat: /* new latitude value */, lng: /* new longitude value */ } }, 1000)

1
谢谢您的回复 -- 注意 我浪费了一个小时,因为Google Docs页面链接到了错误的Tween库(非常相似的)!正确的Tween库是在这个答案中链接的那个。 - alelom

3
@Herman Schaaf 您的解决方案很棒,但是当您双击时,会跳过一些缩放。 :D 所以我做了一个解决方案+平滑缩小 我不能全部归功于自己,我已经编辑了来自jsfiddle.net的JesseDobbelaere代码 这是您和Jesse的代码混合体。
function smoothZoom(map, level, cnt, mode)
{
    if(mode == true)
    {
        if (cnt >= level) {
            return;
        }
        else
        {
            if((maxZoomOut + 2) <= cnt)
            {
                var z = google.maps.event.addListener(map, 'zoom_changed', function(event)
                {
                    google.maps.event.removeListener(z);
                    map.setCenter(marker.getPosition());
                    smoothZoom(map, level, cnt + 1, true);
                });
                setTimeout(function(){map.setZoom(cnt);}, timeOut);
            }
            else
            {
                map.setZoom(cnt);
                smoothZoom(map, level, cnt + 1, true);
            }
        }
    }
    else 
    {
        if (cnt < level) {
            return;
        }
        else
        {
            var z = google.maps.event.addListener(map, 'zoom_changed', function(event)
            {
                google.maps.event.removeListener(z);
                map.setCenter(marker.getPosition());
                smoothZoom(map, level, cnt - 1, false);
            });
            if(maxZoomIn - 2 <= cnt)
            {
                map.setZoom(cnt);
            }
            else
            {
                setTimeout(function(){map.setZoom(cnt);}, timeOut);
            }
        }
    }
}    

我已经创建了一些变量,比如timeOut和maxZoomInOut...你可以在jsfiddle上找到完整的代码http://jsfiddle.net/dexy86/9afy9/


1
动画缩放太多了。 - Aamir Afridi
通过缩小和放大,这似乎是适合我的工作解决方案。 +1 - NiroshanJ

0

我尝试了上面提供的替代方法,但是每当我在桌面浏览器或移动应用程序中查看转换时,它都会立即发生,或者至少在每个循环迭代之间的时间内不可感知,至少在我试用的硬件上是如此。

我使用了与其他方法相同的循环,但更改了超时时间,以便在缩放级别较高时缩放更快,在较低时缩放更慢,因此结果更加明显。

我没有使用固定的超时时间,而是将第一个超时时间设置为0,然后将后续超时时间设置为以下快速且简单的方程式。

((1+ currentZoom) / maxZoom) * 500;

在代码中,我假设最大缩放级别为18,但是您可以使用maxZoom来查找给定纬度/经度的缩放级别。我的代码如下:
timeout = (1+map.getZoom() / 18) * 500;

-2

这将平滑地缩放到所需位置。尝试添加以下行:

LatLng latLng = new LatLng(lat,lng);
map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM_FACTOR));

你能详细说明一下吗? - Haseeb Zulfiqar
5
据我所知,这仅适用于使用Android或iOS SDK,而不适用于JS。 - Matthew Webb
这个答案与问题不符,因为它是关于Java的,而问题是关于Javascript的。 - Muneer
尽管这对于这个问题并不是很有帮助,但它解决了我的问题,因为我正在Android上进行此操作。 - Michael Martin
JavaScript 实现中未定义 CameraUpdateFactory。 - 1owk3y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接