⚠️ The interactive globe has arrived (WIP but pretty good)
- Control the width/height of the globe via CSS
This commit is contained in:
@@ -247,6 +247,8 @@ class Camera extends Object3d {
|
||||
|
||||
this._isPointerDown = true;
|
||||
|
||||
this._pointerParent.classList.add('is-grabbing')
|
||||
|
||||
this.touchEvent = TOUCH ? (event.touches[0] || event.changedTouches[0]) : event;
|
||||
|
||||
this.touchEventPageX = this.touchEvent.pageX;
|
||||
@@ -273,7 +275,7 @@ class Camera extends Object3d {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
// event.preventDefault();
|
||||
|
||||
this.touchEvent = TOUCH ? (event.touches[0] || event.changedTouches[0]) : event;
|
||||
this.touchEventPageX = this.touchEvent.pageX;
|
||||
@@ -281,6 +283,7 @@ class Camera extends Object3d {
|
||||
this.touchEventPageX -= window.pageXOffset || document.documentElement.scrollLeft;
|
||||
this.touchEventPageY -= window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
|
||||
if (this.isRightClick) {
|
||||
this.pointerXMove = this.startPointerX + (this.touchEventPageX - this.pointerXDown);
|
||||
this.pointerYMove = this.startPointerY + (this.touchEventPageY - this.pointerYDown);
|
||||
@@ -291,6 +294,11 @@ class Camera extends Object3d {
|
||||
this.theta = this.thetaDown + ( this.pointerXOrbiter / this.winWidth * 2 * Math.PI);
|
||||
this.phi = this.phiDown + ( this.pointerYOrbiter / this.winHeight * 2 * Math.PI * -1);
|
||||
this.phi = Math.max( this._minPolarAngle, Math.min( this._maxPolarAngle, this.phi ) );
|
||||
|
||||
if( TOUCH ) {
|
||||
this.phi = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -299,6 +307,7 @@ class Camera extends Object3d {
|
||||
_onPointerUp() {
|
||||
this._isPointerDown = false;
|
||||
this.isRightClick = false;
|
||||
this._pointerParent.classList.remove('is-grabbing')
|
||||
}
|
||||
|
||||
update(force) {
|
||||
|
||||
@@ -218,6 +218,7 @@ Texture.fromUrl = function(gl, url, options) {
|
||||
img.onload = null;
|
||||
img.onerror = null;
|
||||
TEXTURE_CACHE[url] = img;
|
||||
options && options.loaded && options.loaded()
|
||||
texture.bindImage(img);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ function hexToRgb(hex) {
|
||||
parseInt(result[2], 16) / 255,
|
||||
parseInt(result[3], 16) / 255
|
||||
] : [0,0,0];
|
||||
}
|
||||
}
|
||||
|
||||
function lonLatToVector3(lng, lat) {
|
||||
var phi = (90-lat)*(Math.PI/180),
|
||||
@@ -29,6 +29,12 @@ function lonLatToVector3(lng, lat) {
|
||||
class WebglGlobe {
|
||||
// Constructor
|
||||
constructor (options) {
|
||||
|
||||
//camera position cache
|
||||
this.cameraX = 0;
|
||||
this.cameraY = 0;
|
||||
this.cameraZ = 0;
|
||||
|
||||
this.options = options;
|
||||
this.$el = options.el;
|
||||
this.cities = options.markers
|
||||
@@ -51,11 +57,19 @@ class WebglGlobe {
|
||||
buildWebglScene () {
|
||||
this.renderer = new Renderer({
|
||||
alpha: this.alpha,
|
||||
antialias: this.antialias,
|
||||
antialias: window.innerWidth < 768 ? true : false,// this.antialias,
|
||||
});
|
||||
this.$el.appendChild(this.renderer.canvas);
|
||||
|
||||
this.texture = Texture.fromUrl(this.renderer.gl, this.options.texture)
|
||||
this.texture = Texture.fromUrl(this.renderer.gl, this.options.texture, {
|
||||
loaded: ()=>{
|
||||
requestAnimationFrame(()=>{
|
||||
requestAnimationFrame(()=>{
|
||||
this.imageLoaded = true;
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.scene = new Container()
|
||||
|
||||
@@ -67,7 +81,8 @@ class WebglGlobe {
|
||||
orbitControl: true,
|
||||
firstPerson: false,
|
||||
lookAt: [0,0,0],
|
||||
position: [0,0, (3) / 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
|
||||
});
|
||||
this.camera.lookAt = vec3.create()
|
||||
this.inverseViewProjectionMatrix = mat4.create()
|
||||
@@ -90,7 +105,6 @@ class WebglGlobe {
|
||||
})
|
||||
this.scene.add( this.globeMesh )
|
||||
|
||||
|
||||
this.markers = []
|
||||
let markers = this.cities
|
||||
|
||||
@@ -108,18 +122,32 @@ class WebglGlobe {
|
||||
widthSegments: 10, heightSegments: 10
|
||||
})
|
||||
let p = lonLatToVector3( markers[i].lng, markers[i].lat )
|
||||
markerMesh.position[0] = p[0] * 1.
|
||||
markerMesh.position[1] = p[1] * 1.
|
||||
markerMesh.position[2] = p[2] * 1.
|
||||
|
||||
markerMesh.position[0] = p[0]
|
||||
markerMesh.position[1] = p[1]
|
||||
markerMesh.position[2] = p[2]
|
||||
this.scene.add( markerMesh )
|
||||
|
||||
let el = document.createElement('div')
|
||||
let el = document.createElement('a')
|
||||
el.setAttribute('href', '/location/' + markers[i].countrySlug + '/' + markers[i].slug )
|
||||
el.setAttribute('sapper-noscroll','')
|
||||
let span = document.createElement('span')
|
||||
span.innerHTML = markers[i].name
|
||||
span.classList.add('marker__label')
|
||||
el.appendChild(span)
|
||||
|
||||
el.addEventListener('click', ()=>{
|
||||
alert('click on ' + markers[i].name)
|
||||
this.options.onLinkClicked && this.options.onLinkClicked()
|
||||
})
|
||||
|
||||
let spanCity = document.createElement('span')
|
||||
spanCity.classList.add('marker__city')
|
||||
spanCity.innerHTML = markers[i].name;
|
||||
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')
|
||||
this.$el.appendChild( el )
|
||||
this.markers.push({
|
||||
@@ -135,12 +163,15 @@ class WebglGlobe {
|
||||
resize () {
|
||||
this.width = this.$el.clientWidth;
|
||||
this.height = this.$el.clientHeight;
|
||||
|
||||
// console.log('GLOBE RESIZE', this.width, this.height)
|
||||
|
||||
settings.resolution[0] = this.width //* settings.devicePixelRatio
|
||||
settings.resolution[1] = this.height //* settings.devicePixelRatio
|
||||
if (!this.supportWebgl) {
|
||||
return
|
||||
}
|
||||
this.renderer.setPixelRatio(settings.devicePixelRatio);
|
||||
this.renderer.setPixelRatio(window.innerWidth < 768 ? 1 : settings.devicePixelRatio);
|
||||
this.renderer.resize(settings.resolution[0], settings.resolution[1])
|
||||
this.camera.aspect = settings.resolution[0] / settings.resolution[1]
|
||||
this.camera.updateProjectionMatrix()
|
||||
@@ -168,13 +199,13 @@ class WebglGlobe {
|
||||
let refPos = vec3.create()
|
||||
vec3.set(refPos, 0, 1, 0 )
|
||||
vec3.transformMat4(refPos, refPos, this.viewProjectionMatrix);
|
||||
let refx = ( (refPos[0] + 1) / 2) * window.innerWidth
|
||||
let refy = (1. - (refPos[1] + 1) / 2) * window.innerHeight
|
||||
let refx = ( (refPos[0] + 1) / 2) * this.width
|
||||
let refy = (1. - (refPos[1] + 1) / 2) * this.height
|
||||
|
||||
let dir2 = vec2.create()
|
||||
vec2.set( dir2, refx, refy )
|
||||
let center2 = vec2.create()
|
||||
vec2.set( center2, window.innerWidth/2, window.innerHeight/2 )
|
||||
vec2.set( center2, this.width/2, this.height/2 )
|
||||
let dir2d2 = vec2.clone(dir2, dir2)
|
||||
vec2.subtract( dir2d2, dir2d2, center2 )
|
||||
this.circleScreenSize = vec2.length( dir2d2 ) * 1.04
|
||||
@@ -187,11 +218,6 @@ class WebglGlobe {
|
||||
return;
|
||||
}
|
||||
|
||||
// this.currPointer[0] += ( this.pointerRatio[0] - this.currPointer[0]) * 0.05
|
||||
// this.currPointer[1] += ( this.pointerRatio[1] - this.currPointer[1]) * 0.05
|
||||
// this.globeMesh.rotation[1] = this.currPointer[0]
|
||||
// this.globeMesh.rotation[2] = this.currPointer[1]
|
||||
|
||||
//manually call this as we prevent ithe camera from update between passes
|
||||
this.camera.update();
|
||||
vec3.set(this.cameraPosition, this.camera.worldMatrix[12], this.camera.worldMatrix[13], this.camera.worldMatrix[14]);
|
||||
@@ -202,15 +228,29 @@ class WebglGlobe {
|
||||
mat4.multiply(this.viewProjectionMatrix, this.viewProjectionMatrix, this.camera.inverseWorldMatrix);
|
||||
mat4.invert(this.inverseViewProjectionMatrix, this.viewProjectionMatrix);
|
||||
|
||||
let needsUpdate = false;
|
||||
if (this.cameraX != this.camera.worldMatrix[12] ||
|
||||
this.cameraY != this.camera.worldMatrix[13] ||
|
||||
this.cameraZ != this.camera.worldMatrix[14]) {
|
||||
this.cameraX = this.camera.worldMatrix[12];
|
||||
this.cameraY = this.camera.worldMatrix[13];
|
||||
this.cameraZ = this.camera.worldMatrix[14];
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (!needsUpdate && this.imageLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
// console.log('RENDER WEBGL')
|
||||
|
||||
let screenPos = vec3.create()
|
||||
this.markers.forEach((marker, i)=>{
|
||||
vec3.set(screenPos, marker.position[0], marker.position[1], marker.position[2] )
|
||||
vec3.transformMat4(screenPos, screenPos, this.viewProjectionMatrix);
|
||||
|
||||
let x = ( (screenPos[0] + 1) / 2) * window.innerWidth
|
||||
let y = (1. - (screenPos[1] + 1) / 2) * window.innerHeight
|
||||
let x = ( (screenPos[0] + 1) / 2) * this.width
|
||||
let y = (1. - (screenPos[1] + 1) / 2) * this.height
|
||||
|
||||
let N = vec3.create()
|
||||
vec3.set( N, marker.position[0], marker.position[1], marker.position[2] )
|
||||
@@ -222,11 +262,11 @@ class WebglGlobe {
|
||||
vec3.normalize( V, V );
|
||||
|
||||
//behind
|
||||
if ( vec3.dot( V, N ) * -1 < 0) {
|
||||
if ( vec3.dot( V, N ) * -1 < 0 ) {
|
||||
let dir = vec2.create()
|
||||
vec2.set( dir, x, y )
|
||||
let center = vec2.create()
|
||||
vec2.set( center, window.innerWidth/2, window.innerHeight/2 )
|
||||
vec2.set( center, this.width/2, this.height/2 )
|
||||
let dir2d = vec2.clone(dir, dir)
|
||||
vec2.subtract( dir2d, dir2d, center )
|
||||
vec2.normalize( dir2d, dir2d );
|
||||
@@ -247,39 +287,6 @@ class WebglGlobe {
|
||||
this.renderer.clear()
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
// initPointer() {
|
||||
// this.currPointer = [0,0]
|
||||
// this.pointer = [0,0]
|
||||
// this.pointerRatio = [0,0]
|
||||
// this._onPointerDown = this._onPointerDown.bind(this)
|
||||
// this._onPointerMove = this._onPointerMove.bind(this)
|
||||
// this._onPointerUp = this._onPointerUp.bind(this)
|
||||
// this._handleOrientation = this._handleOrientation.bind(this)
|
||||
// document.addEventListener(support.pointerdown, this._onPointerDown, false);
|
||||
// document.addEventListener(support.pointermove, this._onPointerMove, false);
|
||||
// document.addEventListener(support.pointerup, this._onPointerUp, false);
|
||||
// window.addEventListener('deviceorientation', this._handleOrientation);
|
||||
// }
|
||||
// _handleOrientation(event) {
|
||||
// this.pointerRatio[0] = (event.gamma) / 25
|
||||
// this.pointerRatio[1] = (event.beta-45) / 25
|
||||
// }
|
||||
// _onPointerDown() {
|
||||
// this._isPointerDown = true;
|
||||
// }
|
||||
// _onPointerUp() {
|
||||
// this._isPointerDown = false
|
||||
// }
|
||||
// _onPointerMove(event) {
|
||||
// let pe = support.touch && event.type != 'mousemove' ? (event.touches[0] || event.changedTouches[0]) : event;
|
||||
// this.pointer[0] = pe.pageX;
|
||||
// this.pointer[1] = pe.pageY;
|
||||
// this.pointer[1] -= window.pageYOffset || document.documentElement.scrollTop
|
||||
// this.pointerRatio[0] = (this.pointer[0] / window.innerWidth - .5) * 2
|
||||
// this.pointerRatio[1] = (this.pointer[1] / window.innerHeight - .5) * 2
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// window.WebglGlobe = WebglGlobe
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
let globe
|
||||
|
||||
// Functions
|
||||
const resize = () => globe.resize()
|
||||
const resize = () => {
|
||||
globe.resize()
|
||||
globe.update()
|
||||
}
|
||||
const update = () => {
|
||||
requestAnimationFrame(update)
|
||||
globe.update()
|
||||
@@ -20,37 +23,36 @@
|
||||
*/
|
||||
onMount(async () => {
|
||||
// For browser only
|
||||
// if (process.browser) {
|
||||
// // Import libraries and code
|
||||
// let WebglGlobe
|
||||
// await import('globe').then(module => WebglGlobe = module.default)
|
||||
if (process.browser) {
|
||||
// Import libraries and code
|
||||
let WebglGlobe
|
||||
await import('globe').then(module => WebglGlobe = module.default)
|
||||
|
||||
// // Init the globe from library
|
||||
// globe = new WebglGlobe({
|
||||
// el: scope,
|
||||
// texture: '/img/globe/map-4k.png',
|
||||
// markers: [...$locations.map(location => {
|
||||
// return {
|
||||
// name: location.name,
|
||||
// slug: location.slug,
|
||||
// countrySlug: location.country.slug,
|
||||
// lat: location.coordinates.lat,
|
||||
// lng: location.coordinates.lng
|
||||
// }
|
||||
// })]
|
||||
// })
|
||||
// Init the globe from library
|
||||
globe = new WebglGlobe({
|
||||
el: scope,
|
||||
texture: '/img/globe/map-2k.png',
|
||||
markers: [...$locations.map(location => {
|
||||
return {
|
||||
name: location.name,
|
||||
slug: location.slug,
|
||||
countryName: location.country.name,
|
||||
countrySlug: location.country.slug,
|
||||
lat: location.coordinates.lat,
|
||||
lng: location.coordinates.lng
|
||||
}
|
||||
})],
|
||||
onLinkClicked: () => {},
|
||||
cameraDistance: 3
|
||||
})
|
||||
|
||||
// // Run the globe
|
||||
// resize()
|
||||
// update()
|
||||
// }
|
||||
// Run the globe
|
||||
resize()
|
||||
update()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- <svelte:window on:resize={resize} /> -->
|
||||
<svelte:window on:resize={resize} />
|
||||
|
||||
<div class="globe" class:globe--part={type === 'part'} bind:this={scope}>
|
||||
<div class="wrap">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="globe" bind:this={scope} />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// Components
|
||||
import IconArrow from 'atoms/IconArrow'
|
||||
import TitleSite from 'atoms/TitleSite'
|
||||
import Globe from 'molecules/InteractiveGlobe'
|
||||
import InteractiveGlobe from 'molecules/InteractiveGlobe'
|
||||
import Locations from 'organisms/Locations'
|
||||
import Footer from 'organisms/Footer'
|
||||
import Transition from 'utils/Transition'
|
||||
@@ -65,7 +65,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Globe />
|
||||
<InteractiveGlobe />
|
||||
|
||||
<Locations />
|
||||
</section>
|
||||
|
||||
@@ -78,7 +78,9 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="globe__cut">
|
||||
<InteractiveGlobe type="part" />
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</section>
|
||||
|
||||
@@ -11,8 +11,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Globe
|
||||
.globe {
|
||||
margin-top: -25vw;
|
||||
margin-bottom: -18vw;
|
||||
|
||||
@include breakpoint (xl) {
|
||||
margin-top: -320px;
|
||||
margin-bottom: -320px;
|
||||
}
|
||||
}
|
||||
|
||||
// Browse
|
||||
.browse {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,31 @@
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
height: 120vw;
|
||||
overflow: hidden;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
|
||||
.wrap {
|
||||
@include breakpoint (xs) {
|
||||
// Cut
|
||||
&__cut {
|
||||
opacity: 0.5;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
height: 35vw;
|
||||
min-height: 400px;
|
||||
padding: 0;
|
||||
|
||||
// Partial globe
|
||||
.globe {
|
||||
margin-top: -18vw;
|
||||
}
|
||||
}
|
||||
|
||||
// Marker
|
||||
.marker {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 8px;
|
||||
@@ -22,26 +36,53 @@
|
||||
opacity: 1;
|
||||
background: #ff6c89;
|
||||
|
||||
// Location name
|
||||
span {
|
||||
transition: color 0.4s $ease-quart;
|
||||
}
|
||||
|
||||
&__label {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 100%;
|
||||
bottom: -230%;
|
||||
left: 230%;
|
||||
color: transparent;
|
||||
}
|
||||
// Location city
|
||||
&__city {
|
||||
font-family: $font-serif;
|
||||
font-size: rem(24px);
|
||||
color: transparent;
|
||||
transition: color 0.4s $ease-quart;
|
||||
line-height: 1;
|
||||
}
|
||||
// Location country
|
||||
&__country {
|
||||
display: block;
|
||||
opacity: 0.8;
|
||||
font-family: $font-sans;
|
||||
font-size: rem(10px);
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
// Active
|
||||
&.is-active {
|
||||
&, span {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
span {
|
||||
.marker {
|
||||
&__city {
|
||||
color: #FF6C89;
|
||||
}
|
||||
&__country {
|
||||
color: $color-text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grabbing
|
||||
&.is-grabbing {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -66,10 +66,22 @@
|
||||
margin-top: 248px;
|
||||
}
|
||||
|
||||
// Description
|
||||
.style-description {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
color: $color-tertiary;
|
||||
}
|
||||
|
||||
// Globe
|
||||
.globe {
|
||||
margin-bottom: -52vh;
|
||||
|
||||
@include breakpoint (xl) {
|
||||
margin-bottom: -550px;
|
||||
}
|
||||
}
|
||||
|
||||
// Browse
|
||||
.browse {
|
||||
margin-top: 0;
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
}
|
||||
|
||||
// Globe
|
||||
.globe--part {
|
||||
.globe__cut {
|
||||
margin-top: 8vw;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
static/img/globe/map-2k.png
Normal file
BIN
static/img/globe/map-2k.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Reference in New Issue
Block a user