Implement Interactive Globe on Homepage
This commit is contained in:
130
src/components/organisms/InteractiveGlobe.svelte
Normal file
130
src/components/organisms/InteractiveGlobe.svelte
Normal file
@@ -0,0 +1,130 @@
|
||||
<script lang="ts">
|
||||
import { onMount, getContext } from 'svelte'
|
||||
import { getPosition, getRandomElement } from '$utils/functions'
|
||||
|
||||
export let type: string = undefined
|
||||
export let autoRotate: boolean = true
|
||||
export let scrollSmooth: number = 0.5
|
||||
export let opacity: number = 1
|
||||
|
||||
const { continent, location } = getContext('global')
|
||||
|
||||
let globe: any
|
||||
let Globe: any
|
||||
let globeEl: HTMLElement
|
||||
let windowHeight: number, windowWidth: number
|
||||
let containerTop: number = 0, containerHeight: number = 0
|
||||
let observer: IntersectionObserver
|
||||
|
||||
const randomContinent = getRandomElement(continent.filter(cont => cont.countries))
|
||||
const globeResolution = windowWidth > 1440 && window.devicePixelRatio > 1 ? '4k' : '2k'
|
||||
const locations = location.map(({ name, slug, country, coordinates: { coordinates }}: any) => ({
|
||||
name,
|
||||
slug,
|
||||
countryName: country.name,
|
||||
countrySlug: country.slug,
|
||||
lat: coordinates[1],
|
||||
lng: coordinates[0],
|
||||
// className: location.close ? 'is-close' : '',
|
||||
}))
|
||||
|
||||
|
||||
/**
|
||||
* Globe update
|
||||
*/
|
||||
const update = () => {
|
||||
requestAnimationFrame(update)
|
||||
globe.update()
|
||||
}
|
||||
|
||||
/**
|
||||
* When scrolling
|
||||
*/
|
||||
const handleScroll = () => {
|
||||
let scrollDiff = (containerTop + windowHeight + (containerHeight - windowHeight) / 2) - document.documentElement.scrollTop
|
||||
let scrollRatio = (1 - (scrollDiff / windowHeight)) * 2
|
||||
if (globe) {
|
||||
globe.updateCameraPos(scrollRatio, scrollDiff - windowHeight)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When resizing
|
||||
*/
|
||||
const handleResize = () => {
|
||||
if (globeEl) {
|
||||
containerTop = getPosition(globeEl).top
|
||||
containerHeight = globeEl.clientHeight
|
||||
}
|
||||
if (globe) {
|
||||
globe.resize()
|
||||
globe.update()
|
||||
}
|
||||
handleScroll()
|
||||
}
|
||||
|
||||
|
||||
onMount(async () => {
|
||||
// Load globe library
|
||||
Globe = await import('$modules/globe')
|
||||
|
||||
// Instantiate globe
|
||||
globe = new Globe.default({
|
||||
el: globeEl,
|
||||
//cameraDistance: size, // Smaller number == larger globe
|
||||
autoRotationSpeed: autoRotate ? -0.0025 : 0,
|
||||
rotationStart: randomContinent.rotation, // In degrees
|
||||
scrollSmoothing: scrollSmooth,
|
||||
opacity,
|
||||
texture: `/images/globe-map-${globeResolution}.png`,
|
||||
markers: locations,
|
||||
onLinkClicked: () => {}
|
||||
})
|
||||
|
||||
// Observe the globe
|
||||
observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
globe.enable()
|
||||
} else {
|
||||
globe.disable()
|
||||
}
|
||||
})
|
||||
}, {
|
||||
threshold: 0,
|
||||
rootMargin: '0px 0px 0px'
|
||||
})
|
||||
observer.observe(globeEl)
|
||||
|
||||
// Run the globe
|
||||
update()
|
||||
handleResize()
|
||||
|
||||
|
||||
// Destroy
|
||||
return () => {
|
||||
if (globe) {
|
||||
globe.destroy()
|
||||
observer.unobserve(globeEl)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
bind:innerHeight={windowHeight}
|
||||
bind:innerWidth={windowWidth}
|
||||
on:scroll={handleScroll}
|
||||
on:resize={handleResize}
|
||||
/>
|
||||
|
||||
|
||||
<section id="globe">
|
||||
{#if type === 'cropped'}
|
||||
<div class="globe--cropped">
|
||||
<div class="globe" bind:this={globeEl} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="globe" bind:this={globeEl} />
|
||||
{/if}
|
||||
</section>
|
||||
Reference in New Issue
Block a user