SVG地图(SVG节点元素的平移和缩放)

555 阅读3分钟

平移可以使用绝对定位的方法实现;缩放则可以通过设置SVG中的width和height属性实现。 其他dom节点也可以参照本思路实现。

效果展示

视频剪辑_爱给网_aigei_com.gif

一、类介绍

这个类需要两个参数:节点的容器和要操作的SVG节点。构造方法如下:

class SvgMap {
    constructor(screen, element) {
        this.screen = screen;
        this.element = element;
    }
}

二、平移

平移操作可以通过监听鼠标的onmousedown、onmousemove、onmouseup事件实现。 鼠标移动的位置就是SVG需要移动的位置(即绝对定位移动的位置)。

//平移控制对象
this.pan = {
    isPan: false,//平移的标志位
    top: 0,//开始时SVG的绝对定位top值
    left: 0,//开始时SVG的绝对定位left值
    startX: null,//开始时鼠标的横坐标位置
    startY: null,//开始时鼠标的纵坐标位置
}

this.screen.onmousedown = (e) => {
    if(e.button === 0) {
        //平移开始方法
    }
}

this.screen.onmousemove = (e) => {
    if(this.pan.isPan) {
        //平移拖动方法
    }
}

this.screen.onmouseup = (e) => {
    if(e.button === 0) {
        //平移结束方法
    }
}

1、平移开始方法

开始平移时,需要设置平移操作的状态,并记录开始时SVG的绝对定位值和鼠标的横纵坐标。

panStart(e) {
    this.pan.isPan = true;
    [this.pan.left, this.pan.top] = this.getAbsolute();//getAbsolute是获取SVG的绝对定位值的方法
    [this.pan.startX, this.pan.startY] = this.getMouseXY(e);//getMouseXY是获取鼠标横纵坐标的方法
}

2、平移结束方法

结束方法很简单,只要修改平移标志位即可。

panEnd(e) {
    this.pan.isPan = false;
}

3、平移拖拽方法

用当前鼠标的位置和开始时记录的鼠标位置即可计算鼠标的移动的值。然后将SVG绝对定位修改同样的值即可。

panMove(e) {
    const [x, y] = this.getMouseXY(e);//getMouseXY是获取鼠标横纵坐标的方法
    this.panTo(this.pan.left + (x - this.pan.startX), this.pan.top + (y - this.pan.startY));//panTo是修改SVG绝对定位值的方法
}

三、缩放

缩放操作可以通过监听鼠标的onwheel事件实现。然后通过设置SVG的width和height属性实现放大缩小。 另外,缩放一般的期望是以鼠标所在位置为中心缩放。所以在SVG设置过宽高后需要重新校正绝对定位,以达到预期效果。

this.screen.onwheel = (e) => {
    if(e.deltaY) {
        //缩放的方法
    }
}

1、缩放方法


//缩放控制对象
this.wheel = {
    defaultX: Number(this.element.getAttribute('width')),//初始宽度
    defaultY: Number(this.element.getAttribute('height')),//初始高度
    zoomStep: 10,//表示一次变化十分之一
    zoom: 0,//缩放计数(放大+1,缩小-1)
    minZoom: -5,//最小缩放
    maxZoom: 10,//最大缩放
}

wheelSize(e) {
    const dir = e.deltaY < 0;//放大、缩小标志位
    this.wheel.zoom += dir? 1 : -1;
    if(this.wheel.zoom > this.wheel.maxZoom) {
        this.wheel.zoom = this.wheel.maxZoom;
    } else if(this.wheel.zoom < this.wheel.minZoom) {
        this.wheel.zoom = this.wheel.minZoom;
    }

    const width = this.element.getAttribute('width');//当前宽度
    const sizeX = this.wheel.defaultX + (this.wheel.defaultX / this.wheel.zoomStep) * this.wheel.zoom;//缩放后的宽度
    const sizeY = this.wheel.defaultY + (this.wheel.defaultY / this.wheel.zoomStep) * this.wheel.zoom;//缩放后的高度

    //设置缩放后的宽高
    this.element.setAttribute('width', sizeX);
    this.element.setAttribute('height', sizeY);

    //校正绝对定位
    const [x1, y1] = this.getAbsolute();
    const [x2, y2] = this.getMouseXY(e);
    const r = sizeX / width - 1;//变化的比例
    this.panTo(x1 + (x1 - x2) * r , y1 + (y1 - y2) * r);
}

四、完整代码

class SvgMap {
    constructor(screen, element) {
        this.screen = screen;
        this.element = element;

        //平移控制对象
        this.pan = {
            isPan: false,
            top: 0,
            left: 0,
            startX: null,
            startY: null
        }

        //缩放控制对象
        this.wheel = {
            defaultX: Number(this.element.getAttribute('width')),
            defaultY: Number(this.element.getAttribute('height')),
            zoomStep: 10,//表示一次变化十分之一
            zoom: 0,//缩放计数(放大+1,缩小-1)
            minZoom: -5,
            maxZoom: 10
        }

        this.init();
    }

    init() {
        this.screen.onmousedown = (e) => {
            if(e.button === 0) {
                this.panStart(e);
            }
        }

        this.screen.onmousemove = (e) => {
            if(this.pan.isPan) {
                this.panMove(e);
            }
        }

        this.screen.onmouseup = (e) => {
            if(e.button === 0) {
                this.panEnd(e);
            }
        }

        this.screen.onwheel = (e) => {
            if(e.deltaY) {
                this.wheelSize(e);
            }
        }
    }

    panStart(e) {
        this.pan.isPan = true;
        [this.pan.left, this.pan.top] = this.getAbsolute();
        [this.pan.startX, this.pan.startY] = this.getMouseXY(e);
    }
    
    panEnd(e) {
        this.pan.isPan = false;
    }

    panMove(e) {
        const [x, y] = this.getMouseXY(e);
        this.panTo(this.pan.left + (x - this.pan.startX), this.pan.top + (y - this.pan.startY))
    }

    //修改绝对定位方法
    panTo(x, y) {
        this.element.style.left = x;
        this.element.style.top = y;
    }

    wheelSize(e) {
        const dir = e.deltaY < 0;
        this.wheel.zoom += dir? 1 : -1;
        if(this.wheel.zoom > this.wheel.maxZoom) {
            this.wheel.zoom = this.wheel.maxZoom;
        } else if(this.wheel.zoom < this.wheel.minZoom) {
            this.wheel.zoom = this.wheel.minZoom;
        }

        const width = this.element.getAttribute('width');

        const sizeX = this.wheel.defaultX + (this.wheel.defaultX / this.wheel.zoomStep) * this.wheel.zoom;
        const sizeY = this.wheel.defaultY + (this.wheel.defaultY / this.wheel.zoomStep) * this.wheel.zoom;

        this.element.setAttribute('width', sizeX);
        this.element.setAttribute('height', sizeY);

        //校正绝对定位
        const [x1, y1] = this.getAbsolute();
        const [x2, y2] = this.getMouseXY(e);
        const r = sizeX / width - 1;
        this.panTo(x1 + (x1 - x2) * r , y1 + (y1 - y2) * r);
    }

    //获取绝对定位值
    getAbsolute() {
        let x = this.element.style.left || '0px';
        let y = this.element.style.top || '0px';
        const rect = this.screen.getBoundingClientRect();
        x.indexOf('%') != -1? (x = rect.x * Number.parseInt(x) / 100) : (x = Number.parseInt(x));
        y.indexOf('%') != -1? (y = rect.y * Number.parseInt(y) / 100) : (y = Number.parseInt(y));
        return [x, y];
    }

    //获取鼠标的横纵坐标
    getMouseXY(e) {
        const rect = this.screen.getBoundingClientRect();
        return [e.clientX - rect.x, e.clientY - rect.y];
    }

}