AnimationEffect.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. /*
  2. * Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/VM.h>
  7. #include <LibWeb/Animations/Animation.h>
  8. #include <LibWeb/Animations/AnimationEffect.h>
  9. #include <LibWeb/Bindings/Intrinsics.h>
  10. #include <LibWeb/DOM/Element.h>
  11. #include <LibWeb/WebIDL/ExceptionOr.h>
  12. namespace Web::Animations {
  13. JS_DEFINE_ALLOCATOR(AnimationEffect);
  14. JS::NonnullGCPtr<AnimationEffect> AnimationEffect::create(JS::Realm& realm)
  15. {
  16. return realm.heap().allocate<AnimationEffect>(realm, realm);
  17. }
  18. OptionalEffectTiming EffectTiming::to_optional_effect_timing() const
  19. {
  20. return {
  21. .delay = delay,
  22. .end_delay = end_delay,
  23. .fill = fill,
  24. .iteration_start = iteration_start,
  25. .iterations = iterations,
  26. .duration = duration,
  27. .direction = direction,
  28. .easing = easing,
  29. };
  30. }
  31. // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-gettiming
  32. EffectTiming AnimationEffect::get_timing() const
  33. {
  34. // 1. Returns the specified timing properties for this animation effect.
  35. return {
  36. .delay = m_start_delay,
  37. .end_delay = m_end_delay,
  38. .fill = m_fill_mode,
  39. .iteration_start = m_iteration_start,
  40. .iterations = m_iteration_count,
  41. .duration = m_iteration_duration,
  42. .direction = m_playback_direction,
  43. .easing = m_easing_function,
  44. };
  45. }
  46. // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-getcomputedtiming
  47. ComputedEffectTiming AnimationEffect::get_computed_timing() const
  48. {
  49. // 1. Returns the calculated timing properties for this animation effect.
  50. // Note: Although some of the attributes of the object returned by getTiming() and getComputedTiming() are common,
  51. // their values may differ in the following ways:
  52. // - duration: while getTiming() may return the string auto, getComputedTiming() must return a number
  53. // corresponding to the calculated value of the iteration duration as defined in the description of the
  54. // duration member of the EffectTiming interface.
  55. //
  56. // In this level of the specification, that simply means that an auto value is replaced by zero.
  57. auto duration = m_iteration_duration.has<String>() ? 0.0 : m_iteration_duration.get<double>();
  58. // - fill: likewise, while getTiming() may return the string auto, getComputedTiming() must return the specific
  59. // FillMode used for timing calculations as defined in the description of the fill member of the EffectTiming
  60. // interface.
  61. //
  62. // In this level of the specification, that simply means that an auto value is replaced by the none FillMode.
  63. auto fill = m_fill_mode == Bindings::FillMode::Auto ? Bindings::FillMode::None : m_fill_mode;
  64. return {
  65. {
  66. .delay = m_start_delay,
  67. .end_delay = m_end_delay,
  68. .fill = fill,
  69. .iteration_start = m_iteration_start,
  70. .iterations = m_iteration_count,
  71. .duration = duration,
  72. .direction = m_playback_direction,
  73. .easing = m_easing_function,
  74. },
  75. end_time(),
  76. active_duration(),
  77. local_time(),
  78. transformed_progress(),
  79. current_iteration(),
  80. };
  81. }
  82. // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-updatetiming
  83. // https://www.w3.org/TR/web-animations-1/#update-the-timing-properties-of-an-animation-effect
  84. WebIDL::ExceptionOr<void> AnimationEffect::update_timing(OptionalEffectTiming timing)
  85. {
  86. // 1. If the iterationStart member of input exists and is less than zero, throw a TypeError and abort this
  87. // procedure.
  88. if (timing.iteration_start.has_value() && timing.iteration_start.value() < 0.0)
  89. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration start value"sv };
  90. // 2. If the iterations member of input exists, and is less than zero or is the value NaN, throw a TypeError and
  91. // abort this procedure.
  92. if (timing.iterations.has_value() && (timing.iterations.value() < 0.0 || isnan(timing.iterations.value())))
  93. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration count value"sv };
  94. // 3. If the duration member of input exists, and is less than zero or is the value NaN, throw a TypeError and
  95. // abort this procedure.
  96. // Note: "auto", the only valid string value, is treated as 0.
  97. auto& duration = timing.duration;
  98. if (duration.has_value() && duration->has<double>() && (duration->get<double>() < 0.0 || isnan(duration->get<double>())))
  99. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid duration value"sv };
  100. // FIXME:
  101. // 4. If the easing member of input exists but cannot be parsed using the <easing-function> production
  102. // [CSS-EASING-1], throw a TypeError and abort this procedure.
  103. // 5. Assign each member that exists in input to the corresponding timing property of effect as follows:
  104. // - delay → start delay
  105. if (timing.delay.has_value())
  106. m_start_delay = timing.delay.value();
  107. // - endDelay → end delay
  108. if (timing.end_delay.has_value())
  109. m_end_delay = timing.end_delay.value();
  110. // - fill → fill mode
  111. if (timing.fill.has_value())
  112. m_fill_mode = timing.fill.value();
  113. // - iterationStart → iteration start
  114. if (timing.iteration_start.has_value())
  115. m_iteration_start = timing.iteration_start.value();
  116. // - iterations → iteration count
  117. if (timing.iterations.has_value())
  118. m_iteration_count = timing.iterations.value();
  119. // - duration → iteration duration
  120. if (timing.duration.has_value())
  121. m_iteration_duration = timing.duration.value();
  122. // - direction → playback direction
  123. if (timing.direction.has_value())
  124. m_playback_direction = timing.direction.value();
  125. // - easing → timing function
  126. if (timing.easing.has_value())
  127. m_easing_function = timing.easing.value();
  128. if (auto animation = m_associated_animation)
  129. animation->effect_timing_changed({});
  130. return {};
  131. }
  132. void AnimationEffect::set_associated_animation(JS::GCPtr<Animation> value)
  133. {
  134. m_associated_animation = value;
  135. if (auto* target = this->target())
  136. target->invalidate_style();
  137. }
  138. // https://www.w3.org/TR/web-animations-1/#animation-direction
  139. AnimationDirection AnimationEffect::animation_direction() const
  140. {
  141. // "backwards" if the effect is associated with an animation and the associated animation’s playback rate is less
  142. // than zero; in all other cases, the animation direction is "forwards".
  143. if (m_associated_animation && m_associated_animation->playback_rate() < 0.0)
  144. return AnimationDirection::Backwards;
  145. return AnimationDirection::Forwards;
  146. }
  147. // https://www.w3.org/TR/web-animations-1/#end-time
  148. double AnimationEffect::end_time() const
  149. {
  150. // 1. The end time of an animation effect is the result of evaluating
  151. // max(start delay + active duration + end delay, 0).
  152. return max(m_start_delay + active_duration() + m_end_delay, 0.0);
  153. }
  154. // https://www.w3.org/TR/web-animations-1/#local-time
  155. Optional<double> AnimationEffect::local_time() const
  156. {
  157. // The local time of an animation effect at a given moment is based on the first matching condition from the
  158. // following:
  159. // -> If the animation effect is associated with an animation,
  160. if (m_associated_animation) {
  161. // the local time is the current time of the animation.
  162. return m_associated_animation->current_time();
  163. }
  164. // -> Otherwise,
  165. // the local time is unresolved.
  166. return {};
  167. }
  168. // https://www.w3.org/TR/web-animations-1/#active-duration
  169. double AnimationEffect::active_duration() const
  170. {
  171. // The active duration is calculated as follows:
  172. // active duration = iteration duration × iteration count
  173. // If either the iteration duration or iteration count are zero, the active duration is zero. This clarification is
  174. // needed since the result of infinity multiplied by zero is undefined according to IEEE 754-2008.
  175. if (m_iteration_duration.has<String>() || m_iteration_duration.get<double>() == 0.0 || m_iteration_count == 0.0)
  176. return 0.0;
  177. return m_iteration_duration.get<double>() * m_iteration_count;
  178. }
  179. Optional<double> AnimationEffect::active_time() const
  180. {
  181. return active_time_using_fill(m_fill_mode);
  182. }
  183. // https://www.w3.org/TR/web-animations-1/#calculating-the-active-time
  184. Optional<double> AnimationEffect::active_time_using_fill(Bindings::FillMode fill_mode) const
  185. {
  186. // The active time is based on the local time and start delay. However, it is only defined when the animation effect
  187. // should produce an output and hence depends on its fill mode and phase as follows,
  188. // -> If the animation effect is in the before phase,
  189. if (is_in_the_before_phase()) {
  190. // The result depends on the first matching condition from the following,
  191. // -> If the fill mode is backwards or both,
  192. if (fill_mode == Bindings::FillMode::Backwards || fill_mode == Bindings::FillMode::Both) {
  193. // Return the result of evaluating max(local time - start delay, 0).
  194. return max(local_time().value() - m_start_delay, 0.0);
  195. }
  196. // -> Otherwise,
  197. // Return an unresolved time value.
  198. return {};
  199. }
  200. // -> If the animation effect is in the active phase,
  201. if (is_in_the_active_phase()) {
  202. // Return the result of evaluating local time - start delay.
  203. return local_time().value() - m_start_delay;
  204. }
  205. // -> If the animation effect is in the after phase,
  206. if (is_in_the_after_phase()) {
  207. // The result depends on the first matching condition from the following,
  208. // -> If the fill mode is forwards or both,
  209. if (fill_mode == Bindings::FillMode::Forwards || fill_mode == Bindings::FillMode::Both) {
  210. // Return the result of evaluating max(min(local time - start delay, active duration), 0).
  211. return max(min(local_time().value() - m_start_delay, active_duration()), 0.0);
  212. }
  213. // -> Otherwise,
  214. // Return an unresolved time value.
  215. return {};
  216. }
  217. // -> Otherwise (the local time is unresolved),
  218. // Return an unresolved time value.
  219. return {};
  220. }
  221. // https://www.w3.org/TR/web-animations-1/#before-active-boundary-time
  222. double AnimationEffect::before_active_boundary_time() const
  223. {
  224. // max(min(start delay, end time), 0)
  225. return max(min(m_start_delay, end_time()), 0.0);
  226. }
  227. // https://www.w3.org/TR/web-animations-1/#active-after-boundary-time
  228. double AnimationEffect::after_active_boundary_time() const
  229. {
  230. // max(min(start delay + active duration, end time), 0)
  231. return max(min(m_start_delay + active_duration(), end_time()), 0.0);
  232. }
  233. // https://www.w3.org/TR/web-animations-1/#animation-effect-before-phase
  234. bool AnimationEffect::is_in_the_before_phase() const
  235. {
  236. // An animation effect is in the before phase if the animation effect’s local time is not unresolved and either of
  237. // the following conditions are met:
  238. auto local_time = this->local_time();
  239. if (!local_time.has_value())
  240. return false;
  241. // - the local time is less than the before-active boundary time, or
  242. auto before_active_boundary_time = this->before_active_boundary_time();
  243. if (local_time.value() < before_active_boundary_time)
  244. return true;
  245. // - the animation direction is "backwards" and the local time is equal to the before-active boundary time.
  246. return animation_direction() == AnimationDirection::Backwards && local_time.value() == before_active_boundary_time;
  247. }
  248. // https://www.w3.org/TR/web-animations-1/#animation-effect-after-phase
  249. bool AnimationEffect::is_in_the_after_phase() const
  250. {
  251. // An animation effect is in the after phase if the animation effect’s local time is not unresolved and either of
  252. // the following conditions are met:
  253. auto local_time = this->local_time();
  254. if (!local_time.has_value())
  255. return false;
  256. // - the local time is greater than the active-after boundary time, or
  257. auto after_active_boundary_time = this->after_active_boundary_time();
  258. if (local_time.value() > after_active_boundary_time)
  259. return true;
  260. // - the animation direction is "forwards" and the local time is equal to the active-after boundary time.
  261. return animation_direction() == AnimationDirection::Forwards && local_time.value() == after_active_boundary_time;
  262. }
  263. // https://www.w3.org/TR/web-animations-1/#animation-effect-active-phase
  264. bool AnimationEffect::is_in_the_active_phase() const
  265. {
  266. // An animation effect is in the active phase if the animation effect’s local time is not unresolved and it is not
  267. // in either the before phase nor the after phase.
  268. return local_time().has_value() && !is_in_the_before_phase() && !is_in_the_after_phase();
  269. }
  270. // https://www.w3.org/TR/web-animations-1/#animation-effect-idle-phase
  271. bool AnimationEffect::is_in_the_idle_phase() const
  272. {
  273. // It is often convenient to refer to the case when an animation effect is in none of the above phases as being in
  274. // the idle phase
  275. return !is_in_the_before_phase() && !is_in_the_active_phase() && !is_in_the_after_phase();
  276. }
  277. AnimationEffect::Phase AnimationEffect::phase() const
  278. {
  279. // This is a convenience method that returns the phase of the animation effect, to avoid having to call all of the
  280. // phase functions separately.
  281. // FIXME: There is a lot of duplicated condition checking here which can probably be inlined into this function
  282. if (is_in_the_before_phase())
  283. return Phase::Before;
  284. if (is_in_the_active_phase())
  285. return Phase::Active;
  286. if (is_in_the_after_phase())
  287. return Phase::After;
  288. return Phase::Idle;
  289. }
  290. // https://www.w3.org/TR/web-animations-1/#overall-progress
  291. Optional<double> AnimationEffect::overall_progress() const
  292. {
  293. // 1. If the active time is unresolved, return unresolved.
  294. auto active_time = this->active_time();
  295. if (!active_time.has_value())
  296. return {};
  297. // 2. Calculate an initial value for overall progress based on the first matching condition from below,
  298. double overall_progress;
  299. // -> If the iteration duration is zero,
  300. if (m_iteration_duration.has<String>() || m_iteration_duration.get<double>() == 0.0) {
  301. // If the animation effect is in the before phase, let overall progress be zero, otherwise, let it be equal to
  302. // the iteration count.
  303. if (is_in_the_before_phase())
  304. overall_progress = 0.0;
  305. else
  306. overall_progress = m_iteration_count;
  307. }
  308. // Otherwise,
  309. else {
  310. // Let overall progress be the result of calculating active time / iteration duration.
  311. overall_progress = active_time.value() / m_iteration_duration.get<double>();
  312. }
  313. // 3. Return the result of calculating overall progress + iteration start.
  314. return overall_progress + m_iteration_start;
  315. }
  316. // https://www.w3.org/TR/web-animations-1/#directed-progress
  317. Optional<double> AnimationEffect::directed_progress() const
  318. {
  319. // 1. If the simple iteration progress is unresolved, return unresolved.
  320. auto simple_iteration_progress = this->simple_iteration_progress();
  321. if (!simple_iteration_progress.has_value())
  322. return {};
  323. // 2. Calculate the current direction using the first matching condition from the following list:
  324. auto current_direction = this->current_direction();
  325. // 3. If the current direction is forwards then return the simple iteration progress.
  326. if (current_direction == AnimationDirection::Forwards)
  327. return simple_iteration_progress;
  328. // Otherwise, return 1.0 - simple iteration progress.
  329. return 1.0 - simple_iteration_progress.value();
  330. }
  331. // https://www.w3.org/TR/web-animations-1/#directed-progress
  332. AnimationDirection AnimationEffect::current_direction() const
  333. {
  334. // 2. Calculate the current direction using the first matching condition from the following list:
  335. // -> If playback direction is normal,
  336. if (m_playback_direction == Bindings::PlaybackDirection::Normal) {
  337. // Let the current direction be forwards.
  338. return AnimationDirection::Forwards;
  339. }
  340. // -> If playback direction is reverse,
  341. if (m_playback_direction == Bindings::PlaybackDirection::Reverse) {
  342. // Let the current direction be reverse.
  343. return AnimationDirection::Backwards;
  344. }
  345. // -> Otherwise,
  346. // 1. Let d be the current iteration.
  347. double d = current_iteration().value();
  348. // 2. If playback direction is alternate-reverse increment d by 1.
  349. if (m_playback_direction == Bindings::PlaybackDirection::AlternateReverse)
  350. d += 1.0;
  351. // 3. If d % 2 == 0, let the current direction be forwards, otherwise let the current direction be reverse. If d
  352. // is infinity, let the current direction be forwards.
  353. if (isinf(d))
  354. return AnimationDirection::Forwards;
  355. if (fmod(d, 2.0) == 0.0)
  356. return AnimationDirection::Forwards;
  357. return AnimationDirection::Backwards;
  358. }
  359. // https://www.w3.org/TR/web-animations-1/#simple-iteration-progress
  360. Optional<double> AnimationEffect::simple_iteration_progress() const
  361. {
  362. // 1. If the overall progress is unresolved, return unresolved.
  363. auto overall_progress = this->overall_progress();
  364. if (!overall_progress.has_value())
  365. return {};
  366. // 2. If overall progress is infinity, let the simple iteration progress be iteration start % 1.0, otherwise, let
  367. // the simple iteration progress be overall progress % 1.0.
  368. double simple_iteration_progress = isinf(overall_progress.value()) ? fmod(m_iteration_start, 1.0) : fmod(overall_progress.value(), 1.0);
  369. // 3. If all of the following conditions are true,
  370. // - the simple iteration progress calculated above is zero, and
  371. // - the animation effect is in the active phase or the after phase, and
  372. // - the active time is equal to the active duration, and
  373. // - the iteration count is not equal to zero.
  374. auto active_time = this->active_time();
  375. if (simple_iteration_progress == 0.0 && (is_in_the_active_phase() || is_in_the_after_phase()) && active_time.has_value() && active_time.value() == active_duration() && m_iteration_count != 0.0) {
  376. // let the simple iteration progress be 1.0.
  377. simple_iteration_progress = 1.0;
  378. }
  379. // 4. Return simple iteration progress.
  380. return simple_iteration_progress;
  381. }
  382. // https://www.w3.org/TR/web-animations-1/#current-iteration
  383. Optional<double> AnimationEffect::current_iteration() const
  384. {
  385. // 1. If the active time is unresolved, return unresolved.
  386. auto active_time = this->active_time();
  387. if (!active_time.has_value())
  388. return {};
  389. // 2. If the animation effect is in the after phase and the iteration count is infinity, return infinity.
  390. if (is_in_the_after_phase() && isinf(m_iteration_count))
  391. return m_iteration_count;
  392. // 3. If the simple iteration progress is 1.0, return floor(overall progress) - 1.
  393. auto simple_iteration_progress = this->simple_iteration_progress();
  394. if (simple_iteration_progress.has_value() && simple_iteration_progress.value() == 1.0)
  395. return floor(overall_progress().value()) - 1.0;
  396. // 4. Otherwise, return floor(overall progress).
  397. return floor(overall_progress().value());
  398. }
  399. // https://www.w3.org/TR/web-animations-1/#transformed-progress
  400. Optional<double> AnimationEffect::transformed_progress() const
  401. {
  402. // 1. If the directed progress is unresolved, return unresolved.
  403. auto directed_progress = this->directed_progress();
  404. if (!directed_progress.has_value())
  405. return {};
  406. // 2. Calculate the value of the before flag as follows:
  407. // 1. Determine the current direction using the procedure defined in §4.9.1 Calculating the directed progress.
  408. auto current_direction = this->current_direction();
  409. // 2. If the current direction is forwards, let going forwards be true, otherwise it is false.
  410. auto going_forwards = current_direction == AnimationDirection::Forwards;
  411. // 3. The before flag is set if the animation effect is in the before phase and going forwards is true; or if the animation effect
  412. // is in the after phase and going forwards is false.
  413. auto before_flag = (is_in_the_before_phase() && going_forwards) || (is_in_the_after_phase() && !going_forwards);
  414. // 3. Return the result of evaluating the animation effect’s timing function passing directed progress as the input progress value and
  415. // before flag as the before flag.
  416. return m_timing_function(directed_progress.value(), before_flag);
  417. }
  418. AnimationEffect::AnimationEffect(JS::Realm& realm)
  419. : Bindings::PlatformObject(realm)
  420. {
  421. }
  422. void AnimationEffect::initialize(JS::Realm& realm)
  423. {
  424. Base::initialize(realm);
  425. set_prototype(&Bindings::ensure_web_prototype<Bindings::AnimationEffectPrototype>(realm, "AnimationEffect"_fly_string));
  426. }
  427. }