gojs中ZoomSlider放大缩小组件的坑


一. 需求

在gojs的画布上使用ZoomSlider放大缩小组件。

二. 问题

使用elementui中的Tabs切换标签,不同标签中的ZoomSlider按钮位置错乱。

三. 排查源码

发现源码根据ID来获取元素,但在VUE中会导致ID不唯一,所以按钮位置错乱

四. 解决问题

创建元素时不使用ID,使用class

并将所有获取方式改为通过类名获取

五. 优化

  1. 放出realign()方法,当画布大小变更时,可使用该方法重置放大缩小组件位置
  2. 将组件放到画布里,以免定位不到
  3. 放到画布后,按钮位置代码优化

六. 代码

/* eslint-disable no-undef */
'use strict';
/*
*  Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
*/

/**
 * This class implements a zoom slider for GoJS diagrams.
 * The constructor takes two arguments:
 *   - `diagram` ***Diagram*** a reference to a GoJS Diagram
 *   - `options` ***Object*** an optional JS Object describing options for the slider
 *
 * Options:
 *   - `alignment` ***Spot*** see {@link #alignment}
 *   - `alignmentFocus` ***Spot*** see {@link #alignmentFocus}
 *   - `size` ***number*** see {@link #size}
 *   - `buttonSize` ***number*** see {@link #buttonSize}
 *   - `orientation` ***string*** see {@link #orientation}
 *   - `opacity` ***number*** see {@link #opacity}
 *
 * Example usage of ZoomSlider:
 * ```js
 * var zoomSlider = new ZoomSlider(myDiagram,
 *   {
 *     alignment: go.Spot.TopRight, alignmentFocus: go.Spot.TopRight,
 *     size: 150, buttonSize: 30, orientation: 'horizontal'
 *   });
 * ```
 *
 * This is the basic HTML Structure that the ZoomSlider creates as a sibling div of the diagram:
 * ```html
 * 
* *
* *
* *
* ``` * *

* The diagram div's parent element should use `position: relative` to ensure the slider gets positioned properly. * * @constructor * @class * @param {Diagram} diagram the Diagram which the slider zooms in/out * @param {Object} options the options for the slider */ export default function ZoomSlider(diagram, options) { this._diagram = diagram; this._initialScale = diagram.scale; this._diagramDiv = diagram.div; this._sliderDiv = null; // Options defaults: this._size = 125; this._buttonSize = 25; this._alignment = go.Spot.BottomRight; this._alignmentFocus = go.Spot.BottomRight; this._orientation = 'vertical'; this._opacity = .75; // Set properties based on options if (options !== undefined) { if (options['size'] !== undefined) this._size = options['size']; if (options['buttonSize'] !== undefined) this._buttonSize = options['buttonSize']; if (options['alignment'] !== undefined) this._alignment = options['alignment']; if (options['alignmentFocus'] !== undefined) this._alignmentFocus = options['alignmentFocus']; if (options['orientation'] !== undefined) this._orientation = options['orientation']; if (options['opacity'] !== undefined) this._opacity = options['opacity']; } // Prepare change listeners var self = this; this.updateOnViewportBoundsChanged = function () { self.scaleToValue(); }; this.updateRealign = function() { self.realign() }; this.init(); } // Public properties /** * This read-only property returns the diagram for which the slider is handling zoom. * @name ZoomSlider#diagram * @return {Diagram} */ Object.defineProperty(ZoomSlider.prototype, 'diagram', { get: function() { return this._diagram; } }); /** * Gets or sets the overall length, in pixels, that the slider will occupy. * The default value is 125. * @name ZoomSlider#size * @return {number} */ Object.defineProperty(ZoomSlider.prototype, 'size', { get: function() { return this._size; }, set: function(val) { var old = this._size; if (old !== val) { this._size = val; this.resize(); } } }); /** * Gets or sets the height/width of the buttons at each end of the slider. * The default value is 25. * @name ZoomSlider#buttonSize * @return {number} */ Object.defineProperty(ZoomSlider.prototype, 'buttonSize', { get: function() { return this._buttonSize; }, set: function(val) { var old = this._buttonSize; if (old !== val) { this._buttonSize = val; this.resize(); } } }); /** * Gets or sets the alignment Spot of this slider to determine where it should be placed relative to the diagram. * The default value is Spot.BottomRight. * @name ZoomSlider#alignment * @return {Spot} */ Object.defineProperty(ZoomSlider.prototype, 'alignment', { get: function() { return this._alignment; }, set: function(val) { var old = this._alignment; if (old !== val) { this._alignment = val; this.realign(); } } }); /** * Gets or sets the Spot on this slider to be used as the alignment point when placing it relative to the diagram. * The default value is Spot.BottomRight. * @name ZoomSlider#alignmentFocus * @return {Spot} */ Object.defineProperty(ZoomSlider.prototype, 'alignmentFocus', { get: function() { return this._alignmentFocus; }, set: function(val) { var old = this._alignmentFocus; if (old !== val) { this._alignmentFocus = val; this.realign(); } } }); /** * Gets or sets whether the slider is oriented vertically or horizontally. * Must be either 'horizontal' or 'vertical' and is case-sensitive. * The default value is `'vertical'`. * @name ZoomSlider#orientation * @return {string} */ Object.defineProperty(ZoomSlider.prototype, 'orientation', { get: function() { return this._orientation; }, set: function(val) { if (val !== 'horizontal' && val !== 'vertical') { throw new Error('Orientation must be "horizontal" or "vertical"'); } var old = this._orientation; if (old !== val) { this._orientation = val; this.resize(true); } } }); /** * Gets or sets the opacity of the slider. * The default value is 0.75. * @name ZoomSlider#opacity * @return {Spot} */ Object.defineProperty(ZoomSlider.prototype, 'opacity', { get: function() { return this._opacity; }, set: function(val) { var old = this._opacity; if (old !== val) { this._opacity = val; this._sliderDiv.style.opacity = val; } } }); /** * @ignore * Initialize the slider. * @this {ZoomSlider} */ ZoomSlider.prototype.init = function() { // Sets up the slider div and inner div's basic attributes and ids this.sliderDivSetup(); this.resize(true); // Set up the runtime code this.sliderListenerSetup(); }; /** * @ignore * Create the necessary divs for the slider and add the slider as a sibling of the diagram. * @this {ZoomSlider} */ ZoomSlider.prototype.sliderDivSetup = function() { this._sliderDiv = document.createElement('div'); this._sliderDiv.className = 'zoomSlider'; // Initialize buttons and range input var zoomOutBtn = document.createElement('button'); zoomOutBtn.className = 'zoomSliderOut zoomButton'; zoomOutBtn.innerHTML = '-'; this._sliderDiv.appendChild(zoomOutBtn); var zoomRangeContainer = document.createElement('div'); zoomRangeContainer.className = 'zoomSliderRangeCtn zoomRangeContainer'; this._sliderDiv.appendChild(zoomRangeContainer); var zoomRangeInput = document.createElement('input'); zoomRangeInput.className = 'zoomSliderRange zoomRangeInput'; zoomRangeInput.type = 'range'; zoomRangeInput.min = -50; zoomRangeInput.max = 100; zoomRangeContainer.appendChild(zoomRangeInput); var zoomInBtn = document.createElement('button'); zoomInBtn.className = 'zoomSliderIn zoomButton'; zoomInBtn.innerHTML = '+'; this._sliderDiv.appendChild(zoomInBtn); // Adds the slider as a sibling of the diagram // IMPORTANT: the diagram div's parent element should use position: relative if (this._diagramDiv !== null) { var diagramParent = this._diagramDiv; if (diagramParent !== null) { diagramParent.appendChild(this._sliderDiv); } } }; /** * @ignore * Add listeners to the buttons and range input. todoczl * Add a diagram listener * @this {ZoomSlider} */ ZoomSlider.prototype.sliderListenerSetup = function() { var diagramParent = this._diagramDiv.parentElement; var zoomOutBtn = diagramParent.getElementsByClassName('zoomSliderOut')[0]; var zoomInBtn = diagramParent.getElementsByClassName('zoomSliderIn')[0]; var zoomRangeInput = diagramParent.getElementsByClassName('zoomSliderRange')[0]; if (zoomOutBtn === null || zoomInBtn === null || zoomRangeInput === null) return; // Set up diagram listener so the slider can be kept in sync with the diagram's scale this.diagram.addDiagramListener('ViewportBoundsChanged', this.updateOnViewportBoundsChanged); // Set up event handlers for buttons and input range slider var self = this; zoomOutBtn.onclick = function() { zoomRangeInput.stepDown(); self.valueToScale(); }; zoomInBtn.onclick = function() { zoomRangeInput.stepUp(); self.valueToScale(); }; var valChanged = function() { self.valueToScale(); }; zoomRangeInput.oninput = valChanged; zoomRangeInput.onchange = valChanged; }; /** * @ignore * Resize the slider. * @this {ZoomSlider} * @param {boolean} reorient whether or not to reorient the slider/buttons */ ZoomSlider.prototype.resize = function(reorient) { var sliderWidth = 0; var sliderHeight = 0; var diagramParent = this._diagramDiv.parentElement; var zoomOutBtn = diagramParent.getElementsByClassName('zoomSliderOut')[0]; var zoomInBtn = diagramParent.getElementsByClassName('zoomSliderIn')[0]; var zoomRangeContainer = diagramParent.getElementsByClassName('zoomSliderRangeCtn')[0]; var zoomRangeInput = diagramParent.getElementsByClassName('zoomSliderRange')[0]; if (this._sliderDiv === null || zoomOutBtn === null || zoomInBtn === null || zoomRangeContainer === null || zoomRangeInput === null) return; if (this.orientation === 'horizontal') { sliderWidth = this.size; sliderHeight = this.buttonSize; var rangeWidth = sliderWidth - sliderHeight * 2; zoomOutBtn.style.width = sliderHeight + 'px'; zoomOutBtn.style.height = sliderHeight + 'px'; zoomRangeContainer.style.width = rangeWidth + 'px'; zoomRangeContainer.style.height = sliderHeight + 'px'; zoomRangeInput.style.width = rangeWidth + 'px'; zoomRangeInput.style.height = sliderHeight + 'px'; zoomRangeInput.style.transformOrigin = null; zoomInBtn.style.width = sliderHeight + 'px'; zoomInBtn.style.height = sliderHeight + 'px'; } else { sliderHeight = this.size; sliderWidth = this.buttonSize; var rangeHeight = sliderHeight - sliderWidth * 2; zoomInBtn.style.width = sliderWidth + 'px'; zoomInBtn.style.height = sliderWidth + 'px'; zoomRangeContainer.style.width = sliderWidth + 'px'; zoomRangeContainer.style.height = rangeHeight + 'px'; zoomRangeInput.style.width = rangeHeight + 'px'; zoomRangeInput.style.height = sliderWidth + 'px'; zoomRangeInput.style.transformOrigin = rangeHeight / 2 + 'px ' + rangeHeight / 2 + 'px'; zoomOutBtn.style.width = sliderWidth + 'px'; zoomOutBtn.style.height = sliderWidth + 'px'; } this._sliderDiv.style.width = sliderWidth + 'px'; this._sliderDiv.style.height = sliderHeight + 'px'; // Reorient the slider, if necessary if (reorient) { this.reorient(); } // Realign based on new size this.realign(); } /** * @ignore * Reorient the slider, changing the transform and the order of the buttons within the div. todoczl * @this {ZoomSlider} */ ZoomSlider.prototype.reorient = function() { var diagramParent = this._diagramDiv.parentElement; var zoomOutBtn = diagramParent.getElementsByClassName('zoomSliderOut')[0]; var zoomInBtn = diagramParent.getElementsByClassName('zoomSliderIn')[0]; var zoomRangeInput = diagramParent.getElementsByClassName('zoomSliderRange')[0]; if (this._sliderDiv === null || zoomOutBtn === null || zoomInBtn === null || zoomRangeInput === null) return; // Need to set the transform of the range input and move the buttons to the correct sides if (this.orientation === 'horizontal') { zoomRangeInput.style.transform = null; this._sliderDiv.insertBefore(zoomOutBtn, this._sliderDiv.firstChild); this._sliderDiv.appendChild(zoomInBtn); } else { zoomRangeInput.style.transform = 'rotate(-90deg)'; this._sliderDiv.insertBefore(zoomInBtn, this._sliderDiv.firstChild); this._sliderDiv.appendChild(zoomOutBtn); } } /** * @ignore * Realigns to slider relative to the diagram. * @this {ZoomSlider} */ ZoomSlider.prototype.realign = function() { if (this._diagramDiv === null || this._sliderDiv === null) return; var sliderWidth = 0; var sliderHeight = 0; if (this.orientation === 'horizontal') { sliderWidth = this.size; sliderHeight = this.buttonSize; } else { sliderHeight = this.size; sliderWidth = this.buttonSize; } // Finds the diagram and diagram's parent in the page var diagramParent = this._diagramDiv.parentElement; // var diagramLoc = this._diagramDiv.getBoundingClientRect(); if (diagramParent !== null) { // var parentLoc = diagramParent.getBoundingClientRect(); var top = // diagramLoc.top - parentLoc.top + this.alignment.y * this._diagramDiv.clientHeight + this.alignment.offsetY - this.alignmentFocus.y * sliderHeight + this.alignmentFocus.offsetY; var left = // diagramLoc.left - parentLoc.left + this.alignment.x * this._diagramDiv.clientWidth + this.alignment.offsetX - this.alignmentFocus.x * sliderWidth + this.alignmentFocus.offsetX; this._sliderDiv.style.top = top + 'px'; this._sliderDiv.style.left = left + 'px'; } } /** * @ignore * Update the value of the slider input to match the diagram's scale. * @this {ZoomSlider} */ ZoomSlider.prototype.scaleToValue = function() { var diagramParent = this._diagramDiv.parentElement; var slider = diagramParent.getElementsByClassName('zoomSliderRange')[0]; var diagram = this.diagram; var A = this._initialScale; var B = diagram.commandHandler.zoomFactor; var y1 = diagram.scale; slider.value = Math.round(Math.log(y1/A) / Math.log(B)).toString(); } /** * @ignore * Update the diagram's scale to match the value of the slider input. * @this {ZoomSlider} */ ZoomSlider.prototype.valueToScale = function() { var diagramParent = this._diagramDiv.parentElement; var slider = diagramParent.getElementsByClassName('zoomSliderRange')[0]; var diagram = this.diagram; var x = parseFloat(slider.value); var A = this._initialScale; var B = diagram.commandHandler.zoomFactor; diagram.scale = A * Math.pow(B, x); } /** * Remove the slider from the page. * @this {ZoomSlider} */ ZoomSlider.prototype.remove = function() { // Remove the listener attached to diagram this.diagram.removeDiagramListener('ViewportBoundsChanged', this.updateOnViewportBoundsChanged); if (this._sliderDiv !== null) { this._sliderDiv.innerHTML = ''; if (this._sliderDiv.parentElement) { this._sliderDiv.parentElement.removeChild(this._sliderDiv); } this._sliderDiv = null; } };