Skip to content

Instantly share code, notes, and snippets.

@angelhdzdev
Last active April 15, 2021 04:38
Show Gist options
  • Save angelhdzdev/600de18f5a4ffbe6d4ea63f8bb8eb6d8 to your computer and use it in GitHub Desktop.
Save angelhdzdev/600de18f5a4ffbe6d4ea63f8bb8eb6d8 to your computer and use it in GitHub Desktop.
Dart HTML Sprite, Sprite Animation and Timer Test
import 'dart:html';
import 'dart:async';
class Button extends Sprite {
Button() : super() {
el = document.createElement("button") as ButtonElement;
el.style.left = "0px";
el.style.top = "0px";
}
set label(String text) {
el.innerHtml = text;
}
}
class Sprite {
late HtmlElement el;
Sprite() {
el = document.createElement("div") as DivElement;
el.style.position = "absolute";
el.style.left = "0px";
el.style.top = "0px";
}
set x(num val) => el.style.left = "${val}px";
num get x => num.tryParse(el.style.left.replaceAll("px", "")) as num;
set y(num val) => el.style.top = "${val}px";
num get y => num.tryParse(el.style.top.replaceAll("px", "")) as num;
}
class Frame<T> {
final num id;
final T element;
Frame(this.id, this.element);
}
typedef Fallback = Function();
class Animation<T> {
final Map<int, Frame<T>> _timeline = {};
int _cursor = 0;
int _to = -1;
int _from = 0;
Frame<T>? currentFrame;
final Fallback fallback;
int speed;
bool looping;
Timer? _timer;
Animation({required this.fallback, this.speed = 150, this.looping = false});
final _controller = new StreamController<Frame<T>>();
void addFrame(int id, T element) {
_timeline[id] = Frame<T>(id, element);
}
Stream<Frame<T>> get onChange => _controller.stream;
void runUntil(int end, Function progress, Function complete) {
_timer = Timer.periodic(Duration(milliseconds: speed), (Timer timer) {
_tick(end, progress, complete);
});
}
void _tick(int end, Function progress, Function complete) {
if (_cursor >= end) {
progress();
complete();
} else {
progress();
_cursor++;
}
}
void stop() {
if (_timer != null) {
_timer?.cancel();
_timer = null;
}
}
void play({from = 0, to = -1}) {
stop();
_from = from;
_cursor = _from;
_to = to;
if (_cursor < 0) {
_cursor = 0;
} else if (_cursor >= _timeline.length) {
_cursor = _timeline.length - 1;
}
if (_to >= _timeline.length) {
_to = _timeline.length - 1;
} else if (_to < 0) {
_to = _timeline.length - 1;
}
runUntil(_to, _progress, _complete);
}
void _progress() {
print('Playing frame $_cursor');
playFrame(_cursor);
}
void _complete() {
print("timer stopped.");
if (looping) {
print("looped.");
play(from: _from, to: _to);
}
}
void playFrame(int id) {
if (_timeline.isEmpty) return;
try {
_cursor = id;
currentFrame = _timeline[_cursor];
_controller.sink.add(currentFrame!);
} catch(error) {
currentFrame = Frame<T>(_cursor, this.fallback());
_controller.sink.add(currentFrame!);
}
}
}
class AnimatedSprite extends Sprite {
final animation = Animation<Sprite>(fallback: () => Sprite(), speed: 580);
AnimatedSprite() {
animation.onChange.listen((Frame<Sprite> frame) {
_draw(frame.element);
});
}
void _draw(Sprite sprite) {
if (el.childNodes.isNotEmpty) {
el.firstChild?.remove();
}
el.append(sprite.el);
}
}
Sprite factory(String color) {
final square = Sprite();
square.el.style.width = "50px";
square.el.style.height = "50px";
square.el.style.backgroundColor = color;
return square;
}
Sprite spriteFrom(
ImageElement image,
num destX,
num destY,
int destWidth,
int destHeight
) {
CanvasElement sourceCanvas = document.createElement("canvas") as CanvasElement;
sourceCanvas.width = image.width;
sourceCanvas.height = image.height;
CanvasRenderingContext2D sourceCanvasCtx = sourceCanvas.getContext("2d") as CanvasRenderingContext2D;
sourceCanvasCtx.drawImageScaled(image, destX, destY, image.width ?? 0, image.height ?? 0);
CanvasElement destCanvas = document.createElement("canvas") as CanvasElement;
destCanvas.width = destWidth;
destCanvas.height = destHeight;
CanvasRenderingContext2D destCanvasCtx = destCanvas.getContext("2d") as CanvasRenderingContext2D;
destCanvasCtx.drawImageScaled(
sourceCanvas,
0, 0, image.width ?? 0, image.height ?? 0
);
final sprite = Sprite();
sprite.el.append(destCanvas);
return sprite;
}
late AnimatedSprite link;
void main() {
final data = ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPIA', 'AADHCAMAAAAUAf6IAAAAS1BMVEW1pdVAwPjoSDD4wGj4sDAwiPCo4Pjml1SYUED4+PgAAA', 'D48Mg44IDIeCBIkEj4wIj46Kj4yBBwgDhvMZiY4Gg4UJhIkPD4KFiwwEj9McapAAAAAXRS', 'TlMAQObYZgAADcVJREFUeF7smu1u5LYSRPX7VM2XN8m97/+kmSHaaKh7BhQkA5tMpryQtW', 'WiyUNKtF3m', '8us/p+XX8h', '/TB/lt9EH+', 'IIc+O/Y/z9', '93D+c/vnW+', 'i+XdkTkTYk', 'Bz5s2Rga+v', '/z/09QWcH8', 'zvjTx4U1+D', '+a2R+bpTDh', 'HoD+ZEXgCY', 'I8zbp918Xv', 'n8PPJjjeE2', 'dAJinRN5wb', 'YMFYEXaFiy', '2T4VWHre3l', 'mo1uEQMrfQ', '6S7g8UYnMr', 'ZkJFYIYFsy', 'LKwQwGAbGo', 'IkdTTA7miA', 'JUPtF1vR8T', '5k4LZCvp1I', '5EGMjWQgEQ', 'LYSKyQEQPN', 'iIIgAEFBkw', 'TGBU1ED2Kp', '/UbHO5Evgx', 'gI4sfNgzmQ', 'sRFJnMg5FR', 'U50FR9LJmC', 'ACAJensw1c', 'fZ8V7kQfy4', 'MIgfH3dkro', 'mMTAIvbSra', '6oTEfNWCGQ', 'm5ImuIRG4d', '70aOhxq4X6', '6cbo9rPthI', 'IOoDXKYifT', 'PkhmBckJMZ', 'rdvzqk52fB', 'D5BFwHMZc7', 'Mxdy+0KKf8', 'OYTMVSm2d7', 'PJArghDCTo', 'QoYgCXOqXj', 'PcjXBzNc72', 'J84u7AJSMC', 'LJTfRiZTEc', '3p7cG2oSFI', 'CK99RK9TO9', '6LfAEud9Q/', 'r98Xbncjkc', 'HYJggmU4Gx', 'JLXVjCrdB6', 'm/IBB1il86', '3rVjX7gN5L', '+u3xceL3Mi', 'YwA6MnQfOe', 'yChoyNK3Lw', '0tBwlinIId', 'iHzHPkfLCx', 'kfqQiC8sBR', 'lsG6miEW8z', 'Ddk2erbKNq', '7Iw8fR+R7k', 'BU5cy4N9go', 'LchyQ6coxI', 'ZfsaYvh6hh', 'Bkbee3BND7', 'lTiCvARyKJ', 'CXRB7CHRkb', '6KsPtruP+/', 'aVHTQfSK7a', 'b75rO5FvXC', '9Be71U5AVJ', 'goYsQJLoq/', 'wKAZCgDgMU', 'dvWhIwtA4R', '9AvgvgxSp3', 'BOh+W7T5ao', 'bSnvmk4ADy', '6cTl+q3L+P', '+bZ1+cgG/m', 'C3BieXPkBc', '5nQuP2vXPs', 'gXy+Jz9nTi', 'fO4y6QJzMW', '+vf5r/ReyH', 'P/967yB/mD', 'DAuMy29AZp', 'o/d387Gjyt', 'j0RGrh0Beh', '04hjzJtzMG', '3jhFLIQami', 'TRkQFoyEFG', 'ix8f4hDyJN', '9ewDZszbER', 'thGUob4IgU', 'kVZMtE7ytf', 'hI4i93w7EQ', 'baxhwbYupg', 'FgIHGSABLr', '5lY1fkSKCw', 'YSMyJDL0UJ', 'cjOXZUAoc9', 'D4GNkFBDBp', 'vgWyFLwbwR', 'efRY7ku+vT', '/HDgYkELBG', 'sGW7o2mo+t', 'ggSeg5sjRH', 'TspyX7PVYz', 'm2hSUoKQcD', 'GUOPE6HFiU', 'El44oMBrxx', 'lflfNMv7Hu', 'oezLEHmFBB', 'DmZYtiCTm6', 'C9qoOGCOI5', 'MokDk1B3b4', '5tGyOg+Nim', '18dS8REGS1', 'gk8moF9u/Y', 'Pd8+lGPnq9', 'DQIgaudTCA', 'KcgMMCFhEg', '2ECPcwsgHo', 'yCGLSY6dZZ', 'wE6VsGXLYd', '5CE9Q8725V', '22MLAPeZJv', 'YwnbBjHNsQ', 'MtudKH/Av2', 'dHvMEBAsSh', '0JhO2fQe5D', 'lZAkw+YcW1', 'TkYHaN9cAK', 'mURDLPEJ6I', 'mrHcTHkIfw', '66FuzrHDTr', '9luolmHFpt', 'U+TPSsAKeR', 'BLh5B7vt2n', 'QjJszLFf5t', 'LZPJFlQpaY', 'RwEsEMCHty', '9CfdW2+6Hq', 'p6pP0Qy5Hy', 'H6pCIf5HfV', 'Z5U/yB/kD/', 'IHeX5+G/gB', 'BIDfgjw/jz', '2P9NtXeO5T', 'Q2OzHY2h3c', 'g/fx47axlg', '6iMBqlMKZG', '7DfJzbkX/+', 'PHbWIkp1f4', 'UM6zLp56+Q', 'zMe5Pcf+4f', 'PY61LAzM+8', 'WrRThZIoSW', 'kZ59Ec+/h5', '7CDLUnMfg4', 'QasgwICQK5', 'j3NPjn3sPL', 'ZlniFnqfQp', 'fonuCzLOtI', '/JOHfk2LvP', 'Y9uyoSeWWY', 'qcXkHzAwFq', 'oISMjGQwYj', 'rO7Tn2z5/H', 'RgDOUvluOv', '2C3FYf2QDC', 'NkzGuXnHPn', 'YeG/MsikfC', 'MqH0Tfezvv', 'FqNbGGP5CZ', 'jHM78sHz2B', 'FjY9GRXQ8b', 'Q/fXyb0LMp', 'IENkzGuR35', '4HnsQQCmIP', 's5sjpySe5X', 'D7ZtgNd7BX', 'uCoMPnsS21', 'BwwTaABt9b', 'uPNWRKnYew', '1b5THEA+fB', '4b265DZcFK', 'tyKH33NsEK', 'yRoxazcW5H', 'PnYeG6TXYS', '+1PqL4NceW', '2jsexSbj3I', 'G8/Tz23E/1', '+sWe5tjwtE', '7q73buaLlx', 'FQbAcLaFxv', 'np3bng/d/0', 'YGmqwVGyCo', 'c2cXqi7RSs', 'cTPzjTDG1N', '3XFsGL/NrH', 'tvjtVX6RX2', 'RgNP+8ZFu5', 'WvT5P38u52', 'vF4s403t6Y', 'J8saq3wFlu', 'fPlXx1+buR', 'eXt/f+MZyA', 'CjZL+6U7GY', '9zywrTbjZL', 'G5z1Hz7qYv', 'sLyNxjGy/Z', 'T/HM3v7SZF', 'wfJOHJG92c', 'hilfxOyHRR', '0LwTx2RvNr', 'KIJc/D19hC', 'S1+h+/FGfp', 'eijJPtR40M', 'mofHV3mU7A', 'kR2cWzDWw/', 'UKOB7eLppq', '94OjLxHBm4', 'SGjjx+WD8+', 'duUvFNx8QT', '5OtLiFaDVg', 'cNtQyfP74U', 'iZcWSH6GfH', '2h2CrAu9HE', 'MHr++IIzXk', 'ACHJ6PPP+Y', '8HwDe/5hcP', '/T1z3zr42g', 'F/n3xWsf+0', 'V+kV9kaJ0q', '8XU0dP59ae', 'TMENloPaEg', 'KXFQhDBw/n', '3J5I+PzO1k', 'qBoVuiqgzK', '47dP6d9rFN', '3JtjckGrQ+', 'kJlYoKqHrC', 'yPkBwWozTh', 'ab+xwzx2Sl', '0nAIpSPYIz', '0cjDB8vgVs', 'CCrOY2T7Kf', '85mo/JdulV', 'qHIxGkG6Fn', 'I0fr4XK8GJ', 'Y7I3G1mskr', '+ZfKCWFpVD', 'T+gFejhyvi', 'f3BC+Oyd5s', '5IzmB8j2Nx', 'EBYfx8Cxz5', 'Q4syTrYf9Z', 'vAOyFbJWKy', 'J0RkF7sZ2E', 'iEA9sP1Ghg', 'u9jL9CU6yM', 'H0FUxHTuxp', '+7lJoWTim1', 'R80wluRntZ', 'ipDzaiBeis', 'RLC1zeYk8L', 'TiQy8YKzN+', 'fBfexdPVZk', 'FefBx4Snfn', 'iEnGHwYfDJ', 'twgyvHZFHv', 'jfCL/2sefj', 'tY/NV4u0AK', '0diuHzH0um', '0NN5e3/75W', 'RKKUJEqqzi', 'X04GWNuPTG', '5keVPitw/s', 'hNBzhiepMn', 'wLOQP5Oa5l', 'SmGODNqSn2', 'PGpkDh21dB', 'OyZT7L1LT4', 'OA7Ku6bS0A', '98EWgCNA33', 'fHU2RWMo5s', '4pBM/sgxuV', 'bYHHWAWh2Z', 'lOj758eT1z', 'LXaDdNayqO', 'yWVF20FRs4', 'KLJ1MKff/s', 'eJacEhMzOb', 'LTE5CVqWjl', 'qxmq8h0B+u', '/u+GfISPXm', 'q2xkRbP29B', 'sK9mRjpwSX', 'jn+GjMTMte', 'zNghasmAUs', 'Yk/QWpSSEu', '74Z8h+Xzqe', 'sWOyVFXFIN', '3K38ilRcId', '/xAZJTN1X/', 'ZmalUx2lXx', 'Hsi2Lx2Tg/', 'acrGFuJc8M', 'bLDvs2Rgkn', 'zdXFuYGCN4', 'cwv8scosB5', 'hhmBzvS8+T', 'axcB+QBw5d', 'hcJImZKsf7', '0vPk0iIg37', 'rgpIiZwjc8', 'PCr4269lFa', 'sZM//XBSel', 'rGZp7rqPHZ', 'NNjIrVjJnH', 'F5zWR2JnZC', '/25vGBbWRS', '2pIfFVfEW7', 'KZx5+Xu3s1', 'yP360a+t3o', 'tM0gf+xOPI', 'dx3YJGAlA4', 'lHkSemr/EZ', 'm0TSGTvtjR', 'zfpMZnbCMX', 'KOdktqOCx5', 'Djpcj4FgFJ', 'zCLeko90cZ', 'wlwxTZwpPH', 'twgAUgusrE', 'Y+LhrLcZpM', 'qQTkwceKiS', 'cpSiqANBvy', 'QheLIwM/Qo', '4fHqeflwGs', '3ZI7nyOT0h', 'UyzJPjLQK+', 'b1ckrrKJPc', '3E4+T5jaD5', 'vS+W7lo2cl', '/jK+SJgT25', '3RdvEVyNuM', 'o08WUyA2TA', 'molN3cEtAh', '/xtWzkYB87', 'JhckCnNb9/', 'O/k5qoMhK3', 'kzUKE7+gGV', '5wjl/Lnhzv', 'Y8NKBkf+LM', 'i3mV/DjW8R', 'zFc53semAt', 'RL5BZCvusv', 'W+ev5Xgfm9', 'qCQ0B+6FsE', 'cZWbmYF9bI', 'DDTsnxtWxm', 'BvaxqZenr8', '8WZf9VNnOw', 'jx2RbcrmsA', 'dy/LwMjOxj', 'u7y7qf3yV9', '1+jPAiv97H', '3mOVgQPwNZ', 'dI273E/CvJ', 'R+C4kklg1N', 'ZSELJOdtBy', 'SEghtGO5Tc', '/m2p2SFw4s', 'jUzigGLYkk', '+t5dTIcGqh', '+NYVsuWsJ5', 'bW3zMZI9v7', '2j155SKA5l', 'DqmlOy5fqe', 'iE/PU2VyRl', 'oKaJ1wvL7K', 'WA4UKuInqP', 'Jqpn9fu9Z6', 'jWw54ym5q/', 'c5GYBBMkCY', 'H/8cP2OvYm', '2vDuzzysP5', 'ENcyGxl0C5', 'ARso6Vm/Og', 'X+GCMw4/fW', '3JEmwnMrFj', 'ZBp39PUYOI', 'F8C/MaHDm2', 'L09mgaUjCx', '+0qtsZm1tu', 'Umjw15sUCZ', 'QMXCL7XQ4y', 'SiOzyVc0T+', 'WcvBzblyfD', 'stCTC1Cwdz', 'zXep6s3UxA', '4z0jQyFpOL', 'L9xXhPJmcx', 'n6TT5Wutkp', 'fOGc3E53no', '85Q1sDd5yU', 'BuxxWofNfq', 'iwIkwJNVnD', 'GyisV80g6W', 'r2usee2E05', 'qSW2zIn3wW', '7H1tcgsht/', 'hWciFRUJmR', 'vdjI5AzSbM', 'nUCtI48nG5', 'kfz52Vf51M', 'QnWtvEp0a+', 'eLX6nPXcwF', 'bxSqYkR3Zi', 'zSNUCTYDW6', 'gSBAPbkd0f', 'Bqq50du0K1', 'VurVTZzcmX', '52nrXSZLJN', 'y17P8nF80D', 'ggY4zwsY3P', 'S1khdicj9D', 'Y/WzVshavb', '7XLztcz5HV', 'nBLpVrLeBd', 'EPjPMmu0he', 'JD10X7Y6ak', '9RbGobkQ9I', 'pBaEA1uz+s', 'n4m5TmqdxK', 'Pi7bAQ8HkN', 'a/9ad1wlf0', 'S2w5w18gW5', '3VTDx92QY9', '0mzJtQLSjF', 'bZyEeWlcPC', 'INk/asDfyQ', 'kgvkmZWQLL', 'm1kCy5us/Y', 'urbNfyOuDx', '5O3A9gvOPq', 'e9a2QrcbwU', '0UDQ+LygiV', 'dZ1kNolpep', 'na71ZI3DyK', '6IJ2OLr3DB', '6bJzeb+/bV', 'TW8jvy/ILT', 'UBo72NTluC', 'i59Rz5V273', '2X1Z639P8v', '8u/gW18g2p', 'TFIeSQAAAA', 'BJRU5ErkJg', 'gg=='];
ImageElement linkSprite = document.createElement("img") as ImageElement;
linkSprite.src = data.join("");
linkSprite.onLoad.listen((Event event) {
final stand = spriteFrom(linkSprite, -121, -62, 70, 80);
final walk = spriteFrom(linkSprite, -20, -60, 70, 80);
link = AnimatedSprite();
link.animation.addFrame(0, walk);
link.animation.addFrame(1, stand);
link.animation.looping = true;
document.body?.append(link.el);
});
linkSprite.width = 1000;
linkSprite.height = 1000;
final stop = Button();
stop.label = "Stop";
stop.el.onClick.listen((MouseEvent event) {
link.animation.stop();
});
final play = Button();
play.label = "Play";
play.el.onClick.listen((MouseEvent event) {
link.animation.play();
});
final container = Sprite();
container.el.style.backgroundColor = "#000";
container.el.style.width = "100%";
container.el.style.height = "60px";
container.el.style.display = "flex";
container.el.style.flexDirection = "row";
container.el.style.alignItems = "center";
container.el.style.justifyContent = "center";
container.el.style.setProperty("gap", " 20px");
document.body?.append(container.el);
container.y = 500;
container.el.append(play.el);
container.el.append(stop.el);
final speed = 10;
final keyboard = DefaultKeyboard();
keyboard.left.and([Keyboard.up]).onHold = () {
print("Diagonal Left/Up");
};
keyboard.left.onHold = () {
link.x -= speed;
};
keyboard.right.onHold = () {
link.x += speed;
};
keyboard.up.onHold = () {
link.y -= speed;
};
keyboard.down.onHold = () {
link.y += speed;
};
}
class DefaultKeyboard extends Keyboard {
final left = Key("ArrowLeft");
final right = Key("ArrowRight");
final up = Key("ArrowUp");
final down = Key("ArrowDown");
final enter = Key("Enter");
DefaultKeyboard() {
addKey(left);
addKey(right);
addKey(up);
addKey(down);
addKey(enter);
}
}
class Key {
List<bool> presses = [];
Timer? timer;
final String name;
Function onPress = () {};
Function onRelease = () {};
Function onHold = () {};
Function onRepeat = () {};
bool multiple = false;
Duration repeatDelay = Duration(seconds: 1);
bool isPressed = false;
bool wasPressed = false;
List<List<Key>> _combos = [];
Key and(List<Key> combo) {
_combos.add(combo);
return this;
}
Key(this.name);
}
class Keyboard {
Keyboard() {
init();
}
Function? onKeyUp;
Function? onKeyDown;
List<Key> _keys = [];
void addKey(Key key) {
_keys.add(key);
}
void init() {
window.addEventListener("keydown", (dynamic event) {
event.preventDefault();
late Key key;
try {
key = _keys.firstWhere((Key item) => item.name == event.key);
} catch(error) {
key = Key(event.key);
}
key.isPressed = true;
if (onKeyDown != null) onKeyDown!(key);
});
window.addEventListener("keyup", (dynamic event) {
event.preventDefault();
late Key key;
try {
key = _keys.firstWhere((Key item) => item.name == event.key);
} catch(error) {
key = Key(event.key);
}
key.isPressed = false;
key.wasPressed = false;
if (onKeyUp != null) onKeyUp!(key);
});
Timer.periodic(Duration(milliseconds: 24), (Timer timer) {
_keys.forEach((Key key) {
_render(key);
});
});
}
void _render(Key key) {
if (key.isPressed) {
if (!key.wasPressed) {
key.onPress();
key.wasPressed = true;
if (key.multiple) {
key.presses.add(true);
if (key.presses.length > 2) key.onRepeat(key.presses.length);
if (key.presses.length > 1) return;
key.timer = Timer(key.repeatDelay, () {
key.presses = [];
if (key.timer != null && key.timer!.isActive) {
key.timer!.cancel();
key.timer = null;
}
});
}
}
key.onHold();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment