mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-16 11:42:04 +08:00
feat: support requestAnimationFrame (#8112)
* feat: support requestAnimationFrame * add native helpers to measure time * test(animation-frame): add tests * chore: refactor animation-frame to its own module * chore: fix tslint
This commit is contained in:

committed by
Alexander Vakrilov

parent
0ffc790d82
commit
2aa6e9bf92
24
nativescript-core/animation-frame/animation-frame.d.ts
vendored
Normal file
24
nativescript-core/animation-frame/animation-frame.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Allows you to create, cancel and react to listeners to animation frames.
|
||||||
|
* @module "animation-frame"
|
||||||
|
*/ /** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called on frame rendered
|
||||||
|
* @argument time Time of the current frame in milliseconds
|
||||||
|
*/
|
||||||
|
export interface FrameRequestCallback {
|
||||||
|
(time: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests an animation frame and returns the timer ID
|
||||||
|
* @param cb Callback to be called on frame
|
||||||
|
*/
|
||||||
|
export function requestAnimationFrame(cb: FrameRequestCallback): number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a previously scheduled animation frame request
|
||||||
|
* @param id timer ID to cancel
|
||||||
|
*/
|
||||||
|
export function cancelAnimationFrame(id: number): void;
|
62
nativescript-core/animation-frame/animation-frame.ts
Normal file
62
nativescript-core/animation-frame/animation-frame.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { FPSCallback } from "../fps-meter/fps-native";
|
||||||
|
import { getTimeInFrameBase } from "./animation-native";
|
||||||
|
|
||||||
|
export interface FrameRequestCallback {
|
||||||
|
(time: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationId = 0;
|
||||||
|
let nextFrameAnimationCallbacks: { [key: string]: FrameRequestCallback } = {};
|
||||||
|
let shouldStop = true;
|
||||||
|
let inAnimationFrame = false;
|
||||||
|
let fpsCallback: FPSCallback;
|
||||||
|
let lastFrameTime = 0;
|
||||||
|
|
||||||
|
function getNewId() {
|
||||||
|
return animationId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureNative() {
|
||||||
|
if (fpsCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fpsCallback = new FPSCallback(doFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doFrame(currentTimeMillis: number) {
|
||||||
|
lastFrameTime = currentTimeMillis;
|
||||||
|
shouldStop = true;
|
||||||
|
const thisFrameCbs = nextFrameAnimationCallbacks;
|
||||||
|
nextFrameAnimationCallbacks = {};
|
||||||
|
inAnimationFrame = true;
|
||||||
|
for (const animationId in thisFrameCbs) {
|
||||||
|
if (thisFrameCbs[animationId]) {
|
||||||
|
thisFrameCbs[animationId](lastFrameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inAnimationFrame = false;
|
||||||
|
if (shouldStop) {
|
||||||
|
fpsCallback.stop(); // TODO: check performance without stopping to allow consistent frame times
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requestAnimationFrame(cb: FrameRequestCallback): number {
|
||||||
|
if (!inAnimationFrame) {
|
||||||
|
inAnimationFrame = true;
|
||||||
|
zonedCallback(cb)(getTimeInFrameBase()); // TODO: store and use lastFrameTime
|
||||||
|
inAnimationFrame = false;
|
||||||
|
|
||||||
|
return getNewId();
|
||||||
|
}
|
||||||
|
ensureNative();
|
||||||
|
const animId = getNewId();
|
||||||
|
nextFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback;
|
||||||
|
shouldStop = false;
|
||||||
|
fpsCallback.start();
|
||||||
|
|
||||||
|
return animId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelAnimationFrame(id: number) {
|
||||||
|
delete nextFrameAnimationCallbacks[id];
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export function getTimeInFrameBase(): number {
|
||||||
|
return java.lang.System.nanoTime() / 1000000;
|
||||||
|
}
|
4
nativescript-core/animation-frame/animation-native.d.ts
vendored
Normal file
4
nativescript-core/animation-frame/animation-native.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Gets the time in millisseconds in the same base as frames
|
||||||
|
*/
|
||||||
|
export function getTimeInFrameBase(): number;
|
@ -0,0 +1,3 @@
|
|||||||
|
import { time } from "../profiling";
|
||||||
|
|
||||||
|
export const getTimeInFrameBase = time;
|
6
nativescript-core/animation-frame/package.json
Normal file
6
nativescript-core/animation-frame/package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "animation-frame",
|
||||||
|
"main": "animation-frame",
|
||||||
|
"types": "animation-frame.d.ts",
|
||||||
|
"nativescript": {}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import "./core";
|
import "./core";
|
||||||
|
|
||||||
import "./polyfills/timers";
|
import "./polyfills/timers";
|
||||||
|
import "./polyfills/animation";
|
||||||
import "./polyfills/dialogs";
|
import "./polyfills/dialogs";
|
||||||
import "./polyfills/xhr";
|
import "./polyfills/xhr";
|
||||||
import "./polyfills/fetch";
|
import "./polyfills/fetch";
|
||||||
|
5
nativescript-core/globals/polyfills/animation/animation.d.ts
vendored
Normal file
5
nativescript-core/globals/polyfills/animation/animation.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Installs animations polyfill.
|
||||||
|
* @module "globals/polyfills/animations"
|
||||||
|
*/ /** */
|
||||||
|
import "../../core";
|
@ -0,0 +1,6 @@
|
|||||||
|
import "../../core";
|
||||||
|
import { installPolyfills } from "../polyfill-helpers";
|
||||||
|
|
||||||
|
global.registerModule("animation", () => require("../../../animation-frame"));
|
||||||
|
|
||||||
|
installPolyfills("animation", ["requestAnimationFrame", "cancelAnimationFrame"]);
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "animation",
|
||||||
|
"main": "animation",
|
||||||
|
"types": "animation.d.ts",
|
||||||
|
"nativescript": {}
|
||||||
|
}
|
85
tests/app/animation-frame/animation-frame.ts
Normal file
85
tests/app/animation-frame/animation-frame.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import * as TKUnit from "../tk-unit";
|
||||||
|
import * as animationFrame from "@nativescript/core/animation-frame";
|
||||||
|
import * as fpsNative from "@nativescript/core/fps-meter/fps-native";
|
||||||
|
|
||||||
|
export function test_requestAnimationFrame_isDefined() {
|
||||||
|
TKUnit.assertNotEqual(animationFrame.requestAnimationFrame, undefined, "Method animationFrame.requestAnimationFrame() should be defined!");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_cancelAnimationFrame_isDefined() {
|
||||||
|
TKUnit.assertNotEqual(animationFrame.cancelAnimationFrame, undefined, "Method animationFrame.cancelAnimationFrame() should be defined!");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_requestAnimationFrame() {
|
||||||
|
let completed: boolean;
|
||||||
|
|
||||||
|
const id = animationFrame.requestAnimationFrame(() => {
|
||||||
|
completed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
TKUnit.waitUntilReady(() => completed, 0.5, false);
|
||||||
|
animationFrame.cancelAnimationFrame(id);
|
||||||
|
TKUnit.assert(completed, "Callback should be called!");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_requestAnimationFrame_callbackCalledInCurrentFrame() {
|
||||||
|
let completed: boolean;
|
||||||
|
let currentFrameTime = 0;
|
||||||
|
const frameCb = new fpsNative.FPSCallback((time) => {
|
||||||
|
currentFrameTime = time;
|
||||||
|
});
|
||||||
|
frameCb.start();
|
||||||
|
|
||||||
|
TKUnit.waitUntilReady(() => currentFrameTime > 0, 0.5);
|
||||||
|
let calledTime = 0;
|
||||||
|
animationFrame.requestAnimationFrame((frameTime) => {
|
||||||
|
calledTime = frameTime;
|
||||||
|
completed = calledTime >= frameTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
TKUnit.waitUntilReady(() => completed, 0.5, false);
|
||||||
|
frameCb.stop();
|
||||||
|
TKUnit.assert(completed, "Callback should be called in current frame!");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_requestAnimationFrame_nextCallbackCalledInNextFrame() {
|
||||||
|
let completed: boolean;
|
||||||
|
let currentFrameTime = 0;
|
||||||
|
const frameCb = new fpsNative.FPSCallback((time) => {
|
||||||
|
currentFrameTime = time;
|
||||||
|
});
|
||||||
|
frameCb.start();
|
||||||
|
|
||||||
|
TKUnit.waitUntilReady(() => currentFrameTime > 0, 0.5);
|
||||||
|
animationFrame.requestAnimationFrame((firstFrameTime) => {
|
||||||
|
animationFrame.requestAnimationFrame((frameTime) => {
|
||||||
|
frameCb.stop();
|
||||||
|
completed = frameTime > firstFrameTime && frameTime === currentFrameTime;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
TKUnit.waitUntilReady(() => completed, 0.5, false);
|
||||||
|
frameCb.stop();
|
||||||
|
TKUnit.assert(completed, "Callback should be called in next frame!");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_requestAnimationFrame_shouldBeCancelled() {
|
||||||
|
let completed: boolean;
|
||||||
|
let currentFrameTime = 0;
|
||||||
|
const frameCb = new fpsNative.FPSCallback((time) => {
|
||||||
|
currentFrameTime = time;
|
||||||
|
});
|
||||||
|
frameCb.start();
|
||||||
|
|
||||||
|
TKUnit.waitUntilReady(() => currentFrameTime > 0, 0.5);
|
||||||
|
animationFrame.requestAnimationFrame((firstFrameTime) => {
|
||||||
|
const cbId = animationFrame.requestAnimationFrame((frameTime) => {
|
||||||
|
completed = true;
|
||||||
|
});
|
||||||
|
animationFrame.cancelAnimationFrame(cbId);
|
||||||
|
});
|
||||||
|
|
||||||
|
TKUnit.wait(1);
|
||||||
|
frameCb.stop();
|
||||||
|
TKUnit.assert(!completed, "Callback should not be called");
|
||||||
|
}
|
@ -76,6 +76,9 @@ allTests["OBSERVABLE"] = observableTests;
|
|||||||
import * as timerTests from "./timer/timer-tests";
|
import * as timerTests from "./timer/timer-tests";
|
||||||
allTests["TIMER"] = timerTests;
|
allTests["TIMER"] = timerTests;
|
||||||
|
|
||||||
|
import * as animationFrameTests from "./animation-frame/animation-frame";
|
||||||
|
allTests["ANIMATION-FRAME"] = animationFrameTests;
|
||||||
|
|
||||||
import * as colorTests from "./color/color-tests";
|
import * as colorTests from "./color/color-tests";
|
||||||
allTests["COLOR"] = colorTests;
|
allTests["COLOR"] = colorTests;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user