Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Marker's "opacity" option and "setOpacity" method #3620

Merged
merged 11 commits into from
Jan 28, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### ✨ Features and improvements

- Add "opacity" option and "setOpacity" method to Marker ([#3620](https://github.com/maplibre/maplibre-gl-js/pull/3620))
- _...Add new stuff here..._

### 🐞 Bug fixes
Expand Down
91 changes: 91 additions & 0 deletions src/ui/marker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,44 @@ describe('marker', () => {
map.remove();
});

test('Sets default opacity if it\'s not provided as option', () => {
const map = createMap();
const marker = new Marker()
.setLngLat([0, 0])
.addTo(map);
expect(marker.getElement().style.opacity).toMatch('1');
map.remove();
});

test('Sets opacity according to options.opacity', () => {
const map = createMap();
const marker = new Marker({opacity: '0.7'})
.setLngLat([0, 0])
.addTo(map);
expect(marker.getElement().style.opacity).toMatch('.7');
map.remove();
});

test('Changes opacity to a new value provided by setOpacity', () => {
const map = createMap();
const marker = new Marker({opacity: '0.7'})
.setLngLat([0, 0])
.addTo(map);
marker.setOpacity('0.6');
expect(marker.getElement().style.opacity).toMatch('.6');
map.remove();
});

test('Resets opacity to default when setOpacity is called without arguments', () => {
const map = createMap();
const marker = new Marker({opacity: '0.7'})
.setLngLat([0, 0])
.addTo(map);
marker.setOpacity();
expect(marker.getElement().style.opacity).toBe('1');
map.remove();
});

test('Marker changes opacity behind terrain and when terrain is removed', () => {
const map = createMap();
map.transform.lngLatToCameraDepth = () => .95; // Mocking distance to marker
Expand Down Expand Up @@ -870,4 +908,57 @@ describe('marker', () => {

map.remove();
});

test('Applies options.opacity when 3d terrain is enabled and marker is in clear view', () => {
const map = createMap();
map.transform.lngLatToCameraDepth = () => .95; // Mocking distance to marker
const marker = new Marker({opacity: '0.7'})
.setLngLat([0, 0])
.addTo(map);

map.terrain = {
getElevationForLngLatZoom: () => 0,
depthAtPoint: () => .95
} as any as Terrain;
map.fire('terrain');

expect(marker.getElement().style.opacity).toMatch('.7');
map.remove();
});

test('Applies options.opacityWhenCovered when marker is hidden by 3d terrain', () => {
const map = createMap();
map.transform.lngLatToCameraDepth = () => .95; // Mocking distance to marker
const marker = new Marker({opacity: '0.7', opacityWhenCovered: '0.3'})
.setLngLat([0, 0])
.addTo(map);

map.terrain = {
getElevationForLngLatZoom: () => 0,
depthAtPoint: () => .92
} as any as Terrain;
map.fire('terrain');

expect(marker.getElement().style.opacity).toMatch('0.3');
map.remove();
});

test('Applies new "opacityWhenCovered" provided by setOpacity when marker is hidden by 3d terrain', async () => {
const map = createMap();
map.transform.lngLatToCameraDepth = () => .95; // Mocking distance to marker
const marker = new Marker({opacityWhenCovered: '0.15'})
.setLngLat([0, 0])
.addTo(map);

map.terrain = {
getElevationForLngLatZoom: () => 0,
depthAtPoint: () => .92
} as any as Terrain;
map.fire('terrain');

marker.setOpacity(undefined, '0.35');

expect(marker.getElement().style.opacity).toMatch('0.35');
map.remove();
});
});
45 changes: 42 additions & 3 deletions src/ui/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ type MarkerOptions = {
* @defaultValue 'auto'
*/
pitchAlignment?: Alignment;
/**
* Marker's opacity when it's in clear view (not behind 3d terrain)
* @defaultValue 1
*/
opacity?: string;
/**
* Marker's opacity when it's behind 3d terrain
* @defaultValue 0.2
*/
opacityWhenCovered?: string;
};

/**
Expand Down Expand Up @@ -128,6 +138,8 @@ export class Marker extends Evented {
_pitchAlignment: Alignment;
_rotationAlignment: Alignment;
_originalTabIndex: string; // original tabindex of _element
_opacity: string;
HarelM marked this conversation as resolved.
Show resolved Hide resolved
_opacityWhenCovered: string;
_opacityTimeout: ReturnType<typeof setTimeout>;

/**
Expand All @@ -146,6 +158,8 @@ export class Marker extends Evented {
this._rotation = options && options.rotation || 0;
this._rotationAlignment = options && options.rotationAlignment || 'auto';
this._pitchAlignment = options && options.pitchAlignment && options.pitchAlignment !== 'auto' ? options.pitchAlignment : this._rotationAlignment;
this.setOpacity(); // set default opacity
this.setOpacity(options?.opacity, options?.opacityWhenCovered);

if (!options || !options.element) {
this._defaultMarker = true;
Expand Down Expand Up @@ -509,7 +523,7 @@ export class Marker extends Evented {
_updateOpacity(force: boolean = false) {
const terrain = this._map.terrain;
if (!terrain) {
if (this._element.style.opacity === '0.2') { this._element.style.opacity = '1'; }
if (this._element.style.opacity !== this._opacity) { this._element.style.opacity = this._opacity; }
return;
}
if (force) {
Expand All @@ -531,7 +545,7 @@ export class Marker extends Evented {

const forgiveness = .006;
if (markerDistance - terrainDistance < forgiveness) {
this._element.style.opacity = '1';
this._element.style.opacity = this._opacity;
return;
}
// If the base is obscured, use the offset to check if the marker's center is obscured.
Expand All @@ -540,7 +554,8 @@ export class Marker extends Evented {
const terrainDistanceCenter = map.terrain.depthAtPoint(new Point(this._pos.x, this._pos.y - this._offset.y));
const markerDistanceCenter = map.transform.lngLatToCameraDepth(this._lngLat, elevation + elevationToCenter);
// Display at full opacity if center is visible.
this._element.style.opacity = (markerDistanceCenter - terrainDistanceCenter > forgiveness) ? '0.2' : '1.0';
const centerIsInvisible = markerDistanceCenter - terrainDistanceCenter > forgiveness;
this._element.style.opacity = centerIsInvisible ? this._opacityWhenCovered : this._opacity;
}

_update = (e?: { type: 'move' | 'moveend' | 'terrain' | 'render' }) => {
Expand Down Expand Up @@ -797,4 +812,28 @@ export class Marker extends Evented {
getPitchAlignment(): Alignment {
return this._pitchAlignment;
}

/**
* Sets the `opacity` and `opacityWhenCovered` properties of the marker.
HarelM marked this conversation as resolved.
Show resolved Hide resolved
* When called without arguments, resets opacity and opacityWhenCovered to defaults
* @param opacity - Sets the `opacity` property of the marker.
* @param opacityWhenCovered - Sets the `opacityWhenCovered` property of the marker.
* @returns `this`
*/
setOpacity(opacity?: string, opacityWhenCovered?: string): this {
if (opacity === undefined && opacityWhenCovered === undefined) {
this._opacity = '1';
this._opacityWhenCovered = '0.2';
}
if (opacity !== undefined) {
this._opacity = opacity;
}
if (opacityWhenCovered !== undefined) {
this._opacityWhenCovered = opacityWhenCovered;
}
if (this._map) {
this._updateOpacity(true);
}
return this;
}
}
2 changes: 1 addition & 1 deletion test/build/min.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('test min build', () => {
const decreaseQuota = 4096;

// feel free to update this value after you've checked that it has changed on purpose :-)
const expectedBytes = 772222;
const expectedBytes = 772550;

expect(actualBytes - expectedBytes).toBeLessThan(increaseQuota);
expect(expectedBytes - actualBytes).toBeLessThan(decreaseQuota);
Expand Down