I guess freeze framed like this it's pretty easy to spot. This is from American Dad season 4, episode 9, time index around 6 minutes (as the picture shows) -- captured from Hulu.
Source code: parallax (plain text)
Technological musings by Jason Mobarak.
I guess freeze framed like this it's pretty easy to spot. This is from American Dad season 4, episode 9, time index around 6 minutes (as the picture shows) -- captured from Hulu.
A while ago I heard about something called parallax scrolling [1] being used on Google's Android platform (specifically on the G1). I had just gotten the G1 at that point and hadn't noticed that it was doing something special when scrolling between the three screens in the "home" application.
The idea is pretty simple, you have two surfaces that scroll at two different speeds, one is bigger or father away, depending on how you look at it.
On the G1 it allows the background of the home screen to be smaller than the total "viewable area" that the three home screens cover. This is useful since the size of the home screen areas can change drastically when the phone is flipped open. It also creates the illusion of depth.
Below is my implementation using Proce55ing (click and drag the light purple square).
Source code: parallax (plain text)
class Position { int x = 0; int y = 0; } | |
class Delta { int dx = 0; int dy = 0; } | |
class RectArea | |
{ | |
Position position = new Position(); | |
Delta delta = new Delta(); | |
color fillColor; | |
int opacity; | |
RectArea(color fillColor, int opacity) | |
{ | |
this.fillColor = fillColor; | |
this.opacity = opacity; | |
} | |
void setSize(int dx, int dy) | |
{ | |
delta.dx = dx; | |
delta.dy = dy; | |
} | |
boolean hitTest(int posX, int posY) | |
{ | |
return posX >= position.x && posX <= (position.x + delta.dx) && | |
posY >= position.y && posY <= (position.y + delta.dy); | |
} | |
void initialize() { placeAtCenter(this); } | |
void setX(int x) { position.x = x; } | |
void moveX(int dx) { position.x += dx; } | |
int maybeMoveX(int dx) { return position.x + dx; } | |
void paint() | |
{ | |
fill(fillColor, opacity); | |
rect(position.x, position.y, delta.dx, delta.dy); | |
} | |
} | |
void placeAtCenter(RectArea area) | |
{ | |
area.position.x = height / 2 - area.delta.dx / 2; | |
area.position.y = width / 2 - area.delta.dy / 2; | |
} | |
boolean drag = false; | |
boolean inDrag = false; | |
int lastMouseX = 0; | |
int canvasX = 400; | |
int canvasY = 400; | |
RectArea viewableArea = new RectArea(#93BAF7, 200); | |
RectArea backgroundArea = new RectArea(#CE7BCE, 255); | |
int scaleBy = 0; | |
int excessXs = 0; | |
int scrollEdgeX1_b = 0; | |
int scrollEdgeX2_b = 0; | |
int scrollEdgeX1_v = 0; | |
int scrollEdgeX2_v = 0; | |
void setup() | |
{ | |
size(canvasX, canvasY); | |
background(0); | |
noStroke(); | |
frameRate(25); | |
lastMouseX = mouseX; | |
viewableArea.setSize(100, 200); | |
backgroundArea.setSize(250, 200); | |
backgroundArea.initialize(); | |
backgroundArea.paint(); | |
viewableArea.initialize(); | |
viewableArea.paint(); | |
float vdx = viewableArea.delta.dx; | |
float bdx = backgroundArea.delta.dx; | |
int slack = (int)((3*vdx - bdx)/2); | |
scaleBy = (int)(vdx / slack); | |
println(scaleBy); | |
scrollEdgeX1_v = viewableArea.position.x - viewableArea.delta.dx; | |
scrollEdgeX2_v = viewableArea.position.x + viewableArea.delta.dx; | |
scrollEdgeX1_b = scrollEdgeX1_v; | |
scrollEdgeX2_b = backgroundArea.position.x + slack; | |
} | |
void putAtEdge(RectArea r, int x) | |
{ | |
r.setX(x); | |
inDrag = false; | |
} | |
void draw() | |
{ | |
if (drag) | |
{ | |
background(0); | |
backgroundArea.paint(); | |
int dx = -1 * (lastMouseX - mouseX); | |
boolean resetDrag = false; | |
if ( viewableArea.maybeMoveX(dx) < scrollEdgeX1_v ) | |
{ | |
putAtEdge(viewableArea, scrollEdgeX1_v); | |
putAtEdge(backgroundArea, scrollEdgeX1_b); | |
} | |
else if ( viewableArea.maybeMoveX(dx) > scrollEdgeX2_v ) | |
{ | |
putAtEdge(viewableArea, scrollEdgeX2_v); | |
putAtEdge(backgroundArea, scrollEdgeX2_b); | |
} | |
else | |
{ | |
viewableArea.moveX(dx); | |
backgroundArea.moveX((dx + excessXs) / scaleBy); | |
excessXs = (dx + excessXs) % scaleBy; | |
} | |
viewableArea.paint(); | |
} | |
lastMouseX = mouseX; | |
} | |
void mouseDragged() | |
{ | |
if (inDrag) | |
return; | |
drag = viewableArea.hitTest(mouseX, mouseY); | |
if (drag) | |
inDrag = true; | |
} | |
void mouseReleased() | |
{ | |
inDrag = false; | |
drag = false; | |
} |
1 - Turns out I was probably reading a Qt developer blog. These posts support that conclusion.