Globe: Add hover on marker dot, Comment code

This commit is contained in:
2020-04-09 23:10:50 +02:00
parent 67902bc5a8
commit a4feadb80f
3 changed files with 114 additions and 78 deletions

View File

@@ -9,12 +9,12 @@ import GlobeVS from './globe-vs'
import GlobeFS from './globe-fs' import GlobeFS from './globe-fs'
function hexToRgb(hex) { function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? [ return result ? [
parseInt(result[1], 16) / 255, parseInt(result[1], 16) / 255,
parseInt(result[2], 16) / 255, parseInt(result[2], 16) / 255,
parseInt(result[3], 16) / 255 parseInt(result[3], 16) / 255
] : [0,0,0]; ] : [0,0,0]
} }
function lonLatToVector3(lng, lat) { function lonLatToVector3(lng, lat) {
@@ -22,30 +22,29 @@ function lonLatToVector3(lng, lat) {
theta = (lng+180)*(Math.PI/180), theta = (lng+180)*(Math.PI/180),
x = -( Math.sin(phi) * Math.cos(theta) ), x = -( Math.sin(phi) * Math.cos(theta) ),
z = Math.sin(phi) * Math.sin(theta), z = Math.sin(phi) * Math.sin(theta),
y = Math.cos(phi); y = Math.cos(phi)
return [x,y,z]; return [x,y,z]
} }
class WebglGlobe { class WebglGlobe {
// Constructor // Constructor
constructor (options) { constructor (options) {
// Camera position cache
this.cameraX = 0
this.cameraY = 0
this.cameraZ = 0
//camera position cache this.options = options
this.cameraX = 0; this.$el = options.el
this.cameraY = 0;
this.cameraZ = 0;
this.options = options;
this.$el = options.el;
this.cities = options.markers this.cities = options.markers
let gl; let gl
let canvas = document.createElement('canvas') let canvas = document.createElement('canvas')
try { gl = canvas.getContext("webgl"); } try { gl = canvas.getContext('webgl') }
catch (x) { catch (x) {
try { gl = canvas.getContext("experimental-webgl"); } try { gl = canvas.getContext('experimental-webgl') }
catch (x) { gl = null; } catch (x) { gl = null }
} }
this.supportWebgl = gl !== null; this.supportWebgl = gl !== null
if (this.supportWebgl) { if (this.supportWebgl) {
this.buildWebglScene() this.buildWebglScene()
this.resize() this.resize()
@@ -58,14 +57,15 @@ class WebglGlobe {
this.renderer = new Renderer({ this.renderer = new Renderer({
alpha: this.alpha, alpha: this.alpha,
antialias: window.innerWidth < 768 ? true : false,// this.antialias, antialias: window.innerWidth < 768 ? true : false,// this.antialias,
}); })
this.$el.appendChild(this.renderer.canvas); this.$el.appendChild(this.renderer.canvas)
// Load texture
this.texture = Texture.fromUrl(this.renderer.gl, this.options.texture, { this.texture = Texture.fromUrl(this.renderer.gl, this.options.texture, {
loaded: () => { loaded: () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.imageLoaded = true; this.imageLoaded = true
}) })
}) })
} }
@@ -73,6 +73,7 @@ class WebglGlobe {
this.scene = new Container() this.scene = new Container()
// Setup camera
this.camera = new Camera({ this.camera = new Camera({
fov: settings.fov, fov: settings.fov,
near: 1, near: 1,
@@ -83,14 +84,14 @@ class WebglGlobe {
lookAt: [0,0,0], lookAt: [0,0,0],
position: [0,0, (this.options.cameraDistance) / 2 / Math.tan(Math.PI * settings.fov / 360)], position: [0,0, (this.options.cameraDistance) / 2 / Math.tan(Math.PI * settings.fov / 360)],
pointerParent: this.$el pointerParent: this.$el
}); })
this.camera.lookAt = vec3.create() this.camera.lookAt = vec3.create()
this.inverseViewProjectionMatrix = mat4.create() this.inverseViewProjectionMatrix = mat4.create()
this.viewProjectionMatrix = mat4.create() this.viewProjectionMatrix = mat4.create()
this.cameraDirection = vec3.create(); this.cameraDirection = vec3.create()
this.cameraPosition = vec3.create(); this.cameraPosition = vec3.create()
this.globeMesh = new Mesh(); this.globeMesh = new Mesh()
this.globeMesh.material = new Material(this.renderer.gl, { this.globeMesh.material = new Material(this.renderer.gl, {
uniforms: { uniforms: {
tInput: this.texture tInput: this.texture
@@ -108,47 +109,63 @@ class WebglGlobe {
this.markers = [] this.markers = []
let markers = this.cities let markers = this.cities
// Instance all markers
for (let i = 0; i < markers.length; i++) { for (let i = 0; i < markers.length; i++) {
let markerMesh = new Mesh(); let markerMesh = new Mesh();
markerMesh.material = new Material(this.renderer.gl, { markerMesh.material = new Material(this.renderer.gl, {
blend: true, blend: true,
uniforms: { uniforms: {
color: hexToRgb( "#FF6C89" ), color: hexToRgb('#FF6C89'),
alpha: 0, alpha: 0,
}, }
}) })
markerMesh.geometry = new SphereGeometryBuffer(this.renderer.gl, { markerMesh.geometry = new SphereGeometryBuffer(this.renderer.gl, {
radius: 0.01, radius: 0.01,
widthSegments: 10, heightSegments: 10 widthSegments: 10, heightSegments: 10
}) })
let p = lonLatToVector3( markers[i].lng, markers[i].lat )
// Position marker
let p = lonLatToVector3( markers[i].lng, markers[i].lat )
markerMesh.position[0] = p[0] markerMesh.position[0] = p[0]
markerMesh.position[1] = p[1] markerMesh.position[1] = p[1]
markerMesh.position[2] = p[2] markerMesh.position[2] = p[2]
this.scene.add( markerMesh ) this.scene.add( markerMesh )
// Wrap marker in link
let el = document.createElement('a') let el = document.createElement('a')
el.setAttribute('href', '/location/' + markers[i].countrySlug + '/' + markers[i].slug ) el.setAttribute('href', '/location/' + markers[i].countrySlug + '/' + markers[i].slug )
el.setAttribute('sapper-noscroll','') el.setAttribute('sapper-noscroll','')
// Add label
let span = document.createElement('span') let span = document.createElement('span')
span.classList.add('marker__label') span.classList.add('marker__label')
el.appendChild(span) el.appendChild(span)
// Add city label
let spanCity = document.createElement('span')
spanCity.classList.add('marker__city')
spanCity.innerHTML = markers[i].name
span.appendChild(spanCity)
// Add country label
let spanCountry = document.createElement('span')
spanCountry.classList.add('marker__country')
spanCountry.innerHTML = markers[i].countryName
span.appendChild(spanCountry)
// Add class
el.classList.add('marker')
// Callback on click
el.addEventListener('click', () => { el.addEventListener('click', () => {
this.options.onLinkClicked && this.options.onLinkClicked() this.options.onLinkClicked && this.options.onLinkClicked()
}) })
let spanCity = document.createElement('span') // Add class on hover
spanCity.classList.add('marker__city') el.addEventListener('mouseenter', () => el.classList.add('hover'))
spanCity.innerHTML = markers[i].name; el.addEventListener('animationend', () => el.classList.remove('hover'))
span.appendChild(spanCity)
let spanCountry = document.createElement('span')
spanCountry.classList.add('marker__country')
spanCountry.innerHTML = markers[i].countryName;
span.appendChild(spanCountry)
el.classList.add('marker') // Append marker to HTML
this.$el.appendChild( el ) this.$el.appendChild( el )
this.markers.push({ this.markers.push({
mesh: markerMesh, mesh: markerMesh,
@@ -161,8 +178,8 @@ class WebglGlobe {
// Resize method // Resize method
resize () { resize () {
this.width = this.$el.clientWidth; this.width = this.$el.clientWidth
this.height = this.$el.clientHeight; this.height = this.$el.clientHeight
// console.log('GLOBE RESIZE', this.width, this.height) // console.log('GLOBE RESIZE', this.width, this.height)
@@ -171,12 +188,11 @@ class WebglGlobe {
if (!this.supportWebgl) { if (!this.supportWebgl) {
return return
} }
this.renderer.setPixelRatio(window.innerWidth < 768 ? 1 : settings.devicePixelRatio); this.renderer.setPixelRatio(window.innerWidth < 768 ? 1 : settings.devicePixelRatio)
this.renderer.resize(settings.resolution[0], settings.resolution[1]) this.renderer.resize(settings.resolution[0], settings.resolution[1])
this.camera.aspect = settings.resolution[0] / settings.resolution[1] this.camera.aspect = settings.resolution[0] / settings.resolution[1]
this.camera.updateProjectionMatrix() this.camera.updateProjectionMatrix()
this.camera.lookAt[0] = 0 this.camera.lookAt[0] = 0
this.camera.lookAt[1] = 0 this.camera.lookAt[1] = 0
this.camera.lookAt[2] = 0 this.camera.lookAt[2] = 0
@@ -186,19 +202,18 @@ class WebglGlobe {
this.camera.position[0] = 0 this.camera.position[0] = 0
this.camera.position[1] = 0 this.camera.position[1] = 0
this.camera.position[2] = (3) / 2 / Math.tan(Math.PI * settings.fov / 360) this.camera.position[2] = (3) / 2 / Math.tan(Math.PI * settings.fov / 360)
this.camera.render(); this.camera.render()
vec3.set(this.cameraPosition, this.camera.worldMatrix[12], this.camera.worldMatrix[13], this.camera.worldMatrix[14]);
vec3.copy(this.cameraDirection, this.camera.lookAt);
vec3.subtract(this.cameraDirection, this.cameraDirection, this.cameraPosition);
vec3.normalize(this.cameraDirection, this.cameraDirection);
mat4.copy(this.viewProjectionMatrix, this.camera.projectionMatrix);
mat4.multiply(this.viewProjectionMatrix, this.viewProjectionMatrix, this.camera.inverseWorldMatrix);
vec3.set(this.cameraPosition, this.camera.worldMatrix[12], this.camera.worldMatrix[13], this.camera.worldMatrix[14])
vec3.copy(this.cameraDirection, this.camera.lookAt)
vec3.subtract(this.cameraDirection, this.cameraDirection, this.cameraPosition)
vec3.normalize(this.cameraDirection, this.cameraDirection)
mat4.copy(this.viewProjectionMatrix, this.camera.projectionMatrix)
mat4.multiply(this.viewProjectionMatrix, this.viewProjectionMatrix, this.camera.inverseWorldMatrix)
let refPos = vec3.create() let refPos = vec3.create()
vec3.set(refPos, 0, 1, 0 ) vec3.set(refPos, 0, 1, 0 )
vec3.transformMat4(refPos, refPos, this.viewProjectionMatrix); vec3.transformMat4(refPos, refPos, this.viewProjectionMatrix)
let refx = ( (refPos[0] + 1) / 2) * this.width let refx = ( (refPos[0] + 1) / 2) * this.width
let refy = (1. - (refPos[1] + 1) / 2) * this.height let refy = (1. - (refPos[1] + 1) / 2) * this.height
@@ -209,33 +224,32 @@ class WebglGlobe {
let dir2d2 = vec2.clone(dir2, dir2) let dir2d2 = vec2.clone(dir2, dir2)
vec2.subtract( dir2d2, dir2d2, center2 ) vec2.subtract( dir2d2, dir2d2, center2 )
this.circleScreenSize = vec2.length( dir2d2 ) * 1.04 this.circleScreenSize = vec2.length( dir2d2 ) * 1.04
} }
// Update // Update
update () { update () {
if (!this.supportWebgl) { if (!this.supportWebgl) {
return; return
} }
//manually call this as we prevent ithe camera from update between passes // Manually call this as we prevent the camera from update between passes
this.camera.update(); this.camera.update()
vec3.set(this.cameraPosition, this.camera.worldMatrix[12], this.camera.worldMatrix[13], this.camera.worldMatrix[14]); vec3.set(this.cameraPosition, this.camera.worldMatrix[12], this.camera.worldMatrix[13], this.camera.worldMatrix[14])
vec3.copy(this.cameraDirection, this.camera.lookAt); vec3.copy(this.cameraDirection, this.camera.lookAt)
vec3.subtract(this.cameraDirection, this.cameraDirection, this.cameraPosition); vec3.subtract(this.cameraDirection, this.cameraDirection, this.cameraPosition)
vec3.normalize(this.cameraDirection, this.cameraDirection); vec3.normalize(this.cameraDirection, this.cameraDirection)
mat4.copy(this.viewProjectionMatrix, this.camera.projectionMatrix); mat4.copy(this.viewProjectionMatrix, this.camera.projectionMatrix)
mat4.multiply(this.viewProjectionMatrix, this.viewProjectionMatrix, this.camera.inverseWorldMatrix); mat4.multiply(this.viewProjectionMatrix, this.viewProjectionMatrix, this.camera.inverseWorldMatrix)
mat4.invert(this.inverseViewProjectionMatrix, this.viewProjectionMatrix); mat4.invert(this.inverseViewProjectionMatrix, this.viewProjectionMatrix)
let needsUpdate = false; let needsUpdate = false
if (this.cameraX != this.camera.worldMatrix[12] || if (this.cameraX != this.camera.worldMatrix[12] ||
this.cameraY != this.camera.worldMatrix[13] || this.cameraY != this.camera.worldMatrix[13] ||
this.cameraZ != this.camera.worldMatrix[14]) { this.cameraZ != this.camera.worldMatrix[14]) {
this.cameraX = this.camera.worldMatrix[12]; this.cameraX = this.camera.worldMatrix[12]
this.cameraY = this.camera.worldMatrix[13]; this.cameraY = this.camera.worldMatrix[13]
this.cameraZ = this.camera.worldMatrix[14]; this.cameraZ = this.camera.worldMatrix[14]
needsUpdate = true; needsUpdate = true
} }
if (!needsUpdate && this.imageLoaded) { if (!needsUpdate && this.imageLoaded) {
@@ -247,7 +261,7 @@ class WebglGlobe {
let screenPos = vec3.create() let screenPos = vec3.create()
this.markers.forEach((marker, i) => { this.markers.forEach((marker, i) => {
vec3.set(screenPos, marker.position[0], marker.position[1], marker.position[2] ) vec3.set(screenPos, marker.position[0], marker.position[1], marker.position[2] )
vec3.transformMat4(screenPos, screenPos, this.viewProjectionMatrix); vec3.transformMat4(screenPos, screenPos, this.viewProjectionMatrix)
let x = ( (screenPos[0] + 1) / 2) * this.width let x = ( (screenPos[0] + 1) / 2) * this.width
let y = (1. - (screenPos[1] + 1) / 2) * this.height let y = (1. - (screenPos[1] + 1) / 2) * this.height
@@ -259,7 +273,7 @@ class WebglGlobe {
let V = vec3.create() let V = vec3.create()
vec3.set( V, marker.position[0], marker.position[1], marker.position[2] ) vec3.set( V, marker.position[0], marker.position[1], marker.position[2] )
vec3.subtract( V, V, this.cameraPosition ) vec3.subtract( V, V, this.cameraPosition )
vec3.normalize( V, V ); vec3.normalize( V, V )
//behind //behind
if ( vec3.dot( V, N ) * -1 < 0 ) { if ( vec3.dot( V, N ) * -1 < 0 ) {
@@ -269,15 +283,15 @@ class WebglGlobe {
vec2.set( center, this.width/2, this.height/2 ) vec2.set( center, this.width/2, this.height/2 )
let dir2d = vec2.clone(dir, dir) let dir2d = vec2.clone(dir, dir)
vec2.subtract( dir2d, dir2d, center ) vec2.subtract( dir2d, dir2d, center )
vec2.normalize( dir2d, dir2d ); vec2.normalize( dir2d, dir2d )
vec2.scale( dir2d, dir2d, this.circleScreenSize ); vec2.scale( dir2d, dir2d, this.circleScreenSize )
vec2.add(dir2d, dir2d, center) vec2.add(dir2d, dir2d, center)
marker.el.style.transform = `translate(${dir2d[0]}px, ${dir2d[1]}px) translateZ(0)`; marker.el.style.transform = `translate(${dir2d[0]}px, ${dir2d[1]}px) translateZ(0)`
marker.el.classList.remove('is-active') marker.el.classList.remove('is-active')
} }
else { else {
//vec3.dot( V, N ) * -1 < 0 ? (1 - vec3.dot( V, N ) / 0.1 ) : 1;//Math.max( 0, vec3.dot( N, V ) ); //vec3.dot( V, N ) * -1 < 0 ? (1 - vec3.dot( V, N ) / 0.1 ) : 1;//Math.max( 0, vec3.dot( N, V ) );
marker.el.style.transform = `translate(${x}px, ${y}px) translateZ(0)`; marker.el.style.transform = `translate(${x}px, ${y}px) translateZ(0)`
marker.el.classList.add('is-active') marker.el.classList.add('is-active')
} }
// marker.el.style.opacity = 1. // marker.el.style.opacity = 1.
@@ -285,7 +299,7 @@ class WebglGlobe {
this.renderer.clearColor(0,0,0,0) this.renderer.clearColor(0,0,0,0)
this.renderer.clear() this.renderer.clear()
this.renderer.render(this.scene, this.camera); this.renderer.render(this.scene, this.camera)
} }
} }

View File

@@ -112,3 +112,17 @@
0% { transform: translateY(0); } 0% { transform: translateY(0); }
50% { transform: translateY(-3px); } 50% { transform: translateY(-3px); }
} }
/*
** Globe
*/
// Marker
@keyframes globeMarkerPulse {
0% {
box-shadow: 0 0 0 0 rgba($color-secondary, 1);
}
100% {
box-shadow: 0 0 0 32px rgba(#fff, 0);
}
}

View File

@@ -26,6 +26,7 @@
// Marker // Marker
.marker { .marker {
position: absolute; position: absolute;
z-index: 2;
cursor: pointer; cursor: pointer;
display: block; display: block;
top: 0; top: 0;
@@ -35,11 +36,18 @@
border-radius: 100%; border-radius: 100%;
opacity: 1; opacity: 1;
background: #ff6c89; background: #ff6c89;
will-change: transform;
span { span {
transition: color 0.4s $ease-quart; transition: color 0.4s $ease-quart;
} }
// Hover glow effect
&.hover {
animation: globeMarkerPulse 1s;
}
// Label
&__label { &__label {
position: absolute; position: absolute;
bottom: -230%; bottom: -230%;