-
-
Save dartlab-user/dbc2a50c7c8ac37544ab to your computer and use it in GitHub Desktop.
Clock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"origin": "dartlab.org", | |
"url": "http://dartlab.org/#:gistId", | |
"history": [ | |
"bf1af9b3c187997b573c" | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<h1>Clock</h1> | |
<p>An html5 clock using absolutely positioned elements.</p> | |
<div id="canvas-content"></div> | |
<footer> | |
<p id="summary"> </p> | |
<p id="notes"> </p> | |
</footer> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
// for details. All rights reserved. Use of this source code is governed by a | |
// BSD-style license that can be found in the LICENSE file. | |
library clock; | |
import 'dart:async'; | |
import 'dart:html'; | |
import 'dart:math'; | |
void main() { | |
new CountDownClock(); | |
} | |
num _fpsAverage; | |
/** | |
* Display the animation's FPS in a div. | |
*/ | |
void showFps(num fps) { | |
if (_fpsAverage == null) { | |
_fpsAverage = fps; | |
} else { | |
_fpsAverage = fps * 0.05 + _fpsAverage * 0.95; | |
querySelector("#notes").text = "${_fpsAverage.round().toInt()} fps"; | |
} | |
} | |
class CountDownClock { | |
static const double NUMBER_SPACING = 19.0; | |
static const double BALL_WIDTH = 19.0; | |
static const double BALL_HEIGHT = 19.0; | |
List<ClockNumber> hours = new List<ClockNumber>(2); | |
List<ClockNumber> minutes = new List<ClockNumber>(2); | |
List<ClockNumber> seconds = new List<ClockNumber>(2); | |
int displayedHour = -1; | |
int displayedMinute = -1; | |
int displayedSecond = -1; | |
Balls balls = new Balls(); | |
CountDownClock() { | |
var parent = querySelector("#canvas-content"); | |
createNumbers(parent, parent.client.width, parent.client.height); | |
updateTime(new DateTime.now()); | |
window.requestAnimationFrame(tick); | |
} | |
void tick(num time) { | |
updateTime(new DateTime.now()); | |
balls.tick(time); | |
window.requestAnimationFrame(tick); | |
} | |
void updateTime(DateTime now) { | |
if (now.hour != displayedHour) { | |
setDigits(pad2(now.hour), hours); | |
displayedHour = now.hour; | |
} | |
if (now.minute != displayedMinute) { | |
setDigits(pad2(now.minute), minutes); | |
displayedMinute = now.minute; | |
} | |
if (now.second != displayedSecond) { | |
setDigits(pad2(now.second), seconds); | |
displayedSecond = now.second; | |
} | |
} | |
void setDigits(String digits, List<ClockNumber> numbers) { | |
for (int i = 0; i < numbers.length; ++i) { | |
int digit = digits.codeUnitAt(i) - '0'.codeUnitAt(0); | |
numbers[i].setPixels(ClockNumbers.PIXELS[digit]); | |
} | |
} | |
String pad3(int number) { | |
if (number < 10) { | |
return "00${number}"; | |
} | |
if (number < 100) { | |
return "0${number}"; | |
} | |
return "${number}"; | |
} | |
String pad2(int number) => number < 10 ? "0${number}" : "${number}"; | |
void createNumbers(Element parent, num width, num height) { | |
DivElement root = new DivElement(); | |
makeRelative(root); | |
root.style.textAlign = 'center'; | |
querySelector("#canvas-content").nodes.add(root); | |
double hSize = (BALL_WIDTH * ClockNumber.WIDTH + NUMBER_SPACING) * 6 | |
+ (BALL_WIDTH + NUMBER_SPACING) * 2; | |
hSize -= NUMBER_SPACING; | |
double vSize = BALL_HEIGHT * ClockNumber.HEIGHT; | |
double x = (width - hSize) / 2; | |
double y = (height - vSize) / 3; | |
for (int i = 0; i < hours.length; ++i) { | |
hours[i] = new ClockNumber(this, x, Balls.BLUE_BALL_INDEX); | |
root.nodes.add(hours[i].root); | |
setElementPosition(hours[i].root, x, y); | |
x += BALL_WIDTH * ClockNumber.WIDTH + NUMBER_SPACING; | |
} | |
root.nodes.add(new Colon(x, y).root); | |
x += BALL_WIDTH + NUMBER_SPACING; | |
for (int i = 0; i < minutes.length; ++i) { | |
minutes[i] = new ClockNumber(this, x, Balls.RED_BALL_INDEX); | |
root.nodes.add(minutes[i].root); | |
setElementPosition(minutes[i].root, x, y); | |
x += BALL_WIDTH * ClockNumber.WIDTH + NUMBER_SPACING; | |
} | |
root.nodes.add(new Colon(x, y).root); | |
x += BALL_WIDTH + NUMBER_SPACING; | |
for (int i = 0; i < seconds.length; ++i) { | |
seconds[i] = new ClockNumber(this, x, Balls.GREEN_BALL_INDEX); | |
root.nodes.add(seconds[i].root); | |
setElementPosition(seconds[i].root, x, y); | |
x += BALL_WIDTH * ClockNumber.WIDTH + NUMBER_SPACING; | |
} | |
} | |
} | |
void makeAbsolute(Element elem) { | |
elem.style.left = '0px'; | |
elem.style.top = '0px'; | |
elem.style.position = 'absolute'; | |
} | |
void makeRelative(Element elem) { | |
elem.style.position = 'relative'; | |
} | |
void setElementPosition(Element elem, double x, double y) { | |
elem.style.transform = 'translate(${x}px, ${y}px)'; | |
} | |
void setElementSize(Element elem, double l, double t, double r, double b) { | |
setElementPosition(elem, l, t); | |
elem.style.right = "${r}px"; | |
elem.style.bottom = "${b}px"; | |
} | |
int get clientWidth => window.innerWidth; | |
int get clientHeight => window.innerHeight; | |
class Balls { | |
static const double RADIUS2 = Ball.RADIUS * Ball.RADIUS; | |
static const int LT_GRAY_BALL_INDEX = 0; | |
static const int GREEN_BALL_INDEX = 1; | |
static const int BLUE_BALL_INDEX = 2; | |
static const int DK_GRAY_BALL_INDEX = 4; | |
static const int RED_BALL_INDEX = 5; | |
static const int MD_GRAY_BALL_INDEX = 6; | |
static const List<String> PNGS = const [ | |
"https://cdn.rawgit.com/dart-lang/sample-clock/master/web/images/ball-d9d9d9.png", | |
"https://cdn.rawgit.com/dart-lang/sample-clock/master/web/images/ball-009a49.png", | |
"https://cdn.rawgit.com/dart-lang/sample-clock/master/web/images/ball-13acfa.png", | |
"https://cdn.rawgit.com/dart-lang/sample-clock/master/web/images/ball-265897.png", | |
"https://cdn.rawgit.com/dart-lang/sample-clock/master/web/images/ball-b6b4b5.png", | |
"https://cdn.rawgit.com/dart-lang/sample-clock/master/web/images/ball-c0000b.png", | |
"https://cdn.rawgit.com/dart-lang/sample-clock/master/web/images/ball-c9c9c9.png" | |
]; | |
DivElement root; | |
num lastTime; | |
List<Ball> balls; | |
Balls() : | |
lastTime = new DateTime.now().millisecondsSinceEpoch, | |
balls = new List<Ball>() { | |
root = new DivElement(); | |
document.body.nodes.add(root); | |
makeAbsolute(root); | |
setElementSize(root, 0.0, 0.0, 0.0, 0.0); | |
} | |
void tick(num now) { | |
showFps(1000.0 / (now - lastTime + 0.01)); | |
double delta = min((now - lastTime) / 1000.0, 0.1); | |
lastTime = now; | |
// incrementally move each ball, removing balls that are offscreen | |
balls = balls.where((ball) => ball.tick(delta)).toList(); | |
collideBalls(delta); | |
} | |
void collideBalls(double delta) { | |
balls.forEach((b0) { | |
balls.forEach((b1) { | |
// See if the two balls are intersecting. | |
double dx = (b0.x - b1.x).abs(); | |
double dy = (b0.y - b1.y).abs(); | |
double d2 = dx * dx + dy * dy; | |
if (d2 < RADIUS2) { | |
// Make sure they're actually on a collision path | |
// (not intersecting while moving apart). | |
// This keeps balls that end up intersecting from getting stuck | |
// without all the complexity of keeping them strictly separated. | |
if (newDistanceSquared(delta, b0, b1) > d2) { | |
return; | |
} | |
// They've collided. Normalize the collision vector. | |
double d = sqrt(d2); | |
if (d == 0) { | |
return; | |
} | |
dx /= d; | |
dy /= d; | |
// Calculate the impact velocity and speed along the collision vector. | |
double impactx = b0.vx - b1.vx; | |
double impacty = b0.vy - b1.vy; | |
double impactSpeed = impactx * dx + impacty * dy; | |
// Bump. | |
b0.vx -= dx * impactSpeed; | |
b0.vy -= dy * impactSpeed; | |
b1.vx += dx * impactSpeed; | |
b1.vy += dy * impactSpeed; | |
} | |
}); | |
}); | |
} | |
double newDistanceSquared(double delta, Ball b0, Ball b1) { | |
double nb0x = b0.x + b0.vx * delta; | |
double nb0y = b0.y + b0.vy * delta; | |
double nb1x = b1.x + b1.vx * delta; | |
double nb1y = b1.y + b1.vy * delta; | |
double ndx = (nb0x - nb1x).abs(); | |
double ndy = (nb0y - nb1y).abs(); | |
double nd2 = ndx * ndx + ndy * ndy; | |
return nd2; | |
} | |
void add(double x, double y, int color) { | |
balls.add(new Ball(root, x, y, color)); | |
} | |
} | |
class Ball { | |
static const double GRAVITY = 400.0; | |
static const double RESTITUTION = 0.8; | |
static const double MIN_VELOCITY = 100.0; | |
static const double INIT_VELOCITY = 800.0; | |
static const double RADIUS = 14.0; | |
static Random random; | |
static double randomVelocity() { | |
if (random == null) { | |
random = new Random(); | |
} | |
return (random.nextDouble() - 0.5) * INIT_VELOCITY; | |
} | |
Element root; | |
ImageElement elem; | |
double x, y; | |
double vx, vy; | |
double ax, ay; | |
double age; | |
Ball(this.root, this.x, this.y, int color) { | |
elem = new ImageElement(src: Balls.PNGS[color]); | |
makeAbsolute(elem); | |
setElementPosition(elem, x, y); | |
root.nodes.add(elem); | |
ax = 0.0; | |
ay = GRAVITY; | |
vx = randomVelocity(); | |
vy = randomVelocity(); | |
} | |
// return false => remove me | |
bool tick(double delta) { | |
// Update velocity and position. | |
vx += ax * delta; | |
vy += ay * delta; | |
x += vx * delta; | |
y += vy * delta; | |
// Handle falling off the edge. | |
if ((x < RADIUS) || (x > clientWidth)) { | |
elem.remove(); | |
return false; | |
} | |
// Handle ground collisions. | |
if (y > clientHeight) { | |
y = clientHeight.toDouble(); | |
vy *= -RESTITUTION; | |
} | |
// Position the element. | |
setElementPosition(elem, x - RADIUS, y - RADIUS); | |
return true; | |
} | |
} | |
class ClockNumber { | |
static const int WIDTH = 4; | |
static const int HEIGHT = 7; | |
CountDownClock app; | |
Element root; | |
List<List<ImageElement>> imgs; | |
List<List<int>> pixels; | |
int ballColor; | |
ClockNumber(this.app, double pos, this.ballColor) { | |
imgs = new List<List<ImageElement>>(HEIGHT); | |
root = new DivElement(); | |
makeAbsolute(root); | |
setElementPosition(root, pos, 0.0); | |
for (int y = 0; y < HEIGHT; ++y) { | |
imgs[y] = new List<ImageElement>(WIDTH); | |
} | |
for (int y = 0; y < HEIGHT; ++y) { | |
for (int x = 0; x < WIDTH; ++x) { | |
imgs[y][x] = new ImageElement(); | |
root.nodes.add(imgs[y][x]); | |
makeAbsolute(imgs[y][x]); | |
setElementPosition(imgs[y][x], | |
x * CountDownClock.BALL_WIDTH, y * CountDownClock.BALL_HEIGHT); | |
} | |
} | |
} | |
void setPixels(List<List<int>> px) { | |
for (int y = 0; y < HEIGHT; ++y) { | |
for (int x = 0; x < WIDTH; ++x) { | |
ImageElement img = imgs[y][x]; | |
if (pixels != null) { | |
if ((pixels[y][x] != 0) && (px[y][x] == 0)) { | |
scheduleMicrotask(() { | |
var r = img.getBoundingClientRect(); | |
double absx = r.left; | |
double absy = r.top; | |
app.balls.add(absx, absy, ballColor); | |
}); | |
} | |
} | |
img.src = px[y][x] != 0 ? Balls.PNGS[ballColor] : Balls.PNGS[6]; | |
} | |
} | |
pixels = px; | |
} | |
} | |
class Colon { | |
Element root; | |
Colon(double xpos, double ypos) { | |
root = new DivElement(); | |
makeAbsolute(root); | |
setElementPosition(root, xpos, ypos); | |
ImageElement dot = new ImageElement(src: Balls.PNGS[Balls.DK_GRAY_BALL_INDEX]); | |
root.nodes.add(dot); | |
makeAbsolute(dot); | |
setElementPosition(dot, 0.0, 2.0 * CountDownClock.BALL_HEIGHT); | |
dot = new ImageElement(src: Balls.PNGS[Balls.DK_GRAY_BALL_INDEX]); | |
root.nodes.add(dot); | |
makeAbsolute(dot); | |
setElementPosition(dot, 0.0, 4.0 * CountDownClock.BALL_HEIGHT); | |
} | |
} | |
class ClockNumbers { | |
static const PIXELS = const [ | |
const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ] | |
], const [ | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ] | |
], const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 0 ], | |
const[ 1, 0, 0, 0 ], | |
const[ 1, 1, 1, 1 ] | |
], const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ] | |
], const [ | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ] | |
], const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 0 ], | |
const[ 1, 0, 0, 0 ], | |
const[ 1, 1, 1, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ] | |
], const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 0 ], | |
const[ 1, 0, 0, 0 ], | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ] | |
], const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ] | |
], const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ] | |
], const [ | |
const[ 1, 1, 1, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 0, 0, 0, 1 ], | |
const[ 1, 1, 1, 1 ] | |
] | |
]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file */ | |
/* for details. All rights reserved. Use of this source code is governed by a */ | |
/* BSD-style license that can be found in the LICENSE file. */ | |
body { | |
background-color: #F8F8F8; | |
font-family: 'Open Sans', sans-serif; | |
font-size: 14px; | |
font-weight: normal; | |
line-height: 1.2em; | |
margin: 15px; | |
} | |
p { | |
color: #333; | |
} | |
#canvas-content { | |
width: 100%; | |
height: 400px; | |
position: relative; | |
border: 1px solid #ccc; | |
background-color: #fff; | |
} | |
#summary { | |
float: left; | |
} | |
#notes { | |
float: right; | |
width: 120px; | |
text-align: right; | |
} | |
.error { | |
font-style: italic; | |
color: red; | |
} | |
#container2 { | |
width: 100px; | |
height: 100px; | |
position: relative; | |
margin: auto; | |
} | |
#target { | |
width: 100%; | |
height: 100%; | |
position: absolute; | |
} | |
#target figure { | |
display: block; | |
position: absolute; | |
width: 150px; | |
height: 80px; | |
border: 1px solid #333; | |
font-size: 36px; | |
color: #222; | |
background: #3291d8; | |
text-align: center; | |
line-height: 2em; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment