Skip to content

Commit b4a0ca2

Browse files
committed
Start working on CSS animation events
1 parent 26326bd commit b4a0ca2

12 files changed

Lines changed: 193 additions & 6 deletions

File tree

apps/fabric-example/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSAnimation.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,22 @@ AnimationProgressState CSSAnimation::getState(double timestamp) const {
4646
return progressProvider_->getState(timestamp);
4747
}
4848

49+
unsigned CSSAnimation::getCurrentIteration() const {
50+
return progressProvider_->getCurrentIteration();
51+
}
52+
53+
double CSSAnimation::getDuration() const {
54+
return progressProvider_->getDuration();
55+
}
56+
57+
double CSSAnimation::getDelay() const {
58+
return progressProvider_->getDelay();
59+
}
60+
61+
double CSSAnimation::getIterationCount() const {
62+
return progressProvider_->getIterationCount();
63+
}
64+
4965
bool CSSAnimation::isReversed() const {
5066
const auto direction = progressProvider_->getDirection();
5167
return direction == AnimationDirection::Reverse || direction == AnimationDirection::AlternateReverse;

packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSAnimation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ class CSSAnimation {
2626

2727
double getStartTimestamp(double timestamp) const;
2828
AnimationProgressState getState(double timestamp) const;
29+
unsigned getCurrentIteration() const;
30+
double getDuration() const;
31+
double getDelay() const;
32+
double getIterationCount() const;
2933
bool isReversed() const;
3034

3135
bool hasForwardsFillMode() const;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <reanimated/CSS/core/CSSAnimation.h>
2+
#include <reanimated/CSS/events/CSSAnimationEvent.h>
3+
4+
#include <algorithm>
5+
6+
namespace reanimated::css {
7+
8+
CSSAnimationEvent createAnimationStartEvent(
9+
const facebook::react::Tag viewTag,
10+
const std::shared_ptr<CSSAnimation> &animation) {
11+
const auto activeDuration = animation->getDuration() * animation->getIterationCount();
12+
const auto elapsedTime = std::min(std::max(-animation->getDelay(), 0.0), activeDuration);
13+
return {viewTag, CSSAnimationEventType::AnimationStart, animation->getName(), elapsedTime};
14+
}
15+
16+
CSSAnimationEvent createAnimationIterationEvent(
17+
const facebook::react::Tag viewTag,
18+
const std::shared_ptr<CSSAnimation> &animation,
19+
const unsigned iteration) {
20+
const auto elapsedTime = static_cast<double>(iteration - 1) * animation->getDuration();
21+
return {viewTag, CSSAnimationEventType::AnimationIteration, animation->getName(), elapsedTime};
22+
}
23+
24+
CSSAnimationEvent createAnimationEndEvent(
25+
const facebook::react::Tag viewTag,
26+
const std::shared_ptr<CSSAnimation> &animation) {
27+
const auto elapsedTime = animation->getDuration() * animation->getIterationCount();
28+
return {viewTag, CSSAnimationEventType::AnimationEnd, animation->getName(), elapsedTime};
29+
}
30+
31+
} // namespace reanimated::css
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#pragma once
2+
3+
#include <react/renderer/core/ReactPrimitives.h>
4+
5+
#include <cstdint>
6+
#include <memory>
7+
#include <string>
8+
9+
namespace reanimated::css {
10+
11+
class CSSAnimation;
12+
13+
enum class CSSAnimationEventType : std::uint8_t {
14+
AnimationStart = 1 << 0,
15+
AnimationEnd = 1 << 1,
16+
AnimationIteration = 1 << 2
17+
};
18+
19+
// Bitmask of CSSAnimationEventType values representing which events a view
20+
// is listening for. A value of 0 means no listeners are registered.
21+
using CSSAnimationEventListeners = std::uint8_t;
22+
23+
inline bool hasListener(CSSAnimationEventListeners listeners, CSSAnimationEventType type) {
24+
return (listeners & static_cast<std::uint8_t>(type)) != 0;
25+
}
26+
27+
struct CSSAnimationEvent {
28+
facebook::react::Tag viewTag;
29+
CSSAnimationEventType type;
30+
std::string animationName;
31+
double elapsedTime; // in milliseconds (convert to seconds at dispatch time)
32+
};
33+
34+
CSSAnimationEvent createAnimationStartEvent(
35+
facebook::react::Tag viewTag,
36+
const std::shared_ptr<CSSAnimation> &animation);
37+
CSSAnimationEvent createAnimationIterationEvent(
38+
facebook::react::Tag viewTag,
39+
const std::shared_ptr<CSSAnimation> &animation,
40+
unsigned iteration);
41+
CSSAnimationEvent createAnimationEndEvent(facebook::react::Tag viewTag, const std::shared_ptr<CSSAnimation> &animation);
42+
43+
} // namespace reanimated::css

packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/AnimationProgressProvider.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ AnimationDirection AnimationProgressProvider::getDirection() const {
3535
return direction_;
3636
}
3737

38+
unsigned AnimationProgressProvider::getCurrentIteration() const {
39+
return currentIteration_;
40+
}
41+
42+
double AnimationProgressProvider::getIterationCount() const {
43+
return iterationCount_;
44+
}
45+
3846
double AnimationProgressProvider::getGlobalProgress() const {
3947
return applyAnimationDirection(rawProgress_.value_or(0));
4048
}

packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/AnimationProgressProvider.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class AnimationProgressProvider final : public KeyframeProgressProvider, public
3333
void setEasingFunction(const EasingFunction &easingFunction);
3434

3535
AnimationDirection getDirection() const;
36+
unsigned getCurrentIteration() const;
37+
double getIterationCount() const;
3638
double getGlobalProgress() const override;
3739
double getKeyframeProgress(double fromOffset, double toOffset) const override;
3840
AnimationProgressState getState(double timestamp) const;

packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/RawProgressProvider.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ namespace reanimated::css {
55
RawProgressProvider::RawProgressProvider(const double timestamp, const double duration, const double delay)
66
: duration_(duration), delay_(delay), creationTimestamp_(timestamp) {}
77

8+
double RawProgressProvider::getDuration() const {
9+
return duration_;
10+
}
11+
12+
double RawProgressProvider::getDelay() const {
13+
return delay_;
14+
}
15+
816
void RawProgressProvider::setDuration(double duration) {
917
duration_ = duration;
1018
}

packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/RawProgressProvider.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class RawProgressProvider {
77
public:
88
RawProgressProvider(double timestamp, double duration, double delay);
99

10+
double getDuration() const;
11+
double getDelay() const;
1012
void setDuration(double duration);
1113
void setDelay(double delay);
1214

packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ void CSSAnimationsRegistry::apply(
2424
const std::optional<std::vector<std::string>> &animationNames,
2525
const CSSAnimationsMap &newAnimations,
2626
const CSSAnimationSettingsUpdatesMap &settingsUpdates,
27-
double timestamp) {
27+
const CSSAnimationEventListeners eventListeners,
28+
const double timestamp) {
2829
auto animationsVector = buildAnimationsVector(rt, shadowNode, animationNames, newAnimations);
2930

3031
const auto viewTag = shadowNode->getTag();
32+
33+
// Update event listener tracking based on current callback props
34+
if (eventListeners != 0) {
35+
eventListenersMap_[viewTag] = eventListeners;
36+
} else {
37+
eventListenersMap_.erase(viewTag);
38+
}
39+
3140
if (animationsVector.empty()) {
3241
remove(viewTag);
3342
return;
@@ -60,6 +69,7 @@ void CSSAnimationsRegistry::remove(const Tag viewTag) {
6069
removeViewAnimations(viewTag);
6170
removeFromUpdatesRegistry(viewTag);
6271
registry_.erase(viewTag);
72+
eventListenersMap_.erase(viewTag);
6373
}
6474

6575
void CSSAnimationsRegistry::update(const double timestamp) {
@@ -82,6 +92,12 @@ void CSSAnimationsRegistry::update(const double timestamp) {
8292
}
8393
}
8494

95+
std::vector<CSSAnimationEvent> CSSAnimationsRegistry::flushEvents() {
96+
std::vector<CSSAnimationEvent> events;
97+
events.swap(pendingEvents_);
98+
return events;
99+
}
100+
85101
CSSAnimationsVector CSSAnimationsRegistry::buildAnimationsVector(
86102
jsi::Runtime &rt,
87103
const std::shared_ptr<const ShadowNode> &shadowNode,
@@ -175,19 +191,29 @@ void CSSAnimationsRegistry::updateViewAnimations(
175191
std::shared_ptr<const ShadowNode> shadowNode = nullptr;
176192
bool hasUpdates = false;
177193

194+
const bool detectEvents = eventListenersMap_.count(viewTag) > 0;
195+
178196
for (const auto animationIndex : animationIndices) {
179197
const auto &animation = registry_[viewTag].animationsVector[animationIndex];
180198
if (!shadowNode) {
181199
shadowNode = animation->getShadowNode();
182200
}
183-
if (animation->getState(timestamp) == AnimationProgressState::Pending) {
201+
202+
const AnimationStateSnapshot beforeSnapshot = {animation->getState(timestamp), animation->getCurrentIteration()};
203+
204+
if (beforeSnapshot.state == AnimationProgressState::Pending) {
184205
animation->run(timestamp);
185206
}
186207

187208
bool updatesAddedToBatch = false;
188209
const auto updates = animation->update(timestamp);
189210
const auto newState = animation->getState(timestamp);
190211

212+
if (detectEvents) {
213+
const AnimationStateSnapshot afterSnapshot = {newState, animation->getCurrentIteration()};
214+
detectAnimationEvents(viewTag, animation, beforeSnapshot, afterSnapshot);
215+
}
216+
191217
if (newState == AnimationProgressState::Finished) {
192218
// Revert changes applied during animation if there is no forwards fill
193219
// mode
@@ -318,6 +344,29 @@ void CSSAnimationsRegistry::handleAnimationsToRevert(const double timestamp) {
318344
animationsToRevertMap_.clear();
319345
}
320346

347+
void CSSAnimationsRegistry::detectAnimationEvents(
348+
const Tag viewTag,
349+
const std::shared_ptr<CSSAnimation> &animation,
350+
const AnimationStateSnapshot &before,
351+
const AnimationStateSnapshot &after) {
352+
const auto listeners = eventListenersMap_.at(viewTag);
353+
354+
if (hasListener(listeners, CSSAnimationEventType::AnimationStart) &&
355+
before.state == AnimationProgressState::Pending && after.state != AnimationProgressState::Pending) {
356+
pendingEvents_.emplace_back(createAnimationStartEvent(viewTag, animation));
357+
}
358+
359+
if (hasListener(listeners, CSSAnimationEventType::AnimationIteration) &&
360+
after.state == AnimationProgressState::Running && after.iteration > before.iteration) {
361+
pendingEvents_.emplace_back(createAnimationIterationEvent(viewTag, animation, after.iteration));
362+
}
363+
364+
if (hasListener(listeners, CSSAnimationEventType::AnimationEnd) && before.state != AnimationProgressState::Finished &&
365+
after.state == AnimationProgressState::Finished) {
366+
pendingEvents_.emplace_back(createAnimationEndEvent(viewTag, animation));
367+
}
368+
}
369+
321370
bool CSSAnimationsRegistry::addStyleUpdates(
322371
folly::dynamic &target,
323372
const folly::dynamic &updates,

0 commit comments

Comments
 (0)