如何使用Javascript顺利平移内联SVG [英] How to pan an inline SVG smoothly with Javascript

查看:121
本文介绍了如何使用Javascript顺利平移内联SVG的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我创建的Cordova/Android应用中,我必须实现自己的缩放&内联SVG图像的平移(不允许使用库,也不适用).到目前为止,我的努力如下所示.

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. 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:

  • 我只需要一级缩放-实际上是2倍缩放.
  • 平移仅与缩放后相关
  • 宽高比预紧不重要
  • 我将SVG的所有内容包装在带有id gOuter
  • 的外部组中
  • 这使我更容易实现缩放&邮政编码.
  • I only require one level of zoom - in effect a x 2 zoom.
  • 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
  • This makes it easier for me to implement the zoom & pan code.

在这里简要介绍一下我如何实现平移

In brief here is how I have implemented panning

  • 我在包装组gOuter上使用touchmove事件来计算移动幅度
  • 将移动作为平移执行,并重新应用先前缩放操作中先前应用的scaling转换,以确保图像不会滑出屏幕.
  • I use the touchmove event on the wrapping group, gOuter to calculate the extent of the move
  • 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.

未解决的问题

  1. 仍然可以平移图片的极端边缘,并最终显示空白的空白
  2. 虽然这在手机的台式PC上很好用,但我发现平移操作并不顺利
  3. 我试图处理此问题,但我对每个鼠标移动都没有响应-Window.requestAnimationFrame处理程序中的响应-但这只是有所帮助
  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
  1. 在手持式设备上,很难平移边缘-在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);

选项1 :您可以使用此选项将svg放入可以调整大小和滚动的容器中.

option 1 : You can use this where you can put the svg in a container which can be sized and scrolled.

使用svg大小进行缩放,并使用容器滚动进行平移.

Uses svg size to zoom and container scroll to pan.

(如果需要,您可以隐藏scollbar,并且仍然可以影响scoll,或者如果愿意,可以使它们可见)

(You can hide the scollbars if you want and still effect a scoll or leave them visible if you prefer)

要使其正常工作,容器必须为display:inline-blockdisplay:block(因为display:inline您不能设置宽度或高度).

For this to work the container must be display:inline-block or display:block (because with display:inline you cannot set width or height).

选项2 :适用于任何情况,无论使用哪种容器.

option 2 : For any situation regardless of container.

使用svg视图框进行缩放和平移.

Uses svg viewbox to zoom and pan..

NB.

选项1可能会更快,因为您要做的工作更少,而将更多的工作留给了底层的本机功能.选项1也更易于编码.选项1还解决了平移的限制-您无法滚动到可用范围之外.

Option 1 may be faster as you're doing less of the work and leaving more of it to the underlying native functionality. Option 1 is also simpler to code. Option 1 also takes care of the limits of panning - you can't scroll beyond what's available.

但是,使用选项1时,如果缩放(调整大小),则可能需要等待浏览器对文档进行重排,然后才能通过设置滚动值进行对齐-直到下一次滚动时,可用的滚动限制才会更新重排-要缩放并保持对齐,您可能需要调整大小并在滚动条可用时调用requestAnimationFrame来设置滚动条.

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.

代码

使用OP svg,并在标记中添加一个视图框.

Uses the OP svg with the addition of a viewbox in the markup.

bootstrap仅用于按钮样式.

bootstrap is just for button styling.

选项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>

选项2示例((在运行此代码段时,您可能需要全页显示,然后缩小浏览器窗口以正确查看它-您可以限制容器,但这是不受限制的容器)

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 )

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>

在实际情况下,缩放和管理对齐方式/用户交互时,您可能需要将svg像素转换为屏幕像素或从屏幕像素转换为svg像素-有两种方法可以执行此操作;使用维护/计算的屏幕与svg像素比率(更快)或使用以下命令直接与svg点进行转换(可能更准确);

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 ; use a maintained/calculated screen to svg pixel ratio (faster) or use the following to convert directly to/from svg points (possibly more accurate);

/*
 * 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 ;    
    }        

这篇关于如何使用Javascript顺利平移内联SVG的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆