SVG鼠标漫游

鼠标漫游

 鼠标漫游就是通过移动光标和滚轮,完成画布缩放、移动的交互过程。

svg 绘图使用原点在左上角的坐标系统,一个单位代表一像素。这里的像素不能简单理解为屏幕像素,是一个用户单位。svg 的 width 和 height 属性决定图像在用户系统的占位。viewBox 属性则决定占位视口映射到 svg 图像范围的映射。

调整 viewBox 即可完成图像内容的缩放和移动:

  • x 增加,视口向右移动,图像内容看起来向左移动
  • y 增加,视口向下移动,图像内容看起来向上移动
  • viewWidth 和 viewHeight 增加,视口变大,图像内容看起来缩小



    
        
        
        鼠标漫游
    

    
        

SvgRoam

JAVASCRIPT

(() => {
  const svg = document.querySelector('svg');
  if (!svg) {
    return;
  }
  const width = Number(svg.getAttribute('width'));
  const height = Number(svg.getAttribute('height'));
  const diagonal = Math.sqrt(width * width + height * height);
  const [x, y, viewWidth, viewHeight] = svg.getAttribute('viewBox')
    .split(' ')
  .map(item => Number(item));
  let currentDiagonal = Math.sqrt(viewWidth * viewWidth + viewHeight * viewHeight);

  let draggingContext: {
    point: [number, number];
    viewBox: string;
  }|null = null;
  svg.addEventListener('wheel', e => {
    e.preventDefault();
    if (e.deltaY > 0) {
      if (currentDiagonal / diagonal >= 5) {
        return;
      }
      currentDiagonal = currentDiagonal + 0.01 * diagonal;
    }
    else if (e.deltaY < 0) {
      if (currentDiagonal / diagonal <= .1) {
        return;
      }
      currentDiagonal = currentDiagonal - 0.01 * diagonal;
    }
    const {offsetX, offsetY} = e;
    const [strX, strY, strW, strH] = svg.getAttribute('viewBox')?.split(' ') ?? [];
    const w = currentDiagonal * width / diagonal;
    const h = currentDiagonal * height / diagonal;
    const x = Number(strX) + offsetX * (Number(strW) - w) / width;
    const y = Number(strY) + offsetY * (Number(strH) - h) / height;
    svg.setAttribute('viewBox', `${x} ${y} ${w} ${h}`);
    if (draggingContext) {
      draggingContext = {
        point: [e.offsetX, e.offsetY],
        viewBox: `${x} ${y} ${w} ${h}`,
      };
    }
  });
  // 拖动
  svg.addEventListener('mousedown', e => {
    draggingContext = {
      point: [e.offsetX, e.offsetY],
      viewBox: svg.getAttribute('viewBox') as string,
    };
  });
  window.addEventListener('mouseup', () => {
    draggingContext = null;
  });
  svg.addEventListener('mousemove', e => {
    if (!draggingContext) {
      return;
    }
    const offset = [e.offsetX - draggingContext.point[0], e.offsetY - draggingContext.point[1]];
    const [strX, strY, strW, strH] = draggingContext.viewBox.split(' ');
    const realOffet = [
      offset[0] * Number(strW) / width,
      offset[1] * Number(strH) / height,
    ];
    const x = Number(strX) - realOffet[0];
    const y = Number(strY) - realOffet[1];
    svg.setAttribute('viewBox', `${x} ${y} ${strW} ${strH}`);
  });
})();

TYPESCRIPT

interface ViewInfo {
    scale: number;
    x: number;
    y: number;
}

abstract class Roam {
  constructor(el: HTMLElement|SVGSVGElement) {
    this.el = el;
    this.scaleHandler();
    this.dragHandler();
  }
  
  abstract init(): void;
  
  el: HTMLElement|SVGSVGElement;
  
  draggingContext: ({point: [number, number]}&ViewInfo)|null = null;
  
  currentScale = 1;
  
  client2offset(clientX: number, clientY: number) {
    const { x, y } = this.el.getBoundingClientRect();
      return {
        offsetX: clientX - x,
        offsetY: clientY - y,
    };
  }
  
  scaleHandler(step = 0.01, minSide = .1, maxSide = 5) {
    this.el.addEventListener('wheel', e => {
      e.preventDefault();
      const event = e as WheelEvent;
      if (event.deltaY < 0) {
        if (this.currentScale >= maxSide) {
          return;
        }
        this.currentScale = this.currentScale + step;
      }
      else if (event.deltaY > 0) {
        if (this.currentScale <= minSide) {
          return;
        }
        this.currentScale = this.currentScale - step;
      }
      const { offsetX, offsetY } = this.client2offset(event.clientX, event.clientY);
      const { x: oldX, y: oldY, scale: oldScale } = this.getViewInfo();
      const x = oldX + offsetX * (oldScale - this.currentScale);
      const y = oldY + offsetY * (oldScale - this.currentScale);
      const viewInfo = {
        x,
        y,
        scale: this.currentScale,
      };
      this.setViewInfo(viewInfo);
      if (this.draggingContext) {
        this.draggingContext = {
          point: [offsetX, offsetY],
          ...viewInfo,
        };
      }
    });
  }
  
  dragHandler() {
    this.el.addEventListener('mousedown', e => {
      // 防止子元素冒泡导致的 offsetX 参照对象变化
      const { clientX, clientY } = e as WheelEvent;
      const {offsetX, offsetY} = this.client2offset(clientX, clientY);

      this.draggingContext = {
        point: [offsetX, offsetY],
        ...this.getViewInfo(),
      };
    });
    window.addEventListener('mouseup', () => {
      this.draggingContext = null;
    });
    this.el.addEventListener('mousemove', e => {
      if (!this.draggingContext) {
        return;
      }
      const { clientX, clientY } = e as WheelEvent;
      const {offsetX, offsetY} = this.client2offset(clientX, clientY);

      const offset = [offsetX - this.draggingContext.point[0], offsetY - this.draggingContext.point[1]];
      const { x: oldX, y: oldY, scale: oldScale } = this.draggingContext;
      const realOffet = [
        offset[0] * Number(oldScale),
        offset[1] * Number(oldScale),
      ];
      this.setViewInfo({
        x: oldX - realOffet[0],
        y: oldY - realOffet[1],
        scale: this.currentScale,
      });
    });
  }
  
  abstract getViewInfo(): ViewInfo;
  
  abstract setViewInfo(params: ViewInfo): void;
}

class SvgRoam extends Roam {
  constructor(el: SVGSVGElement) {
    super(el);
    this.init();
  }
  realWidth = 0;
  realHeight = 0;
  init() {
    this.realWidth = Number(this.el.getAttribute('width'));
    this.realHeight = Number(this.el.getAttribute('height'));
    this.currentScale = this.getViewInfo().scale;
  }
  getViewInfo() {
    const [strX, strY, strW] = this.el.getAttribute('viewBox')?.split(' ') ?? [];
    return {
      x: Number(strX),
      y: Number(strY),
      scale: Number(strW) / this.realWidth,
    };
  }
  setViewInfo({ x, y, scale }: { x: number; y: number; scale: number; }) {
    this.el.setAttribute('viewBox', `${x} ${y} ${scale * this.realWidth} ${scale * this.realHeight}`);
  }
}
const svg = document.querySelector('svg');
if (svg) {
  new SvgRoam(svg);
}

TYPESCRIPT  => JAVASCRIPT

1. 安装node.js

2. 安装typescript依赖包

   npm install -g typescript

3.  转换ts文件为js文件 

   按住shift键,然后鼠标右键,选择在此处打开Powershell 窗口  输入 tsc xxx.ts  将typescript 转为 javascript

 

你可能感兴趣的:(前端,javascript,开发语言)