<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<!DOCTYPE html><html lang='en' class=''>
<head><script src='//production-assets.codepen.io/assets/editor/live/console_runner-079c09a0e3b9ff743e39ee2d5637b9216b3545af0de366d4b9aad9dc87e26bfd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/events_runner-73716630c22bbc8cff4bd0f07b135f00a0bdc5d14629260c3ec49e5606f98fdd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/css_live_reload_init-2c0dc5167d60a5af3ee189d570b1835129687ea2a61bee3513dee3a50c115a77.js'></script><meta charset='UTF-8'><meta name="robots" content="noindex"><link rel="shortcut icon" type="image/x-icon" href="//production-assets.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" /><link rel="mask-icon" type="" href="//production-assets.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" /><link rel="canonical" href="https://codepen.io/edankwan/pen/emqgpr?limit=all&page=27&q=css" />
<script src="https://s.codepen.io/assets/libs/modernizr.js" type="text/javascript"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js'></script><link rel='stylesheet prefetch' href='https://fonts.googleapis.com/css?family=Oswald:400'>
<style class="cp-pen-styles">html, body {
position: absolute;
width: 100%;
height: 100%;
margin: 0 0;
overflow: hidden;
font-family: 'Lato', sans-serif;
background-color: #000;
color: #fff;
}
.world {
position: absolute;
width: 100%;
height: 100%;
cursor: pointer;
cursor: move;
cursor: -moz-grab;
cursor: -webkit-grab;
cursor: grab;
}
.world-bg {
position: absolute;
width: 100%;
height: 100%;
background-position: 50% 50%;
background-size: cover;
}
.world-globe {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
}
.world-globe-pole {
position: absolute;
width: 530px;
height: 530px;
left: -265px;
top: -265px;
border-radius: 50% 50%;
background-color: #fff;
}
.world-globe-doms-container {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
}
.world-globe-halo {
position: absolute;
left: 50%;
top: 50%;
width: 730px;
height: 715px;
margin-left: -368px;
margin-top: -350px;
}
.info {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
padding: 10px 10px;
box-sizing: border-box;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
font-size: 12px;
}
.info-desc {
color: #ddd;
font-size: 10px;
}
a {
color: #ff5f5f;
}
</style></head><body>
<div class="world">
<div class="world-bg"></div>
<div class="world-globe">
<div class="world-globe-pole"></div>
<div class="world-globe-doms-container"></div>
<div class="world-globe-halo"></div>
</div>
</div>
<div class="info">
<div class="info-title">CSS Non-webgl realistic globe Demo</div>
<div class="info-desc">Example for my meetup talk. Use <a href="https://github.com/edankwan/PerspectiveTransform.js" target="_blank">PerspectiveTransform</a> and visual trick to create a CSS 3D globe.</div>
<div class="info-links"><a href="https://twitter.com/edankwan" target="_blank">@edankwan</a></div>
</div>
<script src='//production-assets.codepen.io/assets/common/stopExecutionOnTimeout-b2a7b3fe212eaa732349046d8416e00a9dec26eb7fd347590fbced3ab38af52e.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/stats.js/r11/Stats.js'></script><script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/6043/css_globe_PerspectiveTransform.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js'></script>
<script >var config = {
percent: 0,
lat: 0,
lng: 0,
segX: 14,
segY: 12,
isHaloVisible: true,
isPoleVisible: true,
autoSpin: true,
zoom: 0,
skipPreloaderAnimation: false,
goToHongKong: function() {
goTo(22.28552,114.15769);
}
};
var stats;
var imgs;
var preloader;
var preloadPercent;
var globeDoms;
var vertices;
var world;
var worldBg;
var globe;
var globeContainer;
var globePole;
var globeHalo;
var pixelExpandOffset = 1.5;
var rX = 0;
var rY = 0;
var rZ = 0;
var sinRX;
var sinRY;
var sinRZ;
var cosRX;
var cosRY;
var cosRZ;
var dragX;
var dragY;
var dragLat;
var dragLng;
var isMouseDown = false;
var isTweening = false;
var tick = 1;
var URLS = {
bg: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/6043/css_globe_bg.jpg',
diffuse: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/6043/css_globe_diffuse.jpg',
halo: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/6043/css_globe_halo.png',
};
var transformStyleName = PerspectiveTransform.transformStyleName;
function init(ref) {
world = document.querySelector('.world');
worldBg = document.querySelector('.world-bg');
worldBg.style.backgroundImage = 'url(' + URLS.bg + ')';
globe = document.querySelector('.world-globe');
globeContainer = document.querySelector('.world-globe-doms-container');
globePole = document.querySelector('.world-globe-pole');
globeHalo = document.querySelector('.world-globe-halo');
globeHalo.style.backgroundImage = 'url(' + URLS.halo + ')';
regenerateGlobe();
var gui = new dat.GUI();
gui.add(config, 'lat', -90, 90).listen();
gui.add(config, 'lng', -180, 180).listen();
gui.add(config, 'isHaloVisible');
gui.add(config, 'isPoleVisible');
gui.add(config, 'autoSpin');
gui.add(config, 'goToHongKong');
gui.add(config, 'zoom', 0, 1).listen();
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = 0;
stats.domElement.style.top = 0;
document.body.appendChild( stats.domElement );
// events
world.ondragstart = function () {return false;};
world.addEventListener('mousedown', onMouseDown);
world.addEventListener('mousemove', onMouseMove);
world.addEventListener('mouseup', onMouseUp);
world.addEventListener('touchstart', touchPass(onMouseDown));
world.addEventListener('touchmove', touchPass(onMouseMove));
world.addEventListener('touchend', touchPass(onMouseUp));
loop();
}
function touchPass(func) {
return function(evt) {
evt.preventDefault();
func.call(this, {pageX: evt.changedTouches[0].pageX, pageY: evt.changedTouches[0].pageY});
};
}
function onMouseDown(evt) {
isMouseDown = true;
dragX = evt.pageX;
dragY = evt.pageY;
dragLat = config.lat;
dragLng = config.lng;
}
function onMouseMove(evt) {
if(isMouseDown) {
var dX = evt.pageX - dragX;
var dY = evt.pageY - dragY;
config.lat = clamp(dragLat + dY * 0.5, -90, 90);
config.lng = clampLng(dragLng - dX * 0.5, -180, 180);
}
}
function onMouseUp(evt) {
if(isMouseDown) {
isMouseDown = false;
}
}
function regenerateGlobe() {
var dom, domStyle;
var x, y;
globeDoms = [];
while (dom = globeContainer.firstChild) {if (window.CP.shouldStopExecution(1)){break;}
globeContainer.removeChild(dom);
}
window.CP.exitedLoop(1);
var segX = config.segX;
var segY = config.segY;
var diffuseImgBackgroundStyle = 'url(' + URLS.diffuse + ')';
var segWidth = 1600 / segX | 0;
var segHeight = 800 / segY | 0;
vertices = [];
var verticesRow;
var radius = (536) / 2;
var phiStart = 0;
var phiLength = Math.PI * 2;
var thetaStart = 0;
var thetaLength = Math.PI;
for ( y = 0; y <= segY; y ++ ) {if (window.CP.shouldStopExecution(3)){break;}
verticesRow = [];
for ( x = 0; x <= segX; x ++ ) {if (window.CP.shouldStopExecution(2)){break;}
var u = x / segX;
var v = 0.05 + y / segY * (1 - 0.1);
var vertex = {
x: - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ),
y: -radius * Math.cos( thetaStart + v * thetaLength ),
z: radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ),
phi: phiStart + u * phiLength,
theta: thetaStart + v * thetaLength
};
verticesRow.push( vertex );
}
window.CP.exitedLoop(2);
vertices.push( verticesRow );
}
window.CP.exitedLoop(3);
for ( y = 0; y < segY; ++y ) {if (window.CP.shouldStopExecution(5)){break;}
for ( x = 0; x < segX; ++x ) {if (window.CP.shouldStopExecution(4)){break;}
dom = document.createElement('div');
domStyle = dom.style;
domStyle.position = 'absolute';
domStyle.width = segWidth + 'px';
domStyle.height = segHeight + 'px';
domStyle.overflow = 'hidden';
domStyle[PerspectiveTransform.transformOriginStyleName] = '0 0';
domStyle.backgroundImage = diffuseImgBackgroundStyle;
dom.perspectiveTransform = new PerspectiveTransform(dom , segWidth, segHeight);
dom.topLeft = vertices[ y ][ x ];
dom.topRight = vertices[ y ][ x + 1];
dom.bottomLeft = vertices[ y + 1 ][ x ];
dom.bottomRight = vertices[ y + 1 ][ x + 1 ];
domStyle.backgroundPosition = (-segWidth * x) + 'px ' + (-segHeight * y) + 'px';
globeContainer.appendChild(dom);
globeDoms.push(dom);
}
window.CP.exitedLoop(4);
}
window.CP.exitedLoop(5);
}
function loop() {
requestAnimationFrame(loop);
stats.begin();
render();
stats.end();
}
function render() {
if(config.autoSpin && !isMouseDown && !isTweening) {
config.lng = clampLng(config.lng - 0.2);
}
rX = config.lat / 180 * Math. PI;
rY = (clampLng(config.lng) - 270) / 180 * Math. PI;
globePole.style.display = config.isPoleVisible ? 'block' : 'none';
globeHalo.style.display = config.isHaloVisible ? 'block' : 'none';
var ratio = Math.pow(config.zoom, 1.5);
pixelExpandOffset = 1.5 + (ratio) * -1.25;
ratio = 1 + ratio * 3;
globe.style[transformStyleName] = 'scale3d(' + ratio + ',' + ratio + ',1)';
ratio = 1 + Math.pow(config.zoom, 3) * 0.3;
worldBg.style[transformStyleName] = 'scale3d(' + ratio + ',' + ratio + ',1)';
transformGlobe();
}
function clamp(x, min, max) {
return x < min ? min : x > max ? max : x;
}
function clampLng(lng) {
return ((lng + 180) % 360) - 180;
}
function transformGlobe() {
var dom, perspectiveTransform;
var x, y, v1, v2, v3, v4, vertex, verticesRow, i, len;
if(tick ^= 1) {
sinRY = Math.sin(rY);
sinRX = Math.sin(-rX);
sinRZ = Math.sin(rZ);
cosRY = Math.cos(rY);
cosRX = Math.cos(-rX);
cosRZ = Math.cos(rZ);
var segX = config.segX;
var segY = config.segY;
for ( y = 0; y <= segY; y ++ ) {if (window.CP.shouldStopExecution(7)){break;}
verticesRow = vertices[y];
for ( x = 0; x <= segX; x ++ ) {if (window.CP.shouldStopExecution(6)){break;}
rotate(vertex = verticesRow[x], vertex.x, vertex.y, vertex.z);
}
window.CP.exitedLoop(6);
}
window.CP.exitedLoop(7);
for ( y = 0; y < segY; y ++ ) {if (window.CP.shouldStopExecution(9)){break;}
for ( x = 0; x < segX; x ++ ) {if (window.CP.shouldStopExecution(8)){break;}
dom = globeDoms[x + segX * y];
v1 = dom.topLeft;
v2 = dom.topRight;
v3 = dom.bottomLeft;
v4 = dom.bottomRight;
expand(v1, v2);
expand(v2, v3);
expand(v3, v4);
expand(v4, v1);
perspectiveTransform = dom.perspectiveTransform;
perspectiveTransform.topLeft.x = v1.tx;
perspectiveTransform.topLeft.y = v1.ty;
perspectiveTransform.topRight.x = v2.tx;
perspectiveTransform.topRight.y = v2.ty;
perspectiveTransform.bottomLeft.x = v3.tx;
perspectiveTransform.bottomLeft.y = v3.ty;
perspectiveTransform.bottomRight.x = v4.tx;
perspectiveTransform.bottomRight.y = v4.ty;
perspectiveTransform.hasError = perspectiveTransform.checkError();
if(!(perspectiveTransform.hasError = perspectiveTransform.checkError())) {
perspectiveTransform.calc();
}
}
window.CP.exitedLoop(8);
}
window.CP.exitedLoop(9);
} else {
for ( i = 0, len = globeDoms.length; i < len; i ++ ) {if (window.CP.shouldStopExecution(10)){break;}
perspectiveTransform = globeDoms[i].perspectiveTransform;
if(!perspectiveTransform.hasError) {
perspectiveTransform.update();
} else {
perspectiveTransform.style[transformStyleName] = 'translate3d(-8192px, 0, 0)';
}
}
window.CP.exitedLoop(10);
}
}
function goTo(lat, lng) {
var dX = lat - config.lat;
var dY = lng - config.lng;
var roughDistance = Math.sqrt(dX * dX + dY * dY);
isTweening = true;
TweenMax.to(config, roughDistance * 0.01, {lat: lat, lng: lng, ease:'easeInOutSine'});
TweenMax.to(config, 1, {delay: roughDistance * 0.01, zoom: 1, ease:'easeInOutSine', onComplete: function(){
isTweening = false;
}});
}
function rotate(vertex, x, y, z) {
x0 = x * cosRY - z * sinRY;
z0 = z * cosRY + x * sinRY;
y0 = y * cosRX - z0 * sinRX;
z0 = z0 * cosRX + y * sinRX;
var offset = 1 + (z0 / 4000);
x1 = x0 * cosRZ - y0 * sinRZ;
y0 = y0 * cosRZ + x0 * sinRZ;
vertex.px = x1 * offset;
vertex.py = y0 * offset;
}
// shameless stole and edited from threejs CanvasRenderer
function expand( v1, v2 ) {
var x = v2.px - v1.px, y = v2.py - v1.py,
det = x * x + y * y, idet;
if ( det === 0 ) {
v1.tx = v1.px;
v1.ty = v1.py;
v2.tx = v2.px;
v2.ty = v2.py;
return;
}
idet = pixelExpandOffset / Math.sqrt( det );
x *= idet; y *= idet;
v2.tx = v2.px + x;
v2.ty = v2.py + y;
v1.tx = v1.px - x;
v1.ty = v1.py - y;
}
init();
//# sourceURL=pen.js
</script>
</body></html>