简体   繁体   English

如何使用 Javascript 平滑平移内联 SVG

[英]How to pan an inline SVG smoothly with Javascript

In a Cordova/Android app that I am creating I have to implement my own zooming & panning (no libraries allowed nor suitable) of an inline SVG image.在我创建的 Cordova/Android 应用程序中,我必须实现我自己的内联 SVG 图像的缩放和平移(不允许或不适合的库)。 My effort thus far is shown below.到目前为止,我的努力如下所示。

 var _hold = {zoom:1}; function preparePanZoom() { var actuY,scaleX,scaleY; _hold.factorX = 1600/window.innerWidth; actuY = (0.855*window.innerHeight); _hold.factorY = 770/actuY; _hold.displaceY = 0.145*window.innerHeight; scaleX = 1/_hold.factorX; scaleY = 1/_hold.factorY; _hold.panMax = [0,_hold.displaceY - actuY]; _hold.baseMatrix = `matrix(${scaleX} 0 0 ${scaleY} 0 0)`; _hold.baseScale = `scale(${scaleX},${scaleY})`; document.getElementById('btnReset').addEventListener('touchstart',resetZoom); var gOuter = document.getElementById('gOuter'); gOuter.addEventListener('touchstart',zoomManage); gOuter.setAttribute('transform',_hold.baseScale); } function resetZoom() { document.getElementById('btnReset').style.display = 'none'; var gOuter = document.getElementById('gOuter'); gOuter.setAttribute('transform',_hold.baseScale); gOuter.addEventListener('touchstart',zoomManage); gOuter.removeEventListener('touchstart',panStart); gOuter.removeEventListener('touchmove',panMove); _hold.zoom = 1; } function zoomManage(e) { if (1 < _hold.zoom) return; if (_hold.magnifier) { clearTimeout(_hold.magnifier); delete(_hold.magnifier); if (0 < e.touches.length) { var tch = e.touches[0]; document.getElementById('btnReset').style.display = 'block'; expandAround(tch.clientX,tch.clientY - _hold.displaceY); } } else { _hold.magnifier = setTimeout(clearMagnifier,200); _hold.tapstart = Math.round(new Date().getTime()/50); } } function clearMagnifier() { if (_hold.magnifier) { clearTimeout(_hold.magnifier); delete(_hold.magnifier); } } function expandAround(cX,cY) { var x = cX*1600/window.innerWidth, y = cY*770/(0.855*window.innerHeight), t1 = `translate(${-x},${-y})`, t2 = `translate(${x},${y})`, gOuter = document.getElementById('gOuter'), transform = `${_hold.baseScale} ${t2} scale(2,2) ${t1}`; _hold.panMin = [cX,cY]; _hold.panMax[0]= cX - window.innerWidth; _hold.lastTransform = transform; gOuter.setAttribute('transform',transform); document.getElementById('btnReset').style.display = 'block'; gOuter.removeEventListener('touchstart',zoomManage); gOuter.addEventListener('touchstart',panStart,{passive:true}); gOuter.addEventListener('touchmove',panMove,{passive:true}); _hold.zoom = 2; } function panStart(evt) { evt.stopPropagation(); _hold.rafCount = 0; } function panMove(evt) { var cX,cY, moveX,moveY, cht = evt.changedTouches; evt.stopPropagation(); if (3 < ++_hold.rafCount) return; _hold.rafCount = 0; if (0 < cht.length) { cht = cht[0]; cX = cht.clientX; cY = cht.clientY; if ((0 >= cX) || (_hold.displaceY >= cY)) return; moveX = _hold.panMin[0] - cX; moveY = _hold.panMin[1] - cY; if (0 < moveX) { moveX = (moveX < _hold.panMax[0])?_hold.panMax[0]:moveX; } else { moveX = (moveX > _hold.panMin[0])?_hold.panMin[0]:moveX; } if (0 < moveY) { moveY = (moveY < _hold.panMax[1])?_hold.panMax[1]:moveY; } else { moveY = (moveY > _hold.panMin[1])?_hold.panMin[1]:moveY; } _hold.panText = ` translate(${moveX},${moveY})`; if (!_hold.queued) _hold.queued = window.requestAnimationFrame(performPan); } } function performPan() { delete(_hold.queued); var transform = _hold.lastTransform + _hold.panText; var gOuter = document.getElementById('gOuter'); gOuter.setAttribute('transform',_hold.baseMatrix); gOuter.setAttribute('transform',transform); } preparePanZoom();
 body,html{padding:0;margin:0;font-family:arial;} #btnReset { border-radius:8px; padding:0.5em; background-color:blue; color:white; display:none; } #puzzle { position:relative; height:85.5vh !important; width:100vw !important; } #controlBar { min-height:14.5vh; background-color:blue; padding:0.25em; display:grid; place-items:right center; }
 <div id='controlBar'> <button id='btnReset'>Reset</button> </div> <svg width="100%" height="100%" preserveAspectRatio="none" id="puzzle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> <g id="gOuter"> <rect x="1.135" y="-0.248" width="1597.73" height="767.092" style="fill:rgb(21,135,221);"/> <path d="M170.78,57.624C228.712,57.624 275.745,96.776 275.745,145C275.745,193.224 228.712,232.376 170.78,232.376C112.849,232.376 65.816,193.224 65.816,145C65.816,96.776 112.849,57.624 170.78,57.624ZM170.78,101.312C199.746,101.312 223.262,120.888 223.262,145C223.262,169.112 199.746,188.688 170.78,188.688C141.814,188.688 118.298,169.112 118.298,145C118.298,120.888 141.814,101.312 170.78,101.312Z" style="fill:rgb(199,21,221);"/> <path d="M743.696,185.2C737.747,184.555 731.756,184.555 725.807,185.2L722.861,201.216C717.159,202.19 711.571,203.862 706.197,206.201L696.473,193.977C691.033,196.739 685.844,200.083 680.981,203.964L685.601,219.478C681.1,223.505 677.009,228.073 673.402,233.099L659.507,227.941C656.032,233.37 653.037,239.163 650.563,245.239L661.512,256.095C659.416,262.096 657.919,268.336 657.046,274.702L642.703,277.992C642.125,284.634 642.125,291.323 642.703,297.966L657.046,301.255C657.919,307.622 659.416,313.862 661.512,319.862L650.563,330.719C653.037,336.794 656.032,342.587 659.507,348.017L673.402,342.858C677.009,347.885 681.1,352.453 685.601,356.479L680.981,371.994C685.844,375.874 691.033,379.219 696.473,381.981L706.197,369.756C711.571,372.096 717.159,373.768 722.861,374.742L725.807,390.757C731.756,391.402 737.747,391.402 743.696,390.757L746.642,374.742C752.344,373.768 757.933,372.096 763.307,369.756L773.03,381.981C778.471,379.219 783.659,375.874 788.522,371.994L783.902,356.479C788.404,352.453 792.495,347.885 796.101,342.858L809.996,348.017C813.471,342.587 816.467,336.794 818.941,330.719L807.992,319.862C810.087,313.862 811.585,307.622 812.457,301.255L826.801,297.966C827.379,291.323 827.379,284.634 826.801,277.992L812.457,274.702C811.585,268.336 810.087,262.096 807.992,256.095L818.941,245.239C816.467,239.163 813.471,233.37 809.996,227.941L796.101,233.099C792.495,228.073 788.404,223.505 783.902,219.478L788.522,203.964C783.659,200.083 778.471,196.739 773.03,193.977L763.307,206.201C757.933,203.862 752.344,202.19 746.642,201.216L743.696,185.2ZM734.752,267.326C744.96,267.326 753.248,276.58 753.248,287.979C753.248,299.377 744.96,308.631 734.752,308.631C724.543,308.631 716.255,299.377 716.255,287.979C716.255,276.58 724.543,267.326 734.752,267.326Z" style="fill:rgb(221,97,21);"/> <path d="M1104.68,419.383C1122.96,384.433 1159.51,384.433 1177.78,401.908C1196.06,419.383 1196.06,454.333 1177.78,489.284C1164.99,515.496 1132.09,541.709 1104.68,559.184C1077.27,541.709 1044.37,515.496 1031.58,489.284C1013.3,454.333 1013.3,419.383 1031.58,401.908C1049.85,384.433 1086.4,384.433 1104.68,419.383Z" style="fill:rgb(221,212,21);"/> <path d="M1418.44,147.496C1423.69,141.596 1434.21,141.596 1439.46,144.546C1444.72,147.496 1444.72,153.397 1439.46,159.298C1435.78,163.723 1426.32,168.149 1418.44,171.099C1410.56,168.149 1401.1,163.723 1397.42,159.298C1392.16,153.397 1392.16,147.496 1397.42,144.546C1402.67,141.596 1413.18,141.596 1418.44,147.496Z" style="fill:rgb(68,221,21);"/> <path d="M402.555,569.548L419.013,583.465L410.784,596.648L424.099,601.684L417.813,624.203L404.498,619.167L404.498,635.463L384.155,635.463L384.155,619.167L370.84,624.203L364.553,601.684L377.868,596.648L369.639,583.465L386.097,569.548L394.326,582.731L402.555,569.548Z" style="fill:rgb(68,221,21);"/> <path d="M1400.85,344.716L1406.84,363.44L1418.39,357.654L1416.54,370.591L1435.92,370.591L1420.24,382.163L1429.23,391.525L1416.54,393.735L1422.53,412.458L1406.84,400.887L1400.85,412.458L1394.86,400.887L1379.17,412.458L1385.16,393.735L1372.48,391.525L1381.46,382.163L1365.78,370.591L1385.16,370.591L1383.31,357.654L1394.86,363.44L1400.85,344.716Z" style="fill:rgb(21,57,221);"/> <path d="M332.482,332.234C299.894,332.234 273.475,360.685 273.475,395.78C273.475,430.852 299.915,459.326 332.482,459.326C365.071,459.326 391.489,430.876 391.489,395.78L332.482,395.78L332.482,332.234Z" style="fill:rgb(21,57,221);"/> </g> </svg>

A few notes on my requirements and my implementation:关于我的要求和我的实施的一些说明:

  • I only require one level of zoom - in effect ax 2 zoom.我只需要一级缩放 - 实际上是 ax 2 缩放。
  • Panning is only relevant AFTER zooming平移仅在缩放后相关
  • Aspect ratio presrevation is not important纵横比保留并不重要
  • I wrap the entire contents of the SVG in a outer group bearing the id gOuter我将 SVG 的全部内容包装在一个带有 id gOuter的外部组中
  • This makes it easier for me to implement the zoom & pan code.这使我更容易实现缩放和平移代码。

In brief here is how I have implemented panning简而言之,这里是我如何实现平移

  • I use the touchmove event on the wrapping group, gOuter to calculate the extent of the move我在包装组上使用touchmove事件, gOuter来计算移动的范围
  • The move is performed as a translation and the previously applied scaling transformation from the prior Zoom operation is reapplied to ensure that the image does not slide off the screen.移动是作为平移执行的,并且之前应用的scaling变换从之前的缩放操作被重新应用,以确保图像不会滑出屏幕。

The outstanding issues悬而未决的问题

  1. It is still possible to pan off the extreme edgges of the picture and end up displaying a blank white space仍然可以平移图片的极端边缘并最终显示空白区域
  2. While this works well on a desktop PC on phones I have found that the panning operation is less than smooth虽然这在手机上的台式电脑上运行良好,但我发现平移操作不太流畅
  3. I attempted to deal with this my not responding - the in the Window.requestAnimationFrame handler - to every single mouse move but this has only helped a bit我试图处理这个我没有响应的问题 - 在 Window.requestAnimationFrame 处理程序中 - 每一次鼠标移动,但这只是有点帮助
    1. On hand held devices it is difficult to pan to the edges - on desktop monitors set up in Chrome to mimic a small phone screen it works well since you can simply keep moving beyond the virtual edge of the mimicked handheld screen在手持设备上,很难平移到边缘 - 在 Chrome 中设置的桌面显示器模拟小手机屏幕上它运行良好,因为您可以简单地继续移动到模拟手持屏幕的虚拟边缘之外

I'd be grateful to anyone who might be able to suggest ways to improve the panning process.我会很感激任何能够提出改进平移过程的方法的人。

Here are 2 ways to do this (there may be more);这里有两种方法可以做到这一点(可能还有更多);

option 1 : You can use this where you can put the svg in a container which can be sized and scrolled.选项 1 :您可以使用它来将 svg 放入可以调整大小和滚动的容器中。

Uses svg size to zoom and container scroll to pan.使用 svg 大小进行缩放,使用容器滚动进行平移。

(You can hide the scollbars if you want and still effect a scoll or leave them visible if you prefer) (如果你愿意,你可以隐藏 scollbars 并且仍然影响一个 scoll 或者如果你愿意的话让它们保持可见)

For this to work the container must be display:inline-block or display:block (because with display:inline you cannot set width or height).为此,容器必须是display:inline-blockdisplay:block (因为display:inline你不能设置宽度或高度)。

option 2 : For any situation regardless of container.选项 2 :适用于任何情况,无论容器如何。

Uses svg viewbox to zoom and pan..使用 svg viewbox 缩放和平移..


NB.注意。

Option 1 may be faster as you're doing less of the work and leaving more of it to the underlying native functionality.选项 1 可能会更快,因为您所做的工作较少,而将更多工作留给底层本机功能。 Option 1 is also simpler to code.选项 1 的编码也更简单。 Option 1 also takes care of the limits of panning - you can't scroll beyond what's available.选项 1 还考虑了平移的限制 - 您不能滚动超出可用范围。

However, with option 1, if you zoom (resize) you may need to wait for the browser to reflow the document before you can align by setting the scroll values - the available limits of scroll won't get updated until the next reflow - so to zoom and stay aligned you may need to resize and call requestAnimationFrame to set the scroll when the scroll becomes available.但是,对于选项 1,如果您缩放(调整大小),您可能需要等待浏览器重新排列文档,然后才能通过设置滚动值来对齐 - 滚动的可用限制在下一次重新排列之前不会更新 - 所以要缩放并保持对齐,您可能需要调整大小并在滚动可用时调用 requestAnimationFrame 来设置滚动。


code代码

Uses the OP svg with the addition of a viewbox in the markup.使用 OP svg 并在标记中添加一个视图框。

bootstrap is just for button styling. bootstrap 仅用于按钮样式。

option 1 example选项 1 示例

 let svg = null ; //for zooming let svgContainer = null ; // for scrolling/panning let svgWidth = 0 ;// unknown let svgHeight = 0 ; // unknown const zoomFactor = 1.5 ; let zoomValue = 1; function setSVGSize(){ svgWidth = svg.getBoundingClientRect().width * zoomValue; // offsetWidth is not available on svgs and svg.width.baseVal.value does not behave the same in FF and Chrome; svgHeight = svg.getBoundingClientRect().height * zoomValue; svg.style.height = svgHeight + "px" ; svg.style.width = svgWidth + "px" ; } function zoom(zoomType){ switch(zoomType){ case -1://zoomValue out zoomValue = 1 / zoomFactor ; setSVGSize(); break; case 0://reset //just clear and let the browser decide what it should be zoomValue = 1 ; svg.style.height = "" ; svg.style.width = "" ; break; case 1://zoomValue in zoomValue = zoomFactor ; setSVGSize(); break; default: console.log("invalid zoomType"); } } function pan(dist){ if(dist === 0){//reset svgContainer.scrollLeft = 0 ; } else{ svgContainer.scrollLeft += dist ; } } //initialise svg and svgContainer once available function init(){ svg = document.getElementById("svg") ; svgContainer = document.getElementById("svgContainer") ; } window.addEventListener("load",init); /* * The next bit is just for pan animation / the purposes of demonstrating a smooth pan - it's the same code in both examples (option 1 and option 2). * However, in this option as you can't scroll beyond 0 or scrollWidth the pan calls will have no effect once you reach the edges of the scrollable content. * If moving / panning / scrolling in response to a touch gesture you might not need to animate - * you might just set the new offset to the touch/pointer distance immediately. * ie. use the pan function above directly as pan(pointerMoveDistance). */ let animationFrameRequest = 0 ; // so we can cancel an unfinished pan animation if starting a new one / resetting. const scrPxPanDistance = 200 ; const scrPxFrameSpeed = 1 ; // scr px per frame const framesPerPan = scrPxPanDistance / scrPxFrameSpeed ; let scrPxFrameVelocity = 0; // add a -ve sign to the scrPxFrameSpeed to reverse direction if necesary let framesRemaining = 0 ; function animatePan(){ if(framesRemaining > 0){ framesRemaining-- ; pan(scrPxFrameVelocity); animationFrameRequest = requestAnimationFrame(animatePan) ; } } function cancelCurrentAnimation(){ if(animationFrameRequest){ //cancel any running animation cancelAnimationFrame(animationFrameRequest); animationFrameRequest = 0 ; } } function startAnimatedPan(left){// false => right cancelCurrentAnimation(); scrPxFrameVelocity = left ? scrPxFrameSpeed : -scrPxFrameSpeed ; framesRemaining = framesPerPan ; animatePan(); } function resetPan(){ cancelCurrentAnimation(); pan(0); }
 *{ border:none; padding:0; font-family:Arial; box-sizing:border-box; } body{ margin:10px; background:lightblue; } #svgContainer{ display: inline-block ; /* or "block" - plain inline has no "scollability" at present */ overflow : hidden ; /* also hides the scrollbars but doesn't stop you from scrolling*/ background-color: lightyellow; /* if we don't set some limits on the container everything can just keep getting bigger and there will never be any need/ability to scroll/pan */ max-width: 50vw; max-height: 60vh; } #svg{ margin:0; display:inline; }
 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/> <div id="svgContainer"> <svg id="svg" width="100%" height="100%" viewBox="0 0 1597.73 767.092" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> <g id="gOuter"> <rect x="1.135" y="-0.248" width="1597.73" height="767.092" style="fill:rgb(21,135,221);"/> <path d="M170.78,57.624C228.712,57.624 275.745,96.776 275.745,145C275.745,193.224 228.712,232.376 170.78,232.376C112.849,232.376 65.816,193.224 65.816,145C65.816,96.776 112.849,57.624 170.78,57.624ZM170.78,101.312C199.746,101.312 223.262,120.888 223.262,145C223.262,169.112 199.746,188.688 170.78,188.688C141.814,188.688 118.298,169.112 118.298,145C118.298,120.888 141.814,101.312 170.78,101.312Z" style="fill:rgb(199,21,221);"/> <path d="M743.696,185.2C737.747,184.555 731.756,184.555 725.807,185.2L722.861,201.216C717.159,202.19 711.571,203.862 706.197,206.201L696.473,193.977C691.033,196.739 685.844,200.083 680.981,203.964L685.601,219.478C681.1,223.505 677.009,228.073 673.402,233.099L659.507,227.941C656.032,233.37 653.037,239.163 650.563,245.239L661.512,256.095C659.416,262.096 657.919,268.336 657.046,274.702L642.703,277.992C642.125,284.634 642.125,291.323 642.703,297.966L657.046,301.255C657.919,307.622 659.416,313.862 661.512,319.862L650.563,330.719C653.037,336.794 656.032,342.587 659.507,348.017L673.402,342.858C677.009,347.885 681.1,352.453 685.601,356.479L680.981,371.994C685.844,375.874 691.033,379.219 696.473,381.981L706.197,369.756C711.571,372.096 717.159,373.768 722.861,374.742L725.807,390.757C731.756,391.402 737.747,391.402 743.696,390.757L746.642,374.742C752.344,373.768 757.933,372.096 763.307,369.756L773.03,381.981C778.471,379.219 783.659,375.874 788.522,371.994L783.902,356.479C788.404,352.453 792.495,347.885 796.101,342.858L809.996,348.017C813.471,342.587 816.467,336.794 818.941,330.719L807.992,319.862C810.087,313.862 811.585,307.622 812.457,301.255L826.801,297.966C827.379,291.323 827.379,284.634 826.801,277.992L812.457,274.702C811.585,268.336 810.087,262.096 807.992,256.095L818.941,245.239C816.467,239.163 813.471,233.37 809.996,227.941L796.101,233.099C792.495,228.073 788.404,223.505 783.902,219.478L788.522,203.964C783.659,200.083 778.471,196.739 773.03,193.977L763.307,206.201C757.933,203.862 752.344,202.19 746.642,201.216L743.696,185.2ZM734.752,267.326C744.96,267.326 753.248,276.58 753.248,287.979C753.248,299.377 744.96,308.631 734.752,308.631C724.543,308.631 716.255,299.377 716.255,287.979C716.255,276.58 724.543,267.326 734.752,267.326Z" style="fill:rgb(221,97,21);"/> <path d="M1104.68,419.383C1122.96,384.433 1159.51,384.433 1177.78,401.908C1196.06,419.383 1196.06,454.333 1177.78,489.284C1164.99,515.496 1132.09,541.709 1104.68,559.184C1077.27,541.709 1044.37,515.496 1031.58,489.284C1013.3,454.333 1013.3,419.383 1031.58,401.908C1049.85,384.433 1086.4,384.433 1104.68,419.383Z" style="fill:rgb(221,212,21);"/> <path d="M1418.44,147.496C1423.69,141.596 1434.21,141.596 1439.46,144.546C1444.72,147.496 1444.72,153.397 1439.46,159.298C1435.78,163.723 1426.32,168.149 1418.44,171.099C1410.56,168.149 1401.1,163.723 1397.42,159.298C1392.16,153.397 1392.16,147.496 1397.42,144.546C1402.67,141.596 1413.18,141.596 1418.44,147.496Z" style="fill:rgb(68,221,21);"/> <path d="M402.555,569.548L419.013,583.465L410.784,596.648L424.099,601.684L417.813,624.203L404.498,619.167L404.498,635.463L384.155,635.463L384.155,619.167L370.84,624.203L364.553,601.684L377.868,596.648L369.639,583.465L386.097,569.548L394.326,582.731L402.555,569.548Z" style="fill:rgb(68,221,21);"/> <path d="M1400.85,344.716L1406.84,363.44L1418.39,357.654L1416.54,370.591L1435.92,370.591L1420.24,382.163L1429.23,391.525L1416.54,393.735L1422.53,412.458L1406.84,400.887L1400.85,412.458L1394.86,400.887L1379.17,412.458L1385.16,393.735L1372.48,391.525L1381.46,382.163L1365.78,370.591L1385.16,370.591L1383.31,357.654L1394.86,363.44L1400.85,344.716Z" style="fill:rgb(21,57,221);"/> <path d="M332.482,332.234C299.894,332.234 273.475,360.685 273.475,395.78C273.475,430.852 299.915,459.326 332.482,459.326C365.071,459.326 391.489,430.876 391.489,395.78L332.482,395.78L332.482,332.234Z" style="fill:rgb(21,57,221);"/> </g> </svg> </div> <br><br> <div class="container"> <div class="btn-group"> <button class="btn btn-primary" onclick="zoom(-1);">zoom out</button> <button class="btn btn-secondary" onclick="zoom(0);">reset</button> <button class="btn btn-primary" onclick="zoom(1);">zoom in</button> </div> <div class="btn-group"> <button class="btn btn-primary" onclick="startAnimatedPan(true);">pan left</button> <button class="btn btn-secondary" onclick="resetPan();">reset</button> <button class="btn btn-primary" onclick="startAnimatedPan(false);">pan right</button> </div> </div>

option 2 example ( When running this snippet you may want to go full page and then reduce your browser window to see it properly - you can restrict the container but this is with an unrestricted container )选项 2 示例(运行此代码段时,您可能希望浏览整页,然后缩小浏览器窗口以正确查看它 - 您可以限制容器,但这是使用不受限制的容器)

 let svg = null ; const zoomFactor = 1.5 ; let zoomLevel = 1; const imageWidth = 1597.73; //img px as defined in the svg markup const imageHeight = 767.092; //img px as defined in the svg markup let offsetX = 0 ; //screen px let pixelRatioX = null ;// img px / scr pixel let viewWidth = imageWidth ; let viewHeight = imageHeight; function evalPixelRatioX(){ let svgWidth = svg.getBoundingClientRect().width ; // offsetWidth is not available on svgs and svg.width.baseVal.value does not behave the same in FF and Chrome; pixelRatioX = (imageWidth / svgWidth) /zoomLevel ; } function setViewPort(){ viewWidth = imageWidth / zoomLevel ; viewHeight = imageHeight / zoomLevel ; evalPixelRatioX(); svg.setAttribute("viewBox",`${offsetX * pixelRatioX} 0 ${viewWidth} ${viewHeight}`) ; } function zoom(zoomType){ switch(zoomType){ case -1://zoom out zoomLevel = zoomLevel / zoomFactor ; break; case 0://reset zoomLevel = 1 ; break; case 1://zoom in zoomLevel = zoomLevel * zoomFactor ; break; default: console.log("invalid zoomType"); } setViewPort(); } function pan(dist){//scr px if(dist === 0){//reset offsetX = 0 ; } else{ offsetX += dist ; } setViewPort(); } //initialise svg once available function init(){ svg = document.getElementById("svg") ; } window.addEventListener("load",init); /* * The next bit is just for pan animation / the purposes of demonstrating a smooth pan - it's the same code in both examples (option 1 and option 2). * However, in this option panning in either direction can continue indefinitely as it is not limited by scrollWidth as in option 1 * If moving / panning / scrolling in response to a touch gesture you might not need to animate - * you might just set the new offset to the touch/pointer distance immediately. * ie. use the pan function above directly as pan(pointerMoveDistance). */ let animationFrameRequest = 0 ; // so we can cancel an unfinished pan animation if starting a new one / resetting. const scrPxPanDistance = 200 ; const scrPxFrameSpeed = 1 ; // scr px per frame const framesPerPan = scrPxPanDistance / scrPxFrameSpeed ; let scrPxFrameVelocity = 0; // add a -ve sign to the scrPxFrameSpeed to reverse direction if necesary let framesRemaining = 0 ; function animatePan(){ if(framesRemaining > 0){ framesRemaining-- ; pan(scrPxFrameVelocity); animationFrameRequest = requestAnimationFrame(animatePan) ; } } function cancelCurrentAnimation(){ if(animationFrameRequest){ //cancel any running animation cancelAnimationFrame(animationFrameRequest); animationFrameRequest = 0 ; } } function startAnimatedPan(left){// false => right cancelCurrentAnimation(); scrPxFrameVelocity = left ? scrPxFrameSpeed : -scrPxFrameSpeed ; framesRemaining = framesPerPan ; animatePan(); } function resetPan(){ cancelCurrentAnimation(); pan(0); }
 *{ border:none; padding:0; font-family:Arial; box-sizing:border-box; } body{ margin:10px; background:lightblue; } #bkg{ display: inline ; background-color: lightyellow; } #svg{ margin:0; display:inline; }
 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/> <div id="bkg"><!-- avoid extra space in inline element from markup line returns / whitespace --><svg id="svg" width="100%" height="100%" viewBox="0 0 1597.73 767.092" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><!-- --><g id="gOuter"><!-- --><rect x="1.135" y="-0.248" width="1597.73" height="767.092" style="fill:rgb(21,135,221);"/><!-- --><path d="M170.78,57.624C228.712,57.624 275.745,96.776 275.745,145C275.745,193.224 228.712,232.376 170.78,232.376C112.849,232.376 65.816,193.224 65.816,145C65.816,96.776 112.849,57.624 170.78,57.624ZM170.78,101.312C199.746,101.312 223.262,120.888 223.262,145C223.262,169.112 199.746,188.688 170.78,188.688C141.814,188.688 118.298,169.112 118.298,145C118.298,120.888 141.814,101.312 170.78,101.312Z" style="fill:rgb(199,21,221);"/><!-- --><path d="M743.696,185.2C737.747,184.555 731.756,184.555 725.807,185.2L722.861,201.216C717.159,202.19 711.571,203.862 706.197,206.201L696.473,193.977C691.033,196.739 685.844,200.083 680.981,203.964L685.601,219.478C681.1,223.505 677.009,228.073 673.402,233.099L659.507,227.941C656.032,233.37 653.037,239.163 650.563,245.239L661.512,256.095C659.416,262.096 657.919,268.336 657.046,274.702L642.703,277.992C642.125,284.634 642.125,291.323 642.703,297.966L657.046,301.255C657.919,307.622 659.416,313.862 661.512,319.862L650.563,330.719C653.037,336.794 656.032,342.587 659.507,348.017L673.402,342.858C677.009,347.885 681.1,352.453 685.601,356.479L680.981,371.994C685.844,375.874 691.033,379.219 696.473,381.981L706.197,369.756C711.571,372.096 717.159,373.768 722.861,374.742L725.807,390.757C731.756,391.402 737.747,391.402 743.696,390.757L746.642,374.742C752.344,373.768 757.933,372.096 763.307,369.756L773.03,381.981C778.471,379.219 783.659,375.874 788.522,371.994L783.902,356.479C788.404,352.453 792.495,347.885 796.101,342.858L809.996,348.017C813.471,342.587 816.467,336.794 818.941,330.719L807.992,319.862C810.087,313.862 811.585,307.622 812.457,301.255L826.801,297.966C827.379,291.323 827.379,284.634 826.801,277.992L812.457,274.702C811.585,268.336 810.087,262.096 807.992,256.095L818.941,245.239C816.467,239.163 813.471,233.37 809.996,227.941L796.101,233.099C792.495,228.073 788.404,223.505 783.902,219.478L788.522,203.964C783.659,200.083 778.471,196.739 773.03,193.977L763.307,206.201C757.933,203.862 752.344,202.19 746.642,201.216L743.696,185.2ZM734.752,267.326C744.96,267.326 753.248,276.58 753.248,287.979C753.248,299.377 744.96,308.631 734.752,308.631C724.543,308.631 716.255,299.377 716.255,287.979C716.255,276.58 724.543,267.326 734.752,267.326Z" style="fill:rgb(221,97,21);"/><!-- --><path d="M1104.68,419.383C1122.96,384.433 1159.51,384.433 1177.78,401.908C1196.06,419.383 1196.06,454.333 1177.78,489.284C1164.99,515.496 1132.09,541.709 1104.68,559.184C1077.27,541.709 1044.37,515.496 1031.58,489.284C1013.3,454.333 1013.3,419.383 1031.58,401.908C1049.85,384.433 1086.4,384.433 1104.68,419.383Z" style="fill:rgb(221,212,21);"/><!-- --><path d="M1418.44,147.496C1423.69,141.596 1434.21,141.596 1439.46,144.546C1444.72,147.496 1444.72,153.397 1439.46,159.298C1435.78,163.723 1426.32,168.149 1418.44,171.099C1410.56,168.149 1401.1,163.723 1397.42,159.298C1392.16,153.397 1392.16,147.496 1397.42,144.546C1402.67,141.596 1413.18,141.596 1418.44,147.496Z" style="fill:rgb(68,221,21);"/><!-- --><path d="M402.555,569.548L419.013,583.465L410.784,596.648L424.099,601.684L417.813,624.203L404.498,619.167L404.498,635.463L384.155,635.463L384.155,619.167L370.84,624.203L364.553,601.684L377.868,596.648L369.639,583.465L386.097,569.548L394.326,582.731L402.555,569.548Z" style="fill:rgb(68,221,21);"/><!-- --><path d="M1400.85,344.716L1406.84,363.44L1418.39,357.654L1416.54,370.591L1435.92,370.591L1420.24,382.163L1429.23,391.525L1416.54,393.735L1422.53,412.458L1406.84,400.887L1400.85,412.458L1394.86,400.887L1379.17,412.458L1385.16,393.735L1372.48,391.525L1381.46,382.163L1365.78,370.591L1385.16,370.591L1383.31,357.654L1394.86,363.44L1400.85,344.716Z" style="fill:rgb(21,57,221);"/><!-- --><path d="M332.482,332.234C299.894,332.234 273.475,360.685 273.475,395.78C273.475,430.852 299.915,459.326 332.482,459.326C365.071,459.326 391.489,430.876 391.489,395.78L332.482,395.78L332.482,332.234Z" style="fill:rgb(21,57,221);"/><!-- --></g><!-- --></svg><!-- --></div> <br><br> <div class="container"> <div class="btn-group"> <button class="btn btn-primary" onclick="zoom(-1);">zoom out</button> <button class="btn btn-secondary" onclick="zoom(0);">reset</button> <button class="btn btn-primary" onclick="zoom(1);">zoom in</button> </div> <div class="btn-group"> <button class="btn btn-primary" onclick="startAnimatedPan(true);">pan left</button> <button class="btn btn-secondary" onclick="resetPan();">reset</button> <button class="btn btn-primary" onclick="startAnimatedPan(false);">pan right</button> </div> </div>

In a real situation, when zoomed and managing alignment / user interaction you may need to convert svg pixels to/from screen pixels - there are 2 ways to do this ;在实际情况下,当缩放和管理对齐/用户交互时,您可能需要将 svg 像素转换为/从屏幕像素转换 - 有两种方法可以做到这一点; use a maintained/calculated screen to svg pixel ratio (faster) or use the following to convert directly to/from svg points (possibly more accurate);使用维护/计算的屏幕来 svg 像素比(更快)或使用以下直接转换为/从 svg 点(可能更准确);

/*
 * EDIT scr/screen here is the document containing the svg so the
 * following should convert pageX,pageY to svg coords
*/

    function convertCoords(x,y,toSvg){// toSvg ; true scr->svg, false svg->scr
        let pt = svg.createSVGPoint(); // svg defined elsewhere
        pt.x = x; 
        pt.y = y;    
        let screenCTM = svg.getScreenCTM() ;

        if(toSvg){
            screenCTM = screenCTM.inverse() ; 
        }
        let result =  pt.matrixTransform(screenCTM);

        return result ;    
    }        

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM