Skip to content

Instantly share code, notes, and snippets.

Created February 15, 2016 23:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save anonymous/8cf5577b441efd136330 to your computer and use it in GitHub Desktop.
Save anonymous/8cf5577b441efd136330 to your computer and use it in GitHub Desktop.
BB-8
<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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment