So pumped after the new Star Wars trailer! Can't wait for December!
Forked from Apex Design Studio's Pen BB-8.
Forked from Apex Design Studio's Pen BB-8.
A Pen by cesare soldini on CodePen.
So pumped after the new Star Wars trailer! Can't wait for December!
Forked from Apex Design Studio's Pen BB-8.
Forked from Apex Design Studio's Pen BB-8.
A Pen by cesare soldini on CodePen.
<div class="message"> | |
<h2>move mouse or tap.</h2> | |
</div> | |
<div class="sand"></div> | |
<div class="bb8"> | |
<div class="antennas"> | |
<div class="antenna short"></div> | |
<div class="antenna long"></div> | |
</div> | |
<div class="head"> | |
<div class="stripe one"></div> | |
<div class="stripe two"></div> | |
<div class="eyes"> | |
<div class="eye one"></div> | |
<div class="eye two"></div> | |
</div> | |
<div class="stripe three"></div> | |
</div> | |
<div class="ball"> | |
<div class="lines one"></div> | |
<div class="lines two"></div> | |
<div class="ring one"></div> | |
<div class="ring two"></div> | |
<div class="ring three"></div> | |
</div> | |
<div class="shadow"></div> | |
</div> | |
<div class="shameless"> | |
by | |
<a href="https://www.linkedin.com/in/caesarsol" target="_blank">caesarsol</a> | |
- | |
inspired by | |
<a href="http://codepen.io/apexdesignstudio/pen/PPEJwz" target="_blank">BB-8 (apexstudio)</a> | |
</div> |
function loopFrames(func) { | |
let loopControl = { | |
_continueFlag: true, | |
break: function() { this._continueFlag = false }, | |
continued: function() { return (this._continueFlag === true) } | |
} | |
requestAnimationFrame(() => { | |
func(loopControl) | |
if (loopControl.continued()) loopFrames(func) | |
}) | |
} | |
function switchCase(subject) { | |
const noop = {when: () => noop, default: () => noop} | |
return { | |
when(compare, then) { | |
if (subject === compare) { | |
then() | |
return noop | |
} else { | |
return this | |
} | |
}, | |
default: (then) => then() | |
} | |
} | |
$.fn.transform = function(transf, ...values) { | |
let transforms = this.data('transforms') | |
if (!transforms || transforms.length === 0) { | |
transforms = new Map() | |
} | |
switchCase(transf) | |
.when('translate', () => { | |
let [x, y] = values | |
transforms.set('translate', `${x}px, ${y}px`) | |
}) | |
.when('rotate', () => { | |
let [degrees] = values | |
transforms.set('rotate', `${degrees}deg`) | |
}) | |
.default(() => { | |
throw ("Transformation not supported") | |
}) | |
let transfString = Array.from(transforms.entries()).map(([k,v]) => `${k}(${v})`).join(' ') | |
this.css('transform', transfString) | |
this.data('transforms', transforms) | |
return this | |
} | |
$.fn.translate = function(x,y) { | |
/* Position object absolutely */ | |
requestAnimationFrame(() => | |
this.transform('translate', x, y) | |
) | |
return this | |
}//initialPoint | |
$.fn.rotate = function(degrees, {center} = {}) { | |
if (center) { | |
let originCss = `${center.x}px ${center.y}px` | |
if (!this.css('transform-origin') !== originCss) | |
this.css('transform-origin', originCss) | |
} | |
requestAnimationFrame(() => | |
this.transform('rotate', degrees) | |
) | |
return this | |
} | |
$.fn.translatePoint = function(point) { | |
return this.translate(point.x, point.y) | |
} | |
class PointLogger { | |
constructor(point, name, {color, centerOn}) { | |
const width = 5; | |
this.point = point | |
this.name = name | |
this.center = centerOn || new Point() | |
this.$marker = $('<div>', { | |
id: this.name, | |
css: { | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
width: 1 + width*2, | |
height: 1 + width*2, | |
margin: 0, | |
padding: 0, | |
background: color, | |
marginLeft: -(1 + width), | |
marginTop: -(width), | |
} | |
}) | |
this.$label = $('<span>', { | |
html: this.name, | |
css: { | |
display: 'inline-block', | |
width: 120, | |
fontFamily: 'sans-serif', | |
fontSize: 13, | |
}, | |
append: $('<div>', { css: { background: color }}), | |
}) | |
this.$coordinateLoggerX = $('<input>', { | |
width: 120, | |
css: { | |
backgroundColor: 'transparent', | |
color: 'inherit', | |
border: 'none', | |
}, | |
}) | |
this.$coordinateLoggerY = $('<input>', { | |
width: 120, | |
css: { | |
backgroundColor: 'transparent', | |
color: 'inherit', | |
border: 'none', | |
}, | |
}) | |
this.$coordinateLoggerM = $('<input>', { | |
width: 120, | |
css: { | |
backgroundColor: 'transparent', | |
color: 'inherit', | |
border: 'none', | |
}, | |
}) | |
} | |
watch() { | |
this.point.addWatcher(() => this.log()) | |
} | |
mount() { | |
this.$marker.appendTo($(document.body)) | |
$('<div>', {className: 'point-logger', css: {color: '#ccc'}}).append([ | |
this.$label, | |
this.$coordinateLoggerX, | |
this.$coordinateLoggerY, | |
this.$coordinateLoggerM, | |
]).appendTo($(document.body)) | |
this.watch() | |
this.log() | |
return this | |
} | |
log() { | |
this.$marker.translatePoint(this.point.added(this.center)) | |
this.$coordinateLoggerX.val(this.point.x) | |
this.$coordinateLoggerY.val(this.point.y) | |
this.$coordinateLoggerM.val(this.point.module()) | |
} | |
} | |
class Point { | |
constructor(x = 0, y = 0) { | |
this._x = x | |
this._y = y | |
// list of callbacks we call after each change | |
this._watchers = [] | |
this._callWatchers() | |
} | |
addWatcher(callback) { | |
this._watchers.push(callback.bind(this)) | |
} | |
_callWatchers() { | |
this._watchers.forEach((callback) => callback(this)) | |
} | |
addUpdater(updaterFunc) { | |
updaterFunc(this) | |
} | |
get x() { return this._x } | |
get y() { return this._y } | |
get position() { | |
return [this.x, this.y] | |
} | |
set(x, y) { | |
this._x = x | |
this._y = y | |
this._callWatchers() | |
return this | |
} | |
setWith(transformX, transformY = null) { | |
/* | |
* Accepts one or two callbacks of the form: | |
* function(coordinateValue, coordinateName) | |
* | |
*/ | |
if (!transformY) transformY = transformX | |
this.set(transformX(this.x, 'x'), transformY(this.y, 'y')) | |
return this | |
} | |
/* Other Points */ | |
setOn(otherPoint) { | |
this.set(otherPoint.x, otherPoint.y) | |
return this | |
} | |
follow(otherPoint) { | |
otherPoint.addWatcher(() => { | |
this.setOn(otherPoint) | |
}) | |
return this | |
} | |
followAnimating(otherPoint, {dampening, maxVelocity, maxAcceleration}) { | |
let velocity = 0 | |
let remains = new Point() | |
otherPoint.addWatcher(() => { | |
if (velocity > 0) return | |
loopFrames((loop) => { | |
remains.setOn(otherPoint.subtracted(this)) | |
if (remains.module() > dampening) { | |
let oldVelocity = velocity | |
let advance = remains.multiplied(dampening) | |
velocity = advance.module() | |
if (velocity > maxVelocity) { | |
// Velocity: pixel/frame | |
advance.setModule(maxVelocity) | |
velocity = maxVelocity | |
} | |
if (velocity - oldVelocity > maxAcceleration) { | |
velocity = oldVelocity + maxAcceleration | |
advance.setModule(velocity) | |
} | |
this.add(advance) | |
} else { | |
this.setOn(otherPoint) | |
velocity = 0 | |
loop.break() | |
} | |
}) | |
}) | |
return this | |
} | |
/* Math */ | |
add(otherPoint) { | |
this.setWith((c, cn) => c + otherPoint[cn]) | |
return this | |
} | |
subtract(otherPoint) { | |
this.setWith((c, cn) => c - otherPoint[cn]) | |
return this | |
} | |
multiply(scalar) { | |
this.setWith(c => c * scalar) | |
return this | |
} | |
normalize() { | |
let m = this.module() | |
this.setWith(c => c / m) | |
return this | |
} | |
setModule(module) { | |
this.normalize().multiply(module) | |
return this | |
} | |
/* Immutable Math */ | |
duplicated() { | |
return new Point().setOn(this) | |
} | |
added(otherPoint) { | |
return this.duplicated().add(otherPoint) | |
} | |
subtracted(otherPoint) { | |
return this.duplicated().subtract(otherPoint) | |
} | |
multiplied(scalar) { | |
return this.duplicated().multiply(scalar) | |
} | |
normalized() { | |
return this.duplicated().normalize() | |
} | |
/* Scalars */ | |
moduleSquared() { | |
return Math.pow(this.x, 2) + Math.pow(this.y, 2) | |
} | |
module() { | |
return Math.sqrt(this.moduleSquared()) | |
} | |
} | |
/*********/ | |
var position = new Point() | |
var desired = new Point() | |
var mouse = new Point() | |
// Log the details if you desire! | |
//new PointLogger(desired, 'desired', {color: 'blue'}).mount() | |
//new PointLogger(position, 'position', {color: 'yellow'}).mount() | |
mouse.addUpdater((pt) => { | |
$(document).on("mousemove touchstart touchmove", (event) => { | |
pt.setWith( | |
x => event.pageX, | |
y => event.pageY, | |
) | |
}) | |
}) | |
desired.follow(mouse) | |
position.followAnimating(desired, {dampening: 0.05, maxVelocity: 15, maxAcceleration: .5}) | |
let $image = $('.bb8') | |
let $wheel = $image.find('.ball') | |
position.addWatcher((pt) => { | |
$image.translate(pt.x, 0) | |
$wheel.rotate(pt.x) | |
if (position.x < desired.x) { | |
$('.antennas, .eyes').addClass('right') | |
} else { | |
$('.antennas, .eyes').removeClass('right') | |
} | |
}) |
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/samsarajs.org/dist/0.2.0/samsara.js"></script> |
@import url(https://fonts.googleapis.com/css?family=Raleway); | |
*,*:before,*:after { box-sizing: border-box; } | |
body { | |
background: #869F9D; | |
overflow: hidden; | |
} | |
h2 { | |
color: #ccc; | |
font-family: "raleway", sans-serif; | |
opacity: .5; | |
text-align: center; | |
transition: opacity .8s; | |
&.hide { | |
opacity: 0; | |
} | |
} | |
.sand { | |
background: #B69C77; | |
height: 30%; | |
position: absolute; | |
width: 100%; | |
z-index: -1; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
} | |
// BB-8 | |
$d: 140px; | |
.bb8 { | |
position: absolute; | |
margin-left: -$d/2; | |
width: $d; | |
bottom: 20%; | |
left: 0; | |
} | |
.antennas { | |
position: absolute; | |
transition: left .6s; | |
left: 22%; | |
&.right { | |
left: 0%; | |
} | |
} | |
.antenna { | |
background: #e0d2be; | |
position: absolute; | |
width: 2px; | |
&.short { | |
height: 20px; | |
top: -60px; | |
left: 50px; | |
} | |
&.long { | |
border-top: 6px solid #020204; | |
border-bottom: 6px solid #020204; | |
height: 36px; | |
top: -78px; | |
left: 56px; | |
} | |
} | |
.head { | |
background: #e0d2be; | |
border-radius: 90px 90px 14px 14px; | |
-moz-border-radius: 90px 90px 14px 14px; | |
-webkit-border-radius: 90px 90px 14px 14px; | |
height: 56px; | |
margin-left: -45px; | |
overflow: hidden; | |
position: absolute; | |
width: 90px; | |
z-index: 1; | |
top: -46px; | |
left: 50%; | |
.stripe { | |
position: absolute; | |
width: 100%; | |
} | |
.stripe.one { | |
background: #999; | |
height: 6px; | |
opacity: .8; | |
z-index: 1; | |
top: 6px; | |
} | |
.stripe.two { | |
background: #CD7640; | |
height: 4px; | |
top: 17px; | |
} | |
.stripe.three { | |
background: #999; | |
height: 4px; | |
opacity: .5; | |
bottom: 3px; | |
} | |
.eyes { | |
display: block; | |
height: 100%; | |
position: absolute; | |
width: 100%; | |
transition: left .6s; | |
left: 0%; | |
} | |
.eyes.right { | |
left: 36%; | |
} | |
.eye { | |
border-radius: 50%; | |
display: block; | |
position: absolute; | |
&.one { | |
background: #020204; | |
border: 4px solid #e0d2be; | |
height: 30px; | |
width: 30px; | |
top: 12px; | |
left: 12%; | |
} | |
&.one:after { | |
background: white; | |
border-radius: 50%; | |
content: ""; | |
display: block; | |
height: 3px; | |
position: absolute; | |
width: 3px; | |
top: 4px; | |
right: 4px; | |
} | |
&.two { | |
background: #e0d2be; | |
border: 1px solid #020204; | |
height: 16px; | |
width: 16px; | |
top: 30px; | |
left: 40%; | |
&:after { | |
background: #020204; | |
border-radius: 50%; | |
content: ""; | |
display: block; | |
height: 10px; | |
position: absolute; | |
width: 10px; | |
top: 2px; | |
left: 2px; | |
} | |
} | |
} | |
} | |
.ball { | |
background: #d1c3ad; | |
border-radius: 50%; | |
height: $d; | |
overflow: hidden; | |
position: relative; | |
width: $d; | |
} | |
.lines { | |
border: 2px solid #B19669; | |
border-radius: 50%; | |
height: 400px; | |
opacity: .6; | |
position: absolute; | |
width: 400px; | |
&.two { | |
top: -10px; | |
left: -250px; | |
} | |
} | |
.ring { | |
background: #CD7640; | |
border-radius: 50%; | |
height: 70px; | |
margin-left: -35px; | |
position: absolute; | |
width: 70px; | |
&:after { | |
background: #d1c3ad; | |
border-radius: 50%; | |
content: ""; | |
display: block; | |
height: 70%; | |
margin-top: -35%; | |
margin-left: -35%; | |
position: absolute; | |
width: 70%; | |
top: 50%; | |
left: 50%; | |
} | |
&.one { | |
margin-left: -40px; | |
height: 80px; | |
width: 90px; | |
top: 6%; | |
left: 50%; | |
} | |
&.two { | |
height: 38px; | |
width: 76px; | |
-ms-transform: rotate(36deg); | |
-webkit-transform: rotate(36deg); | |
transform: rotate(36deg); | |
top: 70%; | |
left: 18%; | |
&:after { | |
top: 100%; | |
} | |
} | |
&.three { | |
height: 30px; | |
-ms-transform: rotate(-50deg); | |
-webkit-transform: rotate(-50deg); | |
transform: rotate(-50deg); | |
top: 66%; | |
left: 90%; | |
&:after { | |
top: 110%; | |
} | |
} | |
} | |
.shadow { | |
background: #3A271C; | |
border-radius: 50%; | |
height: $d/6; | |
opacity: .7; | |
position: absolute; | |
width: $d; | |
z-index: -1; | |
left: 5px; | |
bottom: -8px; | |
} | |
.shameless { | |
position: absolute; | |
bottom: 10px; | |
right: 10px; | |
color: #333; | |
font-family: 'Raleway', sans-serif; | |
opacity: .5; | |
} | |
a { | |
text-decoration: none; | |
color: #555; | |
} | |
a:visited { | |
color: #666; | |
} | |
a:hover, | |
a:focus { | |
color: white; | |
} |