diff --git a/src/ui/control/navigation_control.js b/src/ui/control/navigation_control.js index 73802caa0d6..65515352200 100644 --- a/src/ui/control/navigation_control.js +++ b/src/ui/control/navigation_control.js @@ -91,7 +91,11 @@ class NavigationControl { `scale(${1 / Math.pow(Math.cos(this._map.transform.pitch * (Math.PI / 180)), 0.5)}) rotateX(${this._map.transform.pitch}deg) rotateZ(${this._map.transform.angle * (180 / Math.PI)}deg)` : `rotate(${this._map.transform.angle * (180 / Math.PI)}deg)`; - this._compassIcon.style.transform = rotate; + this._map._domRenderTaskQueue.add(() => { + if (this._compassIcon) { + this._compassIcon.style.transform = rotate; + } + }); } onAdd(map: Map) { diff --git a/src/ui/control/scale_control.js b/src/ui/control/scale_control.js index c4693fd9dc9..7e7a7dcf593 100644 --- a/src/ui/control/scale_control.js +++ b/src/ui/control/scale_control.js @@ -96,6 +96,7 @@ function updateScale(map, container, options) { const left = map.unproject([0, y]); const right = map.unproject([maxWidth, y]); const maxMeters = left.distanceTo(right); + const domQueue = map._domRenderTaskQueue; // The real distance corresponding to 100px scale length is rounded off to // near pretty number and the scale length for the same is found out. // Default unit of the scale is based on User's locale. @@ -103,25 +104,27 @@ function updateScale(map, container, options) { const maxFeet = 3.2808 * maxMeters; if (maxFeet > 5280) { const maxMiles = maxFeet / 5280; - setScale(container, maxWidth, maxMiles, map._getUIString('ScaleControl.Miles')); + setScale(container, maxWidth, maxMiles, map._getUIString('ScaleControl.Miles'), domQueue); } else { - setScale(container, maxWidth, maxFeet, map._getUIString('ScaleControl.Feet')); + setScale(container, maxWidth, maxFeet, map._getUIString('ScaleControl.Feet'), domQueue); } } else if (options && options.unit === 'nautical') { const maxNauticals = maxMeters / 1852; - setScale(container, maxWidth, maxNauticals, map._getUIString('ScaleControl.NauticalMiles')); + setScale(container, maxWidth, maxNauticals, map._getUIString('ScaleControl.NauticalMiles'), domQueue); } else if (maxMeters >= 1000) { - setScale(container, maxWidth, maxMeters / 1000, map._getUIString('ScaleControl.Kilometers')); + setScale(container, maxWidth, maxMeters / 1000, map._getUIString('ScaleControl.Kilometers'), domQueue); } else { - setScale(container, maxWidth, maxMeters, map._getUIString('ScaleControl.Meters')); + setScale(container, maxWidth, maxMeters, map._getUIString('ScaleControl.Meters'), domQueue); } } -function setScale(container, maxWidth, maxDistance, unit) { +function setScale(container, maxWidth, maxDistance, unit, domQueue) { const distance = getRoundNum(maxDistance); const ratio = distance / maxDistance; - container.style.width = `${maxWidth * ratio}px`; - container.innerHTML = `${distance} ${unit}`; + domQueue.add(() => { + container.style.width = `${maxWidth * ratio}px`; + container.innerHTML = `${distance} ${unit}`; + }); } function getDecimalRoundNum(d) { diff --git a/src/ui/handler/box_zoom.js b/src/ui/handler/box_zoom.js index 2f43f2541ae..aed9237ef86 100644 --- a/src/ui/handler/box_zoom.js +++ b/src/ui/handler/box_zoom.js @@ -107,10 +107,14 @@ class BoxZoomHandler { minY = Math.min(p0.y, pos.y), maxY = Math.max(p0.y, pos.y); - DOM.setTransform(this._box, `translate(${minX}px,${minY}px)`); - - this._box.style.width = `${maxX - minX}px`; - this._box.style.height = `${maxY - minY}px`; + this._map._domRenderTaskQueue.add(() => { + if (this._box) { + DOM.setTransform(this._box, `translate(${minX}px,${minY}px)`); + + this._box.style.width = `${maxX - minX}px`; + this._box.style.height = `${maxY - minY}px`; + } + }); } mouseupWindow(e: MouseEvent, point: Point) { diff --git a/src/ui/map.js b/src/ui/map.js index e716a7e448e..fb615dd3d4e 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -320,6 +320,7 @@ class Map extends Camera { _collectResourceTiming: boolean; _optimizeForTerrain: boolean; _renderTaskQueue: TaskQueue; + _domRenderTaskQueue: TaskQueue; _controls: Array; _logoControl: IControl; _mapId: number; @@ -420,6 +421,7 @@ class Map extends Camera { this._collectResourceTiming = options.collectResourceTiming; this._optimizeForTerrain = options.optimizeForTerrain; this._renderTaskQueue = new TaskQueue(); + this._domRenderTaskQueue = new TaskQueue(); this._controls = []; this._mapId = uniqueId(); this._locale = extend({}, defaultLocale, options.locale); @@ -2526,6 +2528,7 @@ class Map extends Camera { this.painter.setBaseState(); this._renderTaskQueue.run(paintStartTimeStamp); + this._domRenderTaskQueue.run(paintStartTimeStamp); // A task queue callback may have fired a user event which may have removed the map if (this._removed) return; @@ -2770,6 +2773,7 @@ class Map extends Camera { this._frame = null; } this._renderTaskQueue.clear(); + this._domRenderTaskQueue.clear(); this.painter.destroy(); this.handlers.destroy(); delete this.handlers; diff --git a/src/ui/marker.js b/src/ui/marker.js index 795f20fe36c..a03fbf01588 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -507,7 +507,11 @@ export default class Marker extends Evented { this._pos = this._pos.round(); } - DOM.setTransform(this._element, `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px) ${pitch} ${rotation}`); + this._map._domRenderTaskQueue.add(() => { + if (this._element && this._pos && this._anchor) { + DOM.setTransform(this._element, `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px) ${pitch} ${rotation}`); + } + }); } /** diff --git a/src/ui/popup.js b/src/ui/popup.js index a3c8382a78a..c7db0632fa5 100644 --- a/src/ui/popup.js +++ b/src/ui/popup.js @@ -572,8 +572,12 @@ export default class Popup extends Evented { } const offsetedPos = pos.add(offset[anchor]).round(); - DOM.setTransform(this._container, `${anchorTranslate[anchor]} translate(${offsetedPos.x}px,${offsetedPos.y}px)`); - applyAnchorClass(this._container, anchor, 'popup'); + this._map._domRenderTaskQueue.add(() => { + if (this._container && anchor) { + DOM.setTransform(this._container, `${anchorTranslate[anchor]} translate(${offsetedPos.x}px,${offsetedPos.y}px)`); + applyAnchorClass(this._container, anchor, 'popup'); + } + }); } _focusFirstElement() { diff --git a/test/unit/ui/control/scale.test.js b/test/unit/ui/control/scale.test.js index 56623454e88..ea3b02c010f 100644 --- a/test/unit/ui/control/scale.test.js +++ b/test/unit/ui/control/scale.test.js @@ -6,6 +6,7 @@ import ScaleControl from '../../../../src/ui/control/scale_control.js'; test('ScaleControl appears in bottom-left by default', (t) => { const map = createMap(t); map.addControl(new ScaleControl()); + map._domRenderTaskQueue.run(); t.equal(map.getContainer().querySelectorAll('.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-scale').length, 1); t.end(); @@ -14,6 +15,7 @@ test('ScaleControl appears in bottom-left by default', (t) => { test('ScaleControl appears in the position specified by the position option', (t) => { const map = createMap(t); map.addControl(new ScaleControl(), 'top-left'); + map._domRenderTaskQueue.run(); t.equal(map.getContainer().querySelectorAll('.mapboxgl-ctrl-top-left .mapboxgl-ctrl-scale').length, 1); t.end(); @@ -24,11 +26,13 @@ test('ScaleControl should change unit of distance after calling setUnit', (t) => const scale = new ScaleControl(); const selector = '.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-scale'; map.addControl(scale); + map._domRenderTaskQueue.run(); let contents = map.getContainer().querySelector(selector).innerHTML; t.match(contents, /km/); scale.setUnit('imperial'); + map._domRenderTaskQueue.run(); contents = map.getContainer().querySelector(selector).innerHTML; t.match(contents, /mi/); t.end(); @@ -41,6 +45,7 @@ test('ScaleControl should respect the maxWidth regardless of the unit and actual const selector = '.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-scale'; map.addControl(scale); map.setZoom(12.5); + map._domRenderTaskQueue.run(); const el = map.getContainer().querySelector(selector); t.ok(parseFloat(el.style.width, 10) <= maxWidth, 'ScaleControl respects maxWidth'); diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index c8d546979dd..0bc996185ed 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -219,6 +219,7 @@ test('Marker anchor defaults to center', (t) => { const marker = new Marker() .setLngLat([0, 0]) .addTo(map); + map._domRenderTaskQueue.run(); t.ok(marker.getElement().classList.contains('mapboxgl-marker-anchor-center')); t.match(marker.getElement().style.transform, /translate\(-50%,-50%\)/); @@ -232,6 +233,7 @@ test('Marker anchors as specified by the anchor option', (t) => { const marker = new Marker({anchor: 'top'}) .setLngLat([0, 0]) .addTo(map); + map._domRenderTaskQueue.run(); t.ok(marker.getElement().classList.contains('mapboxgl-marker-anchor-top')); t.match(marker.getElement().style.transform, /translate\(-50%,0\)/); @@ -259,6 +261,7 @@ test('Popup offsets around default Marker', (t) => { .setLngLat([0, 0]) .setPopup(new Popup().setText('Test')) .addTo(map); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup().options.offset.bottom[1] < 0, 'popup is vertically offset somewhere above the tip'); t.ok(marker.getPopup().options.offset.top[1] === 0, 'popup is vertically offset at the tip'); @@ -296,34 +299,42 @@ test('Popup anchors around default Marker', (t) => { Object.defineProperty(marker.getPopup()._container, 'offsetHeight', {value: 100}); // marker should default to above since it has enough space + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-bottom'), 'popup anchors above marker'); // move marker to the top forcing the popup to below marker.setLngLat(map.unproject([mapHeight / 2, markerTop])); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-top'), 'popup anchors below marker'); // move marker to the right forcing the popup to the left marker.setLngLat(map.unproject([mapHeight - markerRight, mapHeight / 2])); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-right'), 'popup anchors left of marker'); // move marker to the left forcing the popup to the right marker.setLngLat(map.unproject([markerRight, mapHeight / 2])); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-left'), 'popup anchors right of marker'); // move marker to the top left forcing the popup to the bottom right marker.setLngLat(map.unproject([markerRight, markerTop])); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-top-left'), 'popup anchors bottom right of marker'); // move marker to the top right forcing the popup to the bottom left marker.setLngLat(map.unproject([mapHeight - markerRight, markerTop])); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-top-right'), 'popup anchors bottom left of marker'); // move marker to the bottom left forcing the popup to the top right marker.setLngLat(map.unproject([markerRight, mapHeight])); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-bottom-left'), 'popup anchors top right of marker'); // move marker to the bottom right forcing the popup to the top left marker.setLngLat(map.unproject([mapHeight - markerRight, mapHeight])); + map._domRenderTaskQueue.run(); t.ok(marker.getPopup()._container.classList.contains('mapboxgl-popup-anchor-bottom-right'), 'popup anchors top left of marker'); t.end(); @@ -725,11 +736,13 @@ test('Marker transforms rotation with the map', (t) => { const marker = new Marker({rotationAlignment: 'map'}) .setLngLat([0, 0]) .addTo(map); + map._domRenderTaskQueue.run(); const rotationRegex = /rotateZ\(-?([0-9]+)deg\)/; const initialRotation = marker.getElement().style.transform.match(rotationRegex)[1]; map.setBearing(map.getBearing() + 180); + map._domRenderTaskQueue.run(); const finalRotation = marker.getElement().style.transform.match(rotationRegex)[1]; t.notEqual(initialRotation, finalRotation); @@ -745,11 +758,13 @@ test('Marker transforms pitch with the map', (t) => { .addTo(map); map.setPitch(0); + map._domRenderTaskQueue.run(); const rotationRegex = /rotateX\(-?([0-9]+)deg\)/; const initialPitch = marker.getElement().style.transform.match(rotationRegex)[1]; map.setPitch(45); + map._domRenderTaskQueue.run(); const finalPitch = marker.getElement().style.transform.match(rotationRegex)[1]; t.notEqual(initialPitch, finalPitch); diff --git a/test/unit/ui/popup.test.js b/test/unit/ui/popup.test.js index 8ecd5e46630..be48373c1a2 100644 --- a/test/unit/ui/popup.test.js +++ b/test/unit/ui/popup.test.js @@ -393,6 +393,7 @@ test('Popup anchors as specified by the anchor option', (t) => { .setLngLat([0, 0]) .setText('Test') .addTo(map); + map._domRenderTaskQueue.run(); t.ok(popup.getElement().classList.contains('mapboxgl-popup-anchor-top-left')); t.end(); @@ -419,6 +420,7 @@ test('Popup anchors as specified by the anchor option', (t) => { .setLngLat([0, 0]) .setText('Test') .addTo(map); + map._domRenderTaskQueue.run(); Object.defineProperty(popup.getElement(), 'offsetWidth', {value: 100}); Object.defineProperty(popup.getElement(), 'offsetHeight', {value: 100}); @@ -426,6 +428,7 @@ test('Popup anchors as specified by the anchor option', (t) => { t.stub(map, 'project').returns(point); t.stub(map.transform, 'locationPoint3D').returns(point); popup.setLngLat([0, 0]); + map._domRenderTaskQueue.run(); t.ok(popup.getElement().classList.contains(`mapboxgl-popup-anchor-${anchor}`)); t.end(); @@ -440,6 +443,7 @@ test('Popup anchors as specified by the anchor option', (t) => { .setLngLat([0, 0]) .setText('Test') .addTo(map); + map._domRenderTaskQueue.run(); t.equal(popup.getElement().style.transform, transform); t.end(); @@ -457,12 +461,14 @@ test('Popup automatically anchors to top if its bottom offset would push it off- .setLngLat([0, 0]) .setText('Test') .addTo(map); + map._domRenderTaskQueue.run(); Object.defineProperty(popup.getElement(), 'offsetWidth', {value: containerWidth / 2}); Object.defineProperty(popup.getElement(), 'offsetHeight', {value: containerHeight / 2}); t.stub(map, 'project').returns(point); popup.setLngLat([0, 0]); + map._domRenderTaskQueue.run(); t.ok(popup.getElement().classList.contains('mapboxgl-popup-anchor-top')); t.end(); @@ -477,6 +483,7 @@ test('Popup is offset via a PointLike offset option', (t) => { .setLngLat([0, 0]) .setText('Test') .addTo(map); + map._domRenderTaskQueue.run(); t.equal(popup.getElement().style.transform, 'translate(0,0) translate(5px,10px)'); t.end(); @@ -491,6 +498,7 @@ test('Popup is offset via an object offset option', (t) => { .setLngLat([0, 0]) .setText('Test') .addTo(map); + map._domRenderTaskQueue.run(); t.equal(popup.getElement().style.transform, 'translate(0,0) translate(5px,10px)'); t.end(); @@ -505,6 +513,7 @@ test('Popup is offset via an incomplete object offset option', (t) => { .setLngLat([0, 0]) .setText('Test') .addTo(map); + map._domRenderTaskQueue.run(); t.equal(popup.getElement().style.transform, 'translate(-100%,0) translate(0px,0px)'); t.end();