Scene.ts

  1. import Animator from "./Animator";
  2. import SceneItem from "./SceneItem";
  3. import { SELECTOR, DURATION, DELAY, RUNNING } from "./consts";
  4. import { playCSS, exportCSS, getRealId, isPausedCSS, isEndedCSS, setPlayCSS } from "./utils";
  5. import { isFunction, IS_WINDOW, IObject, $, IArrayFormat } from "@daybrush/utils";
  6. import {
  7. AnimateElement, SceneState, SceneOptions, EasingType,
  8. AnimatorState, SceneItemOptions, PlayCondition
  9. } from "./types";
  10. import Frame from "./Frame";
  11. import ListMap from "list-map";
  12. /**
  13. * manage sceneItems and play Scene.
  14. * @sort 1
  15. */
  16. class Scene extends Animator<SceneOptions, SceneState> {
  17. /**
  18. * version info
  19. * @type {string}
  20. * @example
  21. * Scene.VERSION // #__VERSION__#
  22. */
  23. public static VERSION: string = "#__VERSION__#";
  24. public items: ListMap<Scene | SceneItem> = new ListMap();
  25. public temp: IObject<Frame>;
  26. /**
  27. * @param - properties
  28. * @param - options
  29. * @example
  30. const scene = new Scene({
  31. item1: {
  32. 0: {
  33. display: "none",
  34. },
  35. 1: {
  36. display: "block",
  37. opacity: 0,
  38. },
  39. 2: {
  40. opacity: 1,
  41. },
  42. },
  43. item2: {
  44. 2: {
  45. opacity: 1,
  46. },
  47. }
  48. });
  49. */
  50. constructor(properties?: {options?: Partial<SceneOptions>} & IObject<any>, options?: Partial<SceneOptions>) {
  51. super();
  52. this.load(properties, options);
  53. }
  54. public getDuration() {
  55. let time = 0;
  56. this.forEach(item => {
  57. time = Math.max(time, item.getTotalDuration() / item.getPlaySpeed());
  58. });
  59. return time || this.state[DURATION];
  60. }
  61. public setDuration(duration: number) {
  62. const items = this.items;
  63. const sceneDuration = this.getDuration();
  64. if (duration === 0 || !isFinite(sceneDuration)) {
  65. return this;
  66. }
  67. if (sceneDuration === 0) {
  68. this.forEach(item => {
  69. item.setDuration(duration);
  70. });
  71. } else {
  72. const ratio = duration / sceneDuration;
  73. this.forEach(item => {
  74. item.setDelay(item.getDelay() * ratio);
  75. item.setDuration(item.getDuration() * ratio);
  76. });
  77. }
  78. super.setDuration(duration);
  79. return this;
  80. }
  81. public getItem<T extends (Scene | SceneItem) = Scene | SceneItem>(name: number | string): T;
  82. /**
  83. * get item in scene by name
  84. * @param - The item's name
  85. * @return {Scene | SceneItem} item
  86. * @example
  87. const item = scene.getItem("item1")
  88. */
  89. public getItem(name: number | string) {
  90. return this.items.get(name);
  91. }
  92. /**
  93. * create item in scene
  94. * @param {} name - name of item to create
  95. * @param {} options - The option object of SceneItem
  96. * @return {} Newly created item
  97. * @example
  98. const item = scene.newItem("item1")
  99. */
  100. public newItem(name: number | string, options: Partial<SceneItemOptions> = {}): Scene | SceneItem {
  101. if (this.items.has(name)) {
  102. return this.items.get(name);
  103. }
  104. const item = new SceneItem();
  105. this.setItem(name, item);
  106. item.setOptions(options);
  107. return item;
  108. }
  109. /**
  110. * remove item in scene
  111. * @param - name of item to remove
  112. * @return An instance itself
  113. * @example
  114. const item = scene.newItem("item1")
  115. scene.removeItem("item1");
  116. */
  117. public removeItem(name: number | string): this {
  118. this.items.remove(name);
  119. return this;
  120. }
  121. /**
  122. * add a sceneItem to the scene
  123. * @param - name of item to create
  124. * @param - sceneItem
  125. * @example
  126. const item = scene.newItem("item1")
  127. */
  128. public setItem(name: number | string, item: Scene | SceneItem) {
  129. item.setId(name);
  130. this.items.set(name, item);
  131. return this;
  132. }
  133. public setTime(time: number | string, isTick?: boolean, isParent?: boolean, parentEasing?: EasingType) {
  134. super.setTime(time, isTick, isParent);
  135. const iterationTime = this.getIterationTime();
  136. const easing = this.getEasing() || parentEasing;
  137. const frames: IObject<any> = {};
  138. this.forEach(item => {
  139. item.setTime(iterationTime * item.getPlaySpeed() - item.getDelay(), isTick, true, easing);
  140. frames[item.getId()] = item.temp;
  141. });
  142. this.temp = frames;
  143. /**
  144. * This event is fired when timeupdate and animate.
  145. * @event Scene#animate
  146. * @param {object} param The object of data to be sent to an event.
  147. * @param {number} param.currentTime The total time that the animator is running.
  148. * @param {number} param.time The iteration time during duration that the animator is running.
  149. * @param {object} param.frames frames of that time.
  150. * @example
  151. const scene = new Scene({
  152. a: {
  153. 0: {
  154. opacity: 0,
  155. },
  156. 1: {
  157. opacity: 1,
  158. }
  159. },
  160. b: {
  161. 0: {
  162. opacity: 0,
  163. },
  164. 1: {
  165. opacity: 1,
  166. }
  167. }
  168. }).on("animate", e => {
  169. console.log(e.frames);
  170. // {a: Frame, b: Frame}
  171. console.log(e.frames.a.get("opacity"));
  172. });
  173. */
  174. this.trigger("animate", {
  175. frames,
  176. currentTime: this.getTime(),
  177. time: iterationTime,
  178. });
  179. return this;
  180. }
  181. /**
  182. * executes a provided function once for each scene item.
  183. * @param - Function to execute for each element, taking three arguments
  184. * @return {Scene} An instance itself
  185. */
  186. public forEach(
  187. func: (
  188. item: Scene | SceneItem,
  189. id: string | number,
  190. index: number,
  191. items: IObject<Scene | SceneItem>,
  192. ) => void,
  193. ) {
  194. const items = this.items;
  195. items.forEach((item, id, index, obj) => {
  196. func(item, id, index, obj);
  197. });
  198. return this;
  199. }
  200. public toCSS(
  201. playCondition?: PlayCondition,
  202. duration: number = this.getDuration(), parentStates: AnimatorState[] = []) {
  203. const totalDuration = !duration || !isFinite(duration) ? 0 : duration;
  204. const styles: string[] = [];
  205. const state = this.state;
  206. state[DURATION] = this.getDuration();
  207. this.forEach(item => {
  208. styles.push(item.toCSS(playCondition, totalDuration, parentStates.concat(state)));
  209. });
  210. return styles.join("");
  211. }
  212. /**
  213. * Export the CSS of the items to the style.
  214. * @param - Add a selector or className to play.
  215. * @return {Scene} An instance itself
  216. */
  217. public exportCSS(
  218. playCondition?: PlayCondition, duration?: number, parentStates?: AnimatorState[]) {
  219. const css = this.toCSS(playCondition, duration, parentStates);
  220. (!parentStates || !parentStates.length) && exportCSS(getRealId(this), css);
  221. return this;
  222. }
  223. public append(item: SceneItem | Scene) {
  224. item.setDelay(item.getDelay() + this.getDuration());
  225. this.setItem(getRealId(item), item);
  226. }
  227. public pauseCSS() {
  228. return this.forEach(item => {
  229. item.pauseCSS();
  230. });
  231. }
  232. public pause() {
  233. super.pause();
  234. isPausedCSS(this) && this.pauseCSS();
  235. this.forEach(item => {
  236. item.pause();
  237. });
  238. return this;
  239. }
  240. public endCSS() {
  241. this.forEach(item => {
  242. item.endCSS();
  243. });
  244. setPlayCSS(this, false);
  245. }
  246. public end() {
  247. isEndedCSS(this) && this.endCSS();
  248. super.end();
  249. return this;
  250. }
  251. public addPlayClass(isPaused: boolean, playClassName?: string, properties: object = {}) {
  252. let animtionElement: AnimateElement;
  253. this.forEach(item => {
  254. const el = item.addPlayClass(isPaused, playClassName, properties);
  255. !animtionElement && (animtionElement = el);
  256. });
  257. return animtionElement;
  258. }
  259. /**
  260. * Play using the css animation and keyframes.
  261. * @param - Check if you want to export css.
  262. * @param [playClassName="startAnimation"] - Add a class name to play.
  263. * @param - The shorthand properties for six of the animation properties.
  264. * @return {Scene} An instance itself
  265. * @see {@link https://www.w3schools.com/cssref/css3_pr_animation.asp}
  266. * @example
  267. scene.playCSS();
  268. scene.playCSS(false, {
  269. direction: "reverse",
  270. fillMode: "forwards",
  271. });
  272. */
  273. public playCSS(isExportCSS = true, playClassName?: string, properties: Partial<AnimatorState> = {}) {
  274. playCSS(this, isExportCSS, playClassName, properties);
  275. return this;
  276. }
  277. public set(properties: any, ...args: any[]): this;
  278. /**
  279. * Set properties to the Scene.
  280. * @param - properties
  281. * @return An instance itself
  282. * @example
  283. scene.set({
  284. ".a": {
  285. 0: {
  286. opacity: 0,
  287. },
  288. 1: {
  289. opacity: 1,
  290. },
  291. },
  292. });
  293. // 0
  294. console.log(scene.getItem(".a").get(0, "opacity"));
  295. // 1
  296. console.log(scene.getItem(".a").get(1, "opacity"));
  297. */
  298. public set(properties: any) {
  299. this.load(properties);
  300. return this;
  301. }
  302. public load(properties: any = {}, options = properties.options) {
  303. if (!properties) {
  304. return this;
  305. }
  306. const selector = options && options[SELECTOR] || this.state[SELECTOR];
  307. for (const name in properties) {
  308. if (name === "options") {
  309. continue;
  310. }
  311. const object = properties[name];
  312. let item;
  313. if (object instanceof Scene || object instanceof SceneItem) {
  314. this.setItem(name, object);
  315. item = object;
  316. } else if (isFunction(object) && selector) {
  317. const elements =
  318. IS_WINDOW
  319. ? $(`${isFunction(selector) ? selector(name) : name}`, true) as IArrayFormat<AnimateElement>
  320. : ([] as AnimateElement[]);
  321. const length = elements.length;
  322. const scene = new Scene();
  323. for (let i = 0; i < length; ++i) {
  324. (scene.newItem(i) as SceneItem).setId().setElement(elements[i]).load(object(i, elements[i]));
  325. }
  326. this.setItem(name, scene);
  327. continue;
  328. } else {
  329. item = this.newItem(name);
  330. item.load(object);
  331. }
  332. selector && item.setSelector(selector);
  333. }
  334. this.setOptions(options);
  335. }
  336. public setOptions(options: Partial<SceneState> = {}): this {
  337. super.setOptions(options);
  338. const selector = options.selector;
  339. if (selector) {
  340. this.state[SELECTOR] = selector;
  341. }
  342. return this;
  343. }
  344. public setSelector(target?: string | boolean | ((id: number | string) => string)) {
  345. const state = this.state;
  346. const selector = target || state[SELECTOR];
  347. state[SELECTOR] = selector;
  348. const isItFunction = isFunction(target);
  349. if (selector) {
  350. this.forEach((item, name) => {
  351. item.setSelector(isItFunction ? (target as (id: number | string) => string)(name) : selector);
  352. });
  353. }
  354. return this;
  355. }
  356. public start(delay: number = this.state[DELAY]): boolean {
  357. const result = super.start(delay);
  358. if (result) {
  359. this.forEach(item => {
  360. item.start(0);
  361. });
  362. } else {
  363. this.forEach(item => {
  364. item.setPlayState(RUNNING);
  365. });
  366. }
  367. return result;
  368. }
  369. }
  370. export default Scene;