代码之家  ›  专栏  ›  技术社区  ›  Tareq Jami

固定字体大小的HTML画布,如何在放大/缩小时进行更改

  •  1
  • Tareq Jami  · 技术社区  · 6 年前

    我以前用HTML画布制作过笛卡尔坐标系。一个用户帮我用鼠标添加了缩放功能。

    但我有一个问题。轴号的字体大小是固定的,因此当缩小时,字体大小也会变小。

    我想要一个固定的字体大小,但数字之间的间隔是可变的。

    例如,如果放大,可以看到X轴0、1、2、3、4、5上的数字。

    但一旦你缩小,应该是0,5,10,15

    像geogebra https://www.geogebra.org/classic

    我需要建立自己的坐标系,不能在项目中使用小程序或嵌入代码。

    密码我到现在都有

    class ViewPort {
        constructor(canvas) {
          this.canvas = canvas
    
          /**
            * Point used to calculate the change of every point's position on
            * canvas after view port is zoomed and panned
            */
          this.center = this.basicCenter
    
          this.zoom = 1
    
          this.shouldPan = false
          this.prevZoomingPoint = null
        }
    
        get canvasWidth() {
          return this.canvas.getBoundingClientRect().width
        }
    
        get canvasHeight() {
          return this.canvas.getBoundingClientRect().height
        }
    
        get canvasLeft() {
          return this.canvas.getBoundingClientRect().left
        }
    
        get canvasTop() {
          return this.canvas.getBoundingClientRect().top
        }
    
        get context() {
          return this.canvas.getContext('2d')
        }
    
        get basicCenter() {
          const { canvasWidth, canvasHeight } = this
    
          const point = {
            x: canvasWidth / 2,
            y: canvasHeight / 2
          }
          return point
        }
    
        get basicWidth() {
          const width = this.canvasWidth
          return width
        }
    
        get basicHeight() {
          const height = this.canvasHeight
          return height
        }
    
        get width() {
          const { basicWidth, zoom } = this
          const width = basicWidth * zoom
          return width
        }
    
        get height() {
          const { basicHeight, zoom } = this
          const height = basicHeight * zoom
          return height
        }
    
        get movement() {
          const { width, height, basicWidth, basicHeight } = this
          const { x: cx, y: cy } = this.center
          const { x: basicCX, y: basicCY } = this.basicCenter
    
          const deltaX = cx - basicCX - ((width - basicWidth) / 2)
          const deltaY = cy - basicCY - ((height - basicHeight) / 2)
          const res = {
            x: deltaX,
            y: deltaY
          }
    
          return res
        }
    
        get pan() {
          const { center, zoom, basicCenter } = this
          const res = {
            x: center.x - basicCenter.x,
            y: center.y - basicCenter.y
          }
          return res
        }
    
        zoomBy(center, deltaZoom) {
          const prevZoom = this.zoom
    
          this.zoom = this.zoom + deltaZoom
    
          this.center = this.zoomPoint(center, this.zoom / prevZoom, this.center)
        }
    
        zoomIn(point) {
          this.zoomBy(point, 0.1)
        }
    
        zoomOut(point) {
          this.zoom > 0.25 && this.zoomBy(point, -0.1)
        }
    
        zoomPoint(center, rate, point) {
          const { x: cx, y: cy } = center
          const { x, y } = point
    
          const deltaX = (x - cx) * rate
          const deltaY = (y - cy) * rate
    
          const newPoint = {
            x: cx + deltaX,
            y: cy + deltaY
          }
          return newPoint
        }
    
        panBy(deltaX, deltaY) {
          const { x: centerX, y: centerY } = this.center
          this.center = {
            x: centerX + deltaX,
            y: centerY + deltaY
          }
        }
    
        getDeltaPointToPrevPanningPoint(point) {
          const { x, y } = point
          const { x: prevX, y: prevY } = this.prevZoomingPoint
    
          const deltaPoint = {
            x: x - prevX,
            y: y - prevY
          }
          return deltaPoint
        }
    
    
        startPan(event) {
          const point = {
            x: event.x - this.canvasLeft,
            y: event.y - this.canvasTop,
          }
    
          this.shouldPan = true
    
          this.prevZoomingPoint = point
        }
    
        panning(event) {
          const point = {
            x: event.x - this.canvasLeft,
            y: event.y - this.canvasTop,
          }
    
          const deltaX = this.getDeltaPointToPrevPanningPoint(point).x
          const deltaY = this.getDeltaPointToPrevPanningPoint(point).y
    
          this.prevZoomingPoint = point
    
          this.panBy(deltaX, deltaY)
        }
    
        stopPan() {
          this.shouldPan = false
        }
    
        transformToInitial(point) {
          const { x, y } = point
          const { movement, zoom } = this
          const res = {
            x: (x - movement.x) / zoom,
            y: (y - movement.y) / zoom
          }
          return res
        }
    
        transform(point) {
          const { x, y } = point
          const { movement, zoom } = this
          const res = {
            x: x * zoom + movement.x,
            y: y * zoom + movement.y
          }
          return res
        }
    
        clearCanvas() {
          this.context.setTransform(1, 0, 0, 1, 0, 0)
          this.context.clearRect(
            0,
            0,
            viewPort.canvasWidth,
            viewPort.canvasHeight
          )
        }
      }
    
      class Interaction {
        constructor({
          canvas,
          viewPort,
          dragger
        }) {
    
          canvas.removeEventListener("mousewheel", mousewheelListener)
          canvas.addEventListener("mousewheel", mousewheelListener)
    
          canvas.removeEventListener("mousedown", mousedownListener)
          canvas.addEventListener("mousedown", mousedownListener)
    
          canvas.removeEventListener("mousemove", mousemoveListener)
          canvas.addEventListener("mousemove", mousemoveListener)
    
          canvas.removeEventListener("mouseup", mouseupListener)
          canvas.addEventListener("mouseup", mouseupListener)
    
    
          function mousewheelListener(event) {
            event.preventDefault()
    
            const point = {
              x: event.x - canvas.getBoundingClientRect().left,
              y: event.y - canvas.getBoundingClientRect().top,
            }
    
            const { deltaX, deltaY } = event
    
            if (isDecreasing()) {
              viewPort.zoomIn(point)
            }
    
            if (isIncreasing()) {
              viewPort.zoomOut(point)
            }
    
            function isIncreasing() {
              const res = deltaX > 0 || deltaY > 0
              return res
            }
            function isDecreasing() {
              const res = deltaX < 0 || deltaY < 0
              return res
            }
    
            render()
    
          }
    
    
          function mousedownListener(event) {
            viewPort.startPan(event)
          }
    
          function mousemoveListener(event) {
            viewPort.shouldPan && viewPort.panning(event)
            viewPort.shouldPan && render()
          }
    
          function mouseupListener(event) {
            viewPort.stopPan(event)
          }
        }
    
      }
      const canvas = document.getElementById("myCanvas")
      const viewPort = new ViewPort(canvas)
      const interaction = new Interaction({ viewPort, canvas })
    
      function render() {
        const { abs, max } = Math
        const { zoom, movement, context: ctx, pan, center, basicCenter } = viewPort
    
        viewPort.clearCanvas()
        ctx.setTransform(zoom, 0, 0, zoom, movement.x, movement.y)
    
    
        // Original codes are rewrote
        const { canvasWidth, canvasHeight } = viewPort
    
        const interval = 20
        const basicWidth = canvasWidth
        const basicHeight = canvasHeight
    
        const potentialWidth = 2 * max(abs(viewPort.transformToInitial({ x: 0, y: 0 }).x - basicCenter.x), abs(viewPort.transformToInitial({ x: basicWidth, y: 0 }).x - basicCenter.x))
        const width = potentialWidth > basicWidth ? potentialWidth : basicWidth
    
        const potentialHeight = 2 * max(abs(viewPort.transformToInitial({ x: 0, y: 0 }).y - basicCenter.y), abs(viewPort.transformToInitial({ x: 0, y: basicHeight }).y - basicCenter.y))
        const height = potentialHeight > basicHeight ? potentialHeight : basicHeight
    
        drawXAxis()
        drawYAxis()
        drawOriginCoordinate()
        drawXCoordinates()
        drawYCoordinates()
    
        function drawXAxis() {
          const path = new Path2D
    
          path.moveTo(basicCenter.x - width / 2, basicHeight / 2)
          path.lineTo(basicCenter.x + width / 2, basicHeight / 2)
    
          ctx.stroke(path)
        }
    
        function drawYAxis() {
          const path = new Path2D
          path.moveTo(basicWidth / 2, basicCenter.y - height / 2)
          path.lineTo(basicWidth / 2, basicCenter.y + height / 2)
    
          ctx.stroke(path)
        }
    
        function drawOriginCoordinate() {
          ctx.fillText(`O`, basicCenter.x + 5, basicCenter.y - 5)
        }
    
        function drawXCoordinates() {
          for (let i = 1; i <= width / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` ${i} `, basicCenter.x + total, basicHeight / 2)
          }
    
          for (let i = 1; i <= width / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` -${i} `, basicCenter.x - total, basicHeight / 2)
          }
        }
    
        function drawYCoordinates() {
          for (let i = 1; i <= height / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` ${i} `, basicWidth / 2, basicCenter.y + total)
          }
    
          for (let i = 1; i <= height / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` -${i} `, basicWidth / 2, basicCenter.y - total)
          }
        }
      }
    
      render()
    <canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;"></canvas>
    1 回复  |  直到 6 年前
        1
  •  0
  •   Andrew Reid    6 年前

    字体大小

    对于字体大小,您需要与画布的缩放值成反比的字体大小。说:

    ctx.font = 12 / zoom + "px Arial";
    

    其中12是缩放时的字体大小( zoom )是1。如果你放大,那么所有的东西都是拉伸的两倍,( zoom = 2 ,字体大小为6。因为字体大小是线性的,而不是面积测量,所以我们不需要在这里平方缩放。

    更新轴

    为了更新显示的数字以便适当地缩放,可以使用一些不同的方法。

    作为一个简单的例子,我们可以找出变焦的数量级(或者基本上是多少个数字,或者有多少个小数点),并根据这个系数缩放显示的数字。例如,如果缩放为10,那么我们将以1/10的增量显示轴号。如果缩放为0.1,那么我们将以1/0.1或10的增量显示轴号。

    首先,让我们找出变焦的大小顺序:

    const orderMagnitude = Math.pow(10,Math.floor(Math.log(zoom) / Math.LN10));
    

    缩放值为1(起始值)将产生0的数量级。缩放值为10将产生1个数量级。

    现在,我们可以将这个数量级转换为以10为底的整数:

    const every = 1 / Math.pow(10,orderMagnitude);
    

    这里我们取一个数量级,例如1,并将其转换为1/10,1/10将是轴上显示的增量(您已经使用了变量名 increment ,所以我叫它 every ,因为它每隔一段时间代表一个轴刻度)。间隔1/10个单位的刻度间距是合适的,因为数量级表示10倍的缩放。

    现在,我们需要将此应用于代码中的几个点:

    const inverval = 20 * every;  // scale the interval to reflect the density of ticks
    

    当然,当您设置轴时,例如:

     for (let i = 1; i <= width / 2 / interval; i++) {
        total = i * interval
        ctx.fillText(` ${i*every} `, basicCenter.x + total, basicHeight / 2)
      }
    

    以下是迄今为止的一个例子(缩小显示的速度更快):

    class ViewPort {
        constructor(canvas) {
          this.canvas = canvas
    
          /**
            * Point used to calculate the change of every point's position on
            * canvas after view port is zoomed and panned
            */
          this.center = this.basicCenter
    
          this.zoom = 1
    
          this.shouldPan = false
          this.prevZoomingPoint = null
        }
    
        get canvasWidth() {
          return this.canvas.getBoundingClientRect().width
        }
    
        get canvasHeight() {
          return this.canvas.getBoundingClientRect().height
        }
    
        get canvasLeft() {
          return this.canvas.getBoundingClientRect().left
        }
    
        get canvasTop() {
          return this.canvas.getBoundingClientRect().top
        }
    
        get context() {
          return this.canvas.getContext('2d')
        }
    
        get basicCenter() {
          const { canvasWidth, canvasHeight } = this
    
          const point = {
            x: canvasWidth / 2,
            y: canvasHeight / 2
          }
          return point
        }
    
        get basicWidth() {
          const width = this.canvasWidth
          return width
        }
    
        get basicHeight() {
          const height = this.canvasHeight
          return height
        }
    
        get width() {
          const { basicWidth, zoom } = this
          const width = basicWidth * zoom
          return width
        }
    
        get height() {
          const { basicHeight, zoom } = this
          const height = basicHeight * zoom
          return height
        }
    
        get movement() {
          const { width, height, basicWidth, basicHeight } = this
          const { x: cx, y: cy } = this.center
          const { x: basicCX, y: basicCY } = this.basicCenter
    
          const deltaX = cx - basicCX - ((width - basicWidth) / 2)
          const deltaY = cy - basicCY - ((height - basicHeight) / 2)
          const res = {
            x: deltaX,
            y: deltaY
          }
    
          return res
        }
    
        get pan() {
          const { center, zoom, basicCenter } = this
          const res = {
            x: center.x - basicCenter.x,
            y: center.y - basicCenter.y
          }
          return res
        }
    
        zoomBy(center, deltaZoom) {
          const prevZoom = this.zoom
    
          this.zoom = this.zoom + deltaZoom
    
          this.center = this.zoomPoint(center, this.zoom / prevZoom, this.center)
        }
    
        zoomIn(point) {
          this.zoomBy(point, 0.1)
        }
    
        zoomOut(point) {
          this.zoom > 0.25 && this.zoomBy(point, -0.1)
        }
    
        zoomPoint(center, rate, point) {
          const { x: cx, y: cy } = center
          const { x, y } = point
    
          const deltaX = (x - cx) * rate
          const deltaY = (y - cy) * rate
    
          const newPoint = {
            x: cx + deltaX,
            y: cy + deltaY
          }
          return newPoint
        }
    
        panBy(deltaX, deltaY) {
          const { x: centerX, y: centerY } = this.center
          this.center = {
            x: centerX + deltaX,
            y: centerY + deltaY
          }
        }
    
        getDeltaPointToPrevPanningPoint(point) {
          const { x, y } = point
          const { x: prevX, y: prevY } = this.prevZoomingPoint
    
          const deltaPoint = {
            x: x - prevX,
            y: y - prevY
          }
          return deltaPoint
        }
    
    
        startPan(event) {
          const point = {
            x: event.x - this.canvasLeft,
            y: event.y - this.canvasTop,
          }
    
          this.shouldPan = true
    
          this.prevZoomingPoint = point
        }
    
        panning(event) {
          const point = {
            x: event.x - this.canvasLeft,
            y: event.y - this.canvasTop,
          }
    
          const deltaX = this.getDeltaPointToPrevPanningPoint(point).x
          const deltaY = this.getDeltaPointToPrevPanningPoint(point).y
    
          this.prevZoomingPoint = point
    
          this.panBy(deltaX, deltaY)
        }
    
        stopPan() {
          this.shouldPan = false
        }
    
        transformToInitial(point) {
          const { x, y } = point
          const { movement, zoom } = this
          const res = {
            x: (x - movement.x) / zoom,
            y: (y - movement.y) / zoom
          }
          return res
        }
    
        transform(point) {
          const { x, y } = point
          const { movement, zoom } = this
          const res = {
            x: x * zoom + movement.x,
            y: y * zoom + movement.y
          }
          return res
        }
    
        clearCanvas() {
          this.context.setTransform(1, 0, 0, 1, 0, 0)
          this.context.clearRect(
            0,
            0,
            viewPort.canvasWidth,
            viewPort.canvasHeight
          )
        }
      }
    
      class Interaction {
        constructor({
          canvas,
          viewPort,
          dragger
        }) {
    
          canvas.removeEventListener("mousewheel", mousewheelListener)
          canvas.addEventListener("mousewheel", mousewheelListener)
    
          canvas.removeEventListener("mousedown", mousedownListener)
          canvas.addEventListener("mousedown", mousedownListener)
    
          canvas.removeEventListener("mousemove", mousemoveListener)
          canvas.addEventListener("mousemove", mousemoveListener)
    
          canvas.removeEventListener("mouseup", mouseupListener)
          canvas.addEventListener("mouseup", mouseupListener)
    
    
          function mousewheelListener(event) {
            event.preventDefault()
    
            const point = {
              x: event.x - canvas.getBoundingClientRect().left,
              y: event.y - canvas.getBoundingClientRect().top,
            }
    
            const { deltaX, deltaY } = event
    
            if (isDecreasing()) {
              viewPort.zoomIn(point)
            }
    
            if (isIncreasing()) {
              viewPort.zoomOut(point)
            }
    
            function isIncreasing() {
              const res = deltaX > 0 || deltaY > 0
              return res
            }
            function isDecreasing() {
              const res = deltaX < 0 || deltaY < 0
              return res
            }
    
            render()
    
          }
    
    
          function mousedownListener(event) {
            viewPort.startPan(event)
          }
    
          function mousemoveListener(event) {
            viewPort.shouldPan && viewPort.panning(event)
            viewPort.shouldPan && render()
          }
    
          function mouseupListener(event) {
            viewPort.stopPan(event)
          }
        }
    
      }
      const canvas = document.getElementById("myCanvas")
      const viewPort = new ViewPort(canvas)
      const interaction = new Interaction({ viewPort, canvas })
    
      function render() {
        const { abs, max } = Math
        const { zoom, movement, context: ctx, pan, center, basicCenter } = viewPort
    
        viewPort.clearCanvas()
        ctx.setTransform(zoom, 0, 0, zoom, movement.x, movement.y)
    
    	
    	// modify font based on zoom:
    	ctx.font = 12 / zoom + "px Arial";
    	// modify number interval based on zoom:
    	const orderMagnitude = Math.floor(Math.log(zoom) / Math.LN10);
    	const every = 1 / Math.pow(10,orderMagnitude);
    
        // Original codes are rewrote
        const { canvasWidth, canvasHeight } = viewPort
    
        const interval = 20 * every; 
        const basicWidth = canvasWidth
        const basicHeight = canvasHeight
    
        const potentialWidth = 2 * max(abs(viewPort.transformToInitial({ x: 0, y: 0 }).x - basicCenter.x), abs(viewPort.transformToInitial({ x: basicWidth, y: 0 }).x - basicCenter.x))
        const width = potentialWidth > basicWidth ? potentialWidth : basicWidth
    
        const potentialHeight = 2 * max(abs(viewPort.transformToInitial({ x: 0, y: 0 }).y - basicCenter.y), abs(viewPort.transformToInitial({ x: 0, y: basicHeight }).y - basicCenter.y))
        const height = potentialHeight > basicHeight ? potentialHeight : basicHeight
    
        drawXAxis()
        drawYAxis()
        drawOriginCoordinate()
        drawXCoordinates()
        drawYCoordinates()
    
        function drawXAxis() {
          const path = new Path2D
    
          path.moveTo(basicCenter.x - width / 2, basicHeight / 2)
          path.lineTo(basicCenter.x + width / 2, basicHeight / 2)
    
          ctx.stroke(path)
        }
    
        function drawYAxis() {
          const path = new Path2D
          path.moveTo(basicWidth / 2, basicCenter.y - height / 2)
          path.lineTo(basicWidth / 2, basicCenter.y + height / 2)
    
          ctx.stroke(path)
        }
    
        function drawOriginCoordinate() {
          ctx.fillText(`O`, basicCenter.x + 5, basicCenter.y - 5)
        }
    
        function drawXCoordinates() {
    	  for (let i = 1; i <= width / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` ${i*every} `, basicCenter.x + total, basicHeight / 2)
          }
    
          for (let i = 1; i <= width / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` -${i*every} `, basicCenter.x - total, basicHeight / 2)
          }
        }
    
        function drawYCoordinates() {
          for (let i = 1; i <= height / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` ${i*every} `, basicWidth / 2, basicCenter.y + total)
          }
    
          for (let i = 1; i <= height / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` -${i*every} `, basicWidth / 2, basicCenter.y - total)
          }
        }
      }
    
      render()
    <canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;"></canvas>

    精炼轴心

    这是正常的,但滴答的阈值为 zoom = 1 位置不理想。也许我们可以通过偏移输入值来稍微修改计算出的数量级:

    const orderMagnitude = Math.pow(10,Math.floor(Math.log(zoom*1.5) / Math.LN10));
    

    这将为不同轴的刻度生成一个间隔稍好的阈值。

    进一步细化

    而不是让每个勾号都从原点开始 1 x 10^n 我们可以使用2或5作为中间值,因为只有在缩放比例变化10倍时才重置刻度是最不理想的。

    一种可能的解决方案是,当缩放比例因子相对于给定数量级增加时,我们会减小刻度之间的间隔(减小 每一个 ):

    // Modify how every often we want to show an axis tick:
    var every;
    if (zoom/Math.pow(10,orderMagnitude) > 4) {
        every = 1 / Math.pow(10,orderMagnitude) * 0.2;
    }
    else if (zoom/Math.pow(10,orderMagnitude) > 2) {
        every = 1 / Math.pow(10,orderMagnitude) * 0.5;
    }
    else {
        every = 1 / Math.pow(10,orderMagnitude);
    }
    

    这给了我们:

    class ViewPort {
        constructor(canvas) {
          this.canvas = canvas
    
          /**
            * Point used to calculate the change of every point's position on
            * canvas after view port is zoomed and panned
            */
          this.center = this.basicCenter
    
          this.zoom = 1
    
          this.shouldPan = false
          this.prevZoomingPoint = null
        }
    
        get canvasWidth() {
          return this.canvas.getBoundingClientRect().width
        }
    
        get canvasHeight() {
          return this.canvas.getBoundingClientRect().height
        }
    
        get canvasLeft() {
          return this.canvas.getBoundingClientRect().left
        }
    
        get canvasTop() {
          return this.canvas.getBoundingClientRect().top
        }
    
        get context() {
          return this.canvas.getContext('2d')
        }
    
        get basicCenter() {
          const { canvasWidth, canvasHeight } = this
    
          const point = {
            x: canvasWidth / 2,
            y: canvasHeight / 2
          }
          return point
        }
    
        get basicWidth() {
          const width = this.canvasWidth
          return width
        }
    
        get basicHeight() {
          const height = this.canvasHeight
          return height
        }
    
        get width() {
          const { basicWidth, zoom } = this
          const width = basicWidth * zoom
          return width
        }
    
        get height() {
          const { basicHeight, zoom } = this
          const height = basicHeight * zoom
          return height
        }
    
        get movement() {
          const { width, height, basicWidth, basicHeight } = this
          const { x: cx, y: cy } = this.center
          const { x: basicCX, y: basicCY } = this.basicCenter
    
          const deltaX = cx - basicCX - ((width - basicWidth) / 2)
          const deltaY = cy - basicCY - ((height - basicHeight) / 2)
          const res = {
            x: deltaX,
            y: deltaY
          }
    
          return res
        }
    
        get pan() {
          const { center, zoom, basicCenter } = this
          const res = {
            x: center.x - basicCenter.x,
            y: center.y - basicCenter.y
          }
          return res
        }
    
        zoomBy(center, deltaZoom) {
          const prevZoom = this.zoom
    
          this.zoom = this.zoom + deltaZoom
    
          this.center = this.zoomPoint(center, this.zoom / prevZoom, this.center)
        }
    
        zoomIn(point) {
          this.zoomBy(point, 0.1)
        }
    
        zoomOut(point) {
          this.zoom > 0.25 && this.zoomBy(point, -0.1)
        }
    
        zoomPoint(center, rate, point) {
          const { x: cx, y: cy } = center
          const { x, y } = point
    
          const deltaX = (x - cx) * rate
          const deltaY = (y - cy) * rate
    
          const newPoint = {
            x: cx + deltaX,
            y: cy + deltaY
          }
          return newPoint
        }
    
        panBy(deltaX, deltaY) {
          const { x: centerX, y: centerY } = this.center
          this.center = {
            x: centerX + deltaX,
            y: centerY + deltaY
          }
        }
    
        getDeltaPointToPrevPanningPoint(point) {
          const { x, y } = point
          const { x: prevX, y: prevY } = this.prevZoomingPoint
    
          const deltaPoint = {
            x: x - prevX,
            y: y - prevY
          }
          return deltaPoint
        }
    
    
        startPan(event) {
          const point = {
            x: event.x - this.canvasLeft,
            y: event.y - this.canvasTop,
          }
    
          this.shouldPan = true
    
          this.prevZoomingPoint = point
        }
    
        panning(event) {
          const point = {
            x: event.x - this.canvasLeft,
            y: event.y - this.canvasTop,
          }
    
          const deltaX = this.getDeltaPointToPrevPanningPoint(point).x
          const deltaY = this.getDeltaPointToPrevPanningPoint(point).y
    
          this.prevZoomingPoint = point
    
          this.panBy(deltaX, deltaY)
        }
    
        stopPan() {
          this.shouldPan = false
        }
    
        transformToInitial(point) {
          const { x, y } = point
          const { movement, zoom } = this
          const res = {
            x: (x - movement.x) / zoom,
            y: (y - movement.y) / zoom
          }
          return res
        }
    
        transform(point) {
          const { x, y } = point
          const { movement, zoom } = this
          const res = {
            x: x * zoom + movement.x,
            y: y * zoom + movement.y
          }
          return res
        }
    
        clearCanvas() {
          this.context.setTransform(1, 0, 0, 1, 0, 0)
          this.context.clearRect(
            0,
            0,
            viewPort.canvasWidth,
            viewPort.canvasHeight
          )
        }
      }
    
      class Interaction {
        constructor({
          canvas,
          viewPort,
          dragger
        }) {
    
          canvas.removeEventListener("mousewheel", mousewheelListener)
          canvas.addEventListener("mousewheel", mousewheelListener)
    
          canvas.removeEventListener("mousedown", mousedownListener)
          canvas.addEventListener("mousedown", mousedownListener)
    
          canvas.removeEventListener("mousemove", mousemoveListener)
          canvas.addEventListener("mousemove", mousemoveListener)
    
          canvas.removeEventListener("mouseup", mouseupListener)
          canvas.addEventListener("mouseup", mouseupListener)
    
    
          function mousewheelListener(event) {
            event.preventDefault()
    
            const point = {
              x: event.x - canvas.getBoundingClientRect().left,
              y: event.y - canvas.getBoundingClientRect().top,
            }
    
            const { deltaX, deltaY } = event
    
            if (isDecreasing()) {
              viewPort.zoomIn(point)
            }
    
            if (isIncreasing()) {
              viewPort.zoomOut(point)
            }
    
            function isIncreasing() {
              const res = deltaX > 0 || deltaY > 0
              return res
            }
            function isDecreasing() {
              const res = deltaX < 0 || deltaY < 0
              return res
            }
    
            render()
    
          }
    
    
          function mousedownListener(event) {
            viewPort.startPan(event)
          }
    
          function mousemoveListener(event) {
            viewPort.shouldPan && viewPort.panning(event)
            viewPort.shouldPan && render()
          }
    
          function mouseupListener(event) {
            viewPort.stopPan(event)
          }
        }
    
      }
      const canvas = document.getElementById("myCanvas")
      const viewPort = new ViewPort(canvas)
      const interaction = new Interaction({ viewPort, canvas })
    
      function render() {
        const { abs, max } = Math
        const { zoom, movement, context: ctx, pan, center, basicCenter } = viewPort
    
        viewPort.clearCanvas()
        ctx.setTransform(zoom, 0, 0, zoom, movement.x, movement.y)
    
    	
    	// modify font based on zoom:
    	ctx.font = 12 / zoom + "px Arial";
    	// modify number interval based on zoom:
    	const orderMagnitude = Math.floor(Math.log(zoom*1.5) / Math.LN10);
    	
    	// Modify how every often we want to show an axis tick:
    	var every;
    	if (zoom/Math.pow(10,orderMagnitude) > 4) {
    		every = 1 / Math.pow(10,orderMagnitude) * 0.2;
    	}
    	else if (zoom/Math.pow(10,orderMagnitude) > 2) {
    		every = 1 / Math.pow(10,orderMagnitude) * 0.5;
    	}
    	else {
    		every = 1 / Math.pow(10,orderMagnitude);
    	}
    	
        // Original codes are rewrote
        const { canvasWidth, canvasHeight } = viewPort
    
        const interval = 30 * every; 
        const basicWidth = canvasWidth
        const basicHeight = canvasHeight
    
        const potentialWidth = 2 * max(abs(viewPort.transformToInitial({ x: 0, y: 0 }).x - basicCenter.x), abs(viewPort.transformToInitial({ x: basicWidth, y: 0 }).x - basicCenter.x))
        const width = potentialWidth > basicWidth ? potentialWidth : basicWidth
    
        const potentialHeight = 2 * max(abs(viewPort.transformToInitial({ x: 0, y: 0 }).y - basicCenter.y), abs(viewPort.transformToInitial({ x: 0, y: basicHeight }).y - basicCenter.y))
        const height = potentialHeight > basicHeight ? potentialHeight : basicHeight
    
        drawXAxis()
        drawYAxis()
        drawOriginCoordinate()
        drawXCoordinates()
        drawYCoordinates()
    
        function drawXAxis() {
          const path = new Path2D
    
          path.moveTo(basicCenter.x - width / 2, basicHeight / 2)
          path.lineTo(basicCenter.x + width / 2, basicHeight / 2)
    
          ctx.stroke(path)
        }
    
        function drawYAxis() {
          const path = new Path2D
          path.moveTo(basicWidth / 2, basicCenter.y - height / 2)
          path.lineTo(basicWidth / 2, basicCenter.y + height / 2)
    
          ctx.stroke(path)
        }
    
        function drawOriginCoordinate() {
          ctx.fillText(`O`, basicCenter.x + 5, basicCenter.y - 5)
        }
    
        function drawXCoordinates() {
    	  for (let i = 1; i <= width / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` ${i*every} `, basicCenter.x + total, basicHeight / 2)
          }
    
          for (let i = 1; i <= width / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` -${i*every} `, basicCenter.x - total, basicHeight / 2)
          }
        }
    
        function drawYCoordinates() {
          for (let i = 1; i <= height / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` ${i*every} `, basicWidth / 2, basicCenter.y + total)
          }
    	  
    	  for (let i = 1; i <= height / 2 / interval; i++) {
            total = i * interval
            ctx.fillText(` -${i*every} `, basicWidth / 2, basicCenter.y - total)
          }
        }
    	
    	
    
    	
      }
    
      render()
    <canvas id=“mycanvas”width=“300”height=“300”style=“border:1px solid d3d3;”></canvas>

    进一步改进

    我没有涉及数字格式,但放大时可以看到一些浮点问题。此外,轴的条形宽度随着放大和缩小而增大,这会影响文本的位置。