From b0abbcb2aa1a3989ebe20ed18b55b30a8498c2cb Mon Sep 17 00:00:00 2001 From: hu de yi Date: Tue, 21 Apr 2026 14:30:06 +0800 Subject: [PATCH 1/7] feat:InfoWindow support autoAdjustAnchor by map --- packages/maptalks/src/ui/InfoWindow.ts | 77 ++++++++++++++++++++++++- packages/maptalks/src/ui/UIComponent.ts | 13 ++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/packages/maptalks/src/ui/InfoWindow.ts b/packages/maptalks/src/ui/InfoWindow.ts index 96397ea391..991ab5cf80 100644 --- a/packages/maptalks/src/ui/InfoWindow.ts +++ b/packages/maptalks/src/ui/InfoWindow.ts @@ -7,6 +7,7 @@ import { Geometry, Marker, MultiPoint, LineString, MultiLineString } from '../ge import type { Map } from '../map'; import { MapEventDataType } from '../map/Map.DomEvents'; import UIComponent, { UIComponentAlignOptionsType, UIComponentOptionsType } from './UIComponent'; +import { bboxInBBOX, BBOX } from '../core/util/bbox'; const PROPERTY_PATTERN = /\{ *([\w_]+) *\}/g; /** @@ -35,7 +36,8 @@ const options: InfoWindowOptionsType = { 'content': null, 'enableTemplate': false, 'horizontalAlignment': 'middle', - 'verticalAlignment': 'top' + 'verticalAlignment': 'top', + 'autoAdjustAnchor': false }; const EMPTY_SIZE = new Size(0, 0); @@ -524,6 +526,78 @@ class InfoWindow extends UIComponent { return width; } + _autoAdjustAnchor() { + const options = this.options as any; + if (!options.autoAdjustAnchor) { + return this + } + const dom = this.getDOM(); + const map = this.getMap(); + if (!map || !dom || !dom.getBoundingClientRect) { + return this + } + const rect = dom.getBoundingClientRect(); + const size = map.getSize(); + + const horizontalAlignment = options.horizontalAlignment; + const verticalAlignment = options.verticalAlignment; + + const width = size.width, height = size.height; + const x1 = rect.left, x2 = rect.right, y1 = rect.top, y2 = rect.bottom; + let temphorizontalAlignment = horizontalAlignment, tempverticalAlignment = verticalAlignment; + const w = rect.width, h = rect.height; + + const bbox1 = [x1 - w / 2, y1, x2 + w / 2, y2], mapBBOX = [0, 0, width, height] as BBOX; + if (bbox1[0] > 0 && bbox1[2] < width) { + temphorizontalAlignment = 'middle'; + } + + if (bboxInBBOX(bbox1 as BBOX, mapBBOX)) { + temphorizontalAlignment = 'middle'; + tempverticalAlignment = 'top'; + if (verticalAlignment === 'bottom') { + let offset = { x: 0, y: 30 }; + const owner = this.getOwner() || {}; + if (owner instanceof Marker) { + const extent = owner._getFixedExtent(); + if (extent) { + const height = extent.getHeight(); + offset.y += height; + } + } + + const translateY = h + offset.y; + const bbox3 = [x1 - w / 2, y1 - translateY, x2 + w / 2, y2 - translateY]; + if (!bboxInBBOX(bbox3 as BBOX, mapBBOX)) { + tempverticalAlignment = verticalAlignment; + } + } + + } else { + if (x1 < 0) { + temphorizontalAlignment = 'right'; + } + if (x2 > width) { + temphorizontalAlignment = 'left'; + } + if (y1 < 0) { + tempverticalAlignment = 'bottom'; + } + if (y2 > height) { + tempverticalAlignment = 'top' + } + } + if (temphorizontalAlignment === horizontalAlignment && tempverticalAlignment === verticalAlignment) { + return this; + } + + this.config({ + horizontalAlignment: temphorizontalAlignment, + verticalAlignment: tempverticalAlignment + }) + + } + } InfoWindow.mergeOptions(options); @@ -541,5 +615,6 @@ export type InfoWindowOptionsType = { title?: string; content?: string | HTMLElement; enableTemplate?: boolean; + autoAdjustAnchor?: boolean; } & UIComponentOptionsType & UIComponentAlignOptionsType; diff --git a/packages/maptalks/src/ui/UIComponent.ts b/packages/maptalks/src/ui/UIComponent.ts index 2d9a829aef..3a7947315e 100644 --- a/packages/maptalks/src/ui/UIComponent.ts +++ b/packages/maptalks/src/ui/UIComponent.ts @@ -121,6 +121,10 @@ class UIComponent extends Eventable(Class) { this.proxyOptions(); } + getOffset() { + return new Point(0, 0); + } + //@internal _appendCustomClass(dom: HTMLElement) { if (!dom) { @@ -884,7 +888,7 @@ class UIComponent extends Eventable(Class) { } onMoving() { - if (this.isVisible() && this.getMap().isTransforming()) { + if (this.isVisible()) { this._updatePosition(); } } @@ -928,6 +932,8 @@ class UIComponent extends Eventable(Class) { return this; } + + //@internal _setPosition() { const dom = this.getDOM(); @@ -936,6 +942,11 @@ class UIComponent extends Eventable(Class) { const p = this.getPosition(); this._pos = p; dom.style[TRANSFORM] = this._toCSSTranslate(p) + ' scale(1)'; + if ((this as any)._autoAdjustAnchor) { + (this as any)._autoAdjustAnchor(); + } + + } //@internal From 1a96087a0566433e536c56faa7f663057cc2075d Mon Sep 17 00:00:00 2001 From: hu de yi Date: Tue, 21 Apr 2026 14:35:37 +0800 Subject: [PATCH 2/7] updates --- packages/maptalks/src/ui/UIComponent.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/maptalks/src/ui/UIComponent.ts b/packages/maptalks/src/ui/UIComponent.ts index 3a7947315e..ca839c7e8a 100644 --- a/packages/maptalks/src/ui/UIComponent.ts +++ b/packages/maptalks/src/ui/UIComponent.ts @@ -121,10 +121,6 @@ class UIComponent extends Eventable(Class) { this.proxyOptions(); } - getOffset() { - return new Point(0, 0); - } - //@internal _appendCustomClass(dom: HTMLElement) { if (!dom) { From 8bab92acada4f9de653438b24d592a37afcd3853 Mon Sep 17 00:00:00 2001 From: hu de yi Date: Tue, 21 Apr 2026 14:37:14 +0800 Subject: [PATCH 3/7] fix lint --- packages/maptalks/src/ui/InfoWindow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/maptalks/src/ui/InfoWindow.ts b/packages/maptalks/src/ui/InfoWindow.ts index 991ab5cf80..d523c8f8b5 100644 --- a/packages/maptalks/src/ui/InfoWindow.ts +++ b/packages/maptalks/src/ui/InfoWindow.ts @@ -556,7 +556,7 @@ class InfoWindow extends UIComponent { temphorizontalAlignment = 'middle'; tempverticalAlignment = 'top'; if (verticalAlignment === 'bottom') { - let offset = { x: 0, y: 30 }; + const offset = { x: 0, y: 30 }; const owner = this.getOwner() || {}; if (owner instanceof Marker) { const extent = owner._getFixedExtent(); From 4fa0cec5a66f20fa39e1b04e533cacbbf0aeffee Mon Sep 17 00:00:00 2001 From: hu de yi Date: Tue, 21 Apr 2026 16:35:25 +0800 Subject: [PATCH 4/7] updates --- packages/maptalks/src/ui/InfoWindow.ts | 36 ++++++++++++++------------ 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/maptalks/src/ui/InfoWindow.ts b/packages/maptalks/src/ui/InfoWindow.ts index d523c8f8b5..7bc48b9851 100644 --- a/packages/maptalks/src/ui/InfoWindow.ts +++ b/packages/maptalks/src/ui/InfoWindow.ts @@ -527,7 +527,7 @@ class InfoWindow extends UIComponent { } _autoAdjustAnchor() { - const options = this.options as any; + const options = this.options; if (!options.autoAdjustAnchor) { return this } @@ -544,17 +544,21 @@ class InfoWindow extends UIComponent { const width = size.width, height = size.height; const x1 = rect.left, x2 = rect.right, y1 = rect.top, y2 = rect.bottom; - let temphorizontalAlignment = horizontalAlignment, tempverticalAlignment = verticalAlignment; + + let horizontalAlign = horizontalAlignment, + verticalAlign = verticalAlignment; const w = rect.width, h = rect.height; + const halfw = w / 2; - const bbox1 = [x1 - w / 2, y1, x2 + w / 2, y2], mapBBOX = [0, 0, width, height] as BBOX; + const bbox1 = [x1 - halfw, y1, x2 + halfw, y2], mapBBOX = [0, 0, width, height] as BBOX; if (bbox1[0] > 0 && bbox1[2] < width) { - temphorizontalAlignment = 'middle'; + horizontalAlign = 'middle'; } + //dom rect in map view if (bboxInBBOX(bbox1 as BBOX, mapBBOX)) { - temphorizontalAlignment = 'middle'; - tempverticalAlignment = 'top'; + horizontalAlign = 'middle'; + verticalAlign = 'top'; if (verticalAlignment === 'bottom') { const offset = { x: 0, y: 30 }; const owner = this.getOwner() || {}; @@ -567,33 +571,33 @@ class InfoWindow extends UIComponent { } const translateY = h + offset.y; - const bbox3 = [x1 - w / 2, y1 - translateY, x2 + w / 2, y2 - translateY]; + const bbox3 = [x1 - halfw, y1 - translateY, x2 + halfw, y2 - translateY]; + //判断是否可以 verticalAlign=top if (!bboxInBBOX(bbox3 as BBOX, mapBBOX)) { - tempverticalAlignment = verticalAlignment; + verticalAlign = verticalAlignment; } } - } else { if (x1 < 0) { - temphorizontalAlignment = 'right'; + horizontalAlign = 'right'; } if (x2 > width) { - temphorizontalAlignment = 'left'; + horizontalAlign = 'left'; } if (y1 < 0) { - tempverticalAlignment = 'bottom'; + verticalAlign = 'bottom'; } if (y2 > height) { - tempverticalAlignment = 'top' + verticalAlign = 'top' } } - if (temphorizontalAlignment === horizontalAlignment && tempverticalAlignment === verticalAlignment) { + if (horizontalAlign === horizontalAlignment && verticalAlign === verticalAlignment) { return this; } this.config({ - horizontalAlignment: temphorizontalAlignment, - verticalAlignment: tempverticalAlignment + horizontalAlignment: horizontalAlign, + verticalAlignment: verticalAlign }) } From 0e17247fd4b1dcc60d70ddec3e060b37d2574736 Mon Sep 17 00:00:00 2001 From: hu de yi Date: Tue, 21 Apr 2026 17:05:56 +0800 Subject: [PATCH 5/7] updates --- packages/maptalks/src/ui/InfoWindow.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/maptalks/src/ui/InfoWindow.ts b/packages/maptalks/src/ui/InfoWindow.ts index 7bc48b9851..32998f53ae 100644 --- a/packages/maptalks/src/ui/InfoWindow.ts +++ b/packages/maptalks/src/ui/InfoWindow.ts @@ -551,14 +551,12 @@ class InfoWindow extends UIComponent { const halfw = w / 2; const bbox1 = [x1 - halfw, y1, x2 + halfw, y2], mapBBOX = [0, 0, width, height] as BBOX; + //always middle if (bbox1[0] > 0 && bbox1[2] < width) { horizontalAlign = 'middle'; } - //dom rect in map view - if (bboxInBBOX(bbox1 as BBOX, mapBBOX)) { - horizontalAlign = 'middle'; - verticalAlign = 'top'; + const topJudge = () => { if (verticalAlignment === 'bottom') { const offset = { x: 0, y: 30 }; const owner = this.getOwner() || {}; @@ -571,12 +569,17 @@ class InfoWindow extends UIComponent { } const translateY = h + offset.y; - const bbox3 = [x1 - halfw, y1 - translateY, x2 + halfw, y2 - translateY]; + const bbox3 = [x1, y1 - translateY, x2, y2 - translateY]; //判断是否可以 verticalAlign=top - if (!bboxInBBOX(bbox3 as BBOX, mapBBOX)) { - verticalAlign = verticalAlignment; + if (bboxInBBOX(bbox3 as BBOX, mapBBOX)) { + verticalAlign = 'top'; } } + } + + //dom rect in map view + if (bboxInBBOX(bbox1 as BBOX, mapBBOX)) { + topJudge(); } else { if (x1 < 0) { horizontalAlign = 'right'; @@ -590,7 +593,9 @@ class InfoWindow extends UIComponent { if (y2 > height) { verticalAlign = 'top' } + topJudge(); } + if (horizontalAlign === horizontalAlignment && verticalAlign === verticalAlignment) { return this; } From eddfca82f18e8d521841824119a5b44af270d3de Mon Sep 17 00:00:00 2001 From: hu de yi Date: Wed, 22 Apr 2026 09:13:32 +0800 Subject: [PATCH 6/7] update spec --- packages/maptalks/test/map/MapSpec.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/maptalks/test/map/MapSpec.js b/packages/maptalks/test/map/MapSpec.js index dfc36f6de4..6f1caed89e 100644 --- a/packages/maptalks/test/map/MapSpec.js +++ b/packages/maptalks/test/map/MapSpec.js @@ -302,8 +302,13 @@ describe('Map.Spec', function () { it('zoom in/out with animation', function (done) { map.config('zoomAnimation', true); var cur = map.getZoom(); + function getZoom() { + const zoom = map.getZoom(); + return parseInt(zoom.toFixed(0)); + + } map.on('zoomend', function () { - expect(map.getZoom()).to.be.eql(cur + 1); + expect(getZoom()).to.be.eql(cur + 1); done(); }); map.zoomIn(); @@ -390,12 +395,12 @@ describe('Map.Spec', function () { }); it('fit to china extent', function (done) { - const chinaExtent = [73.499013,3.397894,135.087377,53.561308]; + const chinaExtent = [73.499013, 3.397894, 135.087377, 53.561308]; map.fitExtent(chinaExtent, 0, { 'animation': false }); const [lon, lat] = map.getCenter().toArray(); const lonDiff = Math.abs(lon - 104.293195); const latDiff = Math.abs(lat - 31.76872613); - const delta = Math.pow(10,-10); + const delta = Math.pow(10, -10); expect(lonDiff).to.be.below(delta); expect(latDiff).to.be.below(delta); done(); From 0a4a0a8ec5362807aab04db4f7962daf433962da Mon Sep 17 00:00:00 2001 From: hu de yi Date: Mon, 27 Apr 2026 09:21:44 +0800 Subject: [PATCH 7/7] updates --- packages/maptalks/src/ui/InfoWindow.ts | 85 +--------------------- packages/maptalks/src/ui/UIComponent.ts | 94 +++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 90 deletions(-) diff --git a/packages/maptalks/src/ui/InfoWindow.ts b/packages/maptalks/src/ui/InfoWindow.ts index 32998f53ae..6b6d2868e8 100644 --- a/packages/maptalks/src/ui/InfoWindow.ts +++ b/packages/maptalks/src/ui/InfoWindow.ts @@ -7,7 +7,6 @@ import { Geometry, Marker, MultiPoint, LineString, MultiLineString } from '../ge import type { Map } from '../map'; import { MapEventDataType } from '../map/Map.DomEvents'; import UIComponent, { UIComponentAlignOptionsType, UIComponentOptionsType } from './UIComponent'; -import { bboxInBBOX, BBOX } from '../core/util/bbox'; const PROPERTY_PATTERN = /\{ *([\w_]+) *\}/g; /** @@ -36,8 +35,7 @@ const options: InfoWindowOptionsType = { 'content': null, 'enableTemplate': false, 'horizontalAlignment': 'middle', - 'verticalAlignment': 'top', - 'autoAdjustAnchor': false + 'verticalAlignment': 'top' }; const EMPTY_SIZE = new Size(0, 0); @@ -526,86 +524,7 @@ class InfoWindow extends UIComponent { return width; } - _autoAdjustAnchor() { - const options = this.options; - if (!options.autoAdjustAnchor) { - return this - } - const dom = this.getDOM(); - const map = this.getMap(); - if (!map || !dom || !dom.getBoundingClientRect) { - return this - } - const rect = dom.getBoundingClientRect(); - const size = map.getSize(); - - const horizontalAlignment = options.horizontalAlignment; - const verticalAlignment = options.verticalAlignment; - - const width = size.width, height = size.height; - const x1 = rect.left, x2 = rect.right, y1 = rect.top, y2 = rect.bottom; - - let horizontalAlign = horizontalAlignment, - verticalAlign = verticalAlignment; - const w = rect.width, h = rect.height; - const halfw = w / 2; - - const bbox1 = [x1 - halfw, y1, x2 + halfw, y2], mapBBOX = [0, 0, width, height] as BBOX; - //always middle - if (bbox1[0] > 0 && bbox1[2] < width) { - horizontalAlign = 'middle'; - } - - const topJudge = () => { - if (verticalAlignment === 'bottom') { - const offset = { x: 0, y: 30 }; - const owner = this.getOwner() || {}; - if (owner instanceof Marker) { - const extent = owner._getFixedExtent(); - if (extent) { - const height = extent.getHeight(); - offset.y += height; - } - } - - const translateY = h + offset.y; - const bbox3 = [x1, y1 - translateY, x2, y2 - translateY]; - //判断是否可以 verticalAlign=top - if (bboxInBBOX(bbox3 as BBOX, mapBBOX)) { - verticalAlign = 'top'; - } - } - } - - //dom rect in map view - if (bboxInBBOX(bbox1 as BBOX, mapBBOX)) { - topJudge(); - } else { - if (x1 < 0) { - horizontalAlign = 'right'; - } - if (x2 > width) { - horizontalAlign = 'left'; - } - if (y1 < 0) { - verticalAlign = 'bottom'; - } - if (y2 > height) { - verticalAlign = 'top' - } - topJudge(); - } - - if (horizontalAlign === horizontalAlignment && verticalAlign === verticalAlignment) { - return this; - } - this.config({ - horizontalAlignment: horizontalAlign, - verticalAlignment: verticalAlign - }) - - } } @@ -624,6 +543,6 @@ export type InfoWindowOptionsType = { title?: string; content?: string | HTMLElement; enableTemplate?: boolean; - autoAdjustAnchor?: boolean; + } & UIComponentOptionsType & UIComponentAlignOptionsType; diff --git a/packages/maptalks/src/ui/UIComponent.ts b/packages/maptalks/src/ui/UIComponent.ts index ca839c7e8a..2077687aba 100644 --- a/packages/maptalks/src/ui/UIComponent.ts +++ b/packages/maptalks/src/ui/UIComponent.ts @@ -18,7 +18,8 @@ import Coordinate from '../geo/Coordinate'; import type { Map } from './../map/Map'; import { Point } from '../geo'; import { MapStateCache } from '../map/MapStateCache'; - +import { bboxInBBOX, BBOX } from '../core/util/bbox'; +import { Marker } from '../geometry'; /** * @property {Object} options * @property {Boolean} [options.eventsPropagation=false] - whether stop ALL events' propagation. @@ -61,7 +62,8 @@ const options: UIComponentOptionsType = { 'collisionWeight': 0, 'collisionFadeIn': false, 'zIndex': 0, - 'enableScrollbar': true + 'enableScrollbar': true, + 'autoAdjustAnchor': false }; const COLLISION_STATES = ['collision', 'collisionBufferSize', 'collisionWeight', 'collisionFadeIn'] @@ -938,11 +940,7 @@ class UIComponent extends Eventable(Class) { const p = this.getPosition(); this._pos = p; dom.style[TRANSFORM] = this._toCSSTranslate(p) + ' scale(1)'; - if ((this as any)._autoAdjustAnchor) { - (this as any)._autoAdjustAnchor(); - } - - + this._autoAdjustAnchor(); } //@internal @@ -1080,6 +1078,87 @@ class UIComponent extends Eventable(Class) { this.fire('mouseout'); } + + _autoAdjustAnchor() { + const options = this.options as any; + if (!options.autoAdjustAnchor) { + return this + } + const dom = this.getDOM(); + const map = this.getMap(); + if (!map || !dom || !dom.getBoundingClientRect) { + return this + } + const rect = dom.getBoundingClientRect(); + const size = map.getSize(); + + const horizontalAlignment = options.horizontalAlignment; + const verticalAlignment = options.verticalAlignment; + + const width = size.width, height = size.height; + const x1 = rect.left, x2 = rect.right, y1 = rect.top, y2 = rect.bottom; + + let horizontalAlign = horizontalAlignment, + verticalAlign = verticalAlignment; + const w = rect.width, h = rect.height; + const halfw = w / 2; + + const bbox1 = [x1 - halfw, y1, x2 + halfw, y2], mapBBOX = [0, 0, width, height] as BBOX; + //always middle + if (bbox1[0] > 0 && bbox1[2] < width) { + horizontalAlign = 'middle'; + } + + const topJudge = () => { + if (verticalAlignment === 'bottom') { + const offset = { x: 0, y: 30 }; + const owner = this.getOwner() || {}; + if (owner instanceof Marker) { + const extent = owner._getFixedExtent(); + if (extent) { + const height = extent.getHeight(); + offset.y += height; + } + } + + const translateY = h + offset.y; + const bbox3 = [x1, y1 - translateY, x2, y2 - translateY]; + //判断是否可以 verticalAlign=top + if (bboxInBBOX(bbox3 as BBOX, mapBBOX)) { + verticalAlign = 'top'; + } + } + } + + //dom rect in map view + if (bboxInBBOX(bbox1 as BBOX, mapBBOX)) { + topJudge(); + } else { + if (x1 < 0) { + horizontalAlign = 'right'; + } + if (x2 > width) { + horizontalAlign = 'left'; + } + if (y1 < 0) { + verticalAlign = 'bottom'; + } + if (y2 > height) { + verticalAlign = 'top' + } + topJudge(); + } + + if (horizontalAlign === horizontalAlignment && verticalAlign === verticalAlignment) { + return this; + } + + this.config({ + horizontalAlignment: horizontalAlign, + verticalAlignment: verticalAlign + }) + + } } UIComponent.mergeOptions(options); @@ -1108,6 +1187,7 @@ export type UIComponentOptionsType = { zIndex?: number; cssName?: string | Array; enableScrollbar?: boolean; + autoAdjustAnchor?: boolean; } let resizeObserver: ResizeObserver;