Skip to content

Commit

Permalink
Merge pull request #559 from openkraken/fix/animation-timing
Browse files Browse the repository at this point in the history
fix: transition execution timing
  • Loading branch information
answershuto authored Aug 10, 2021
2 parents bc59a77 + 9653db8 commit 8dad11a
Show file tree
Hide file tree
Showing 22 changed files with 344 additions and 53 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
130 changes: 130 additions & 0 deletions integration_tests/specs/css/css-transitions/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,134 @@ describe('Transition all', () => {
doneFn();
}, 100);
});

it('set the transition property style before node layout does not work 1', async () => {
const container1 = document.createElement('div');
document.body.appendChild(container1);
setElementStyle(container1, {
position: 'absolute',
top: '100px',
left: 0,
padding: '20px',
backgroundColor: '#999',
transition: 'all 1s linear',
transform: 'translate3d(200px, 0, 0)',
});
container1.appendChild(document.createTextNode('DIV'));
await snapshot();
});

it('set the transition property style before node layout does not work 2', async () => {
const container1 = document.createElement('div');
document.body.appendChild(container1);
setElementStyle(container1, {
position: 'absolute',
top: '100px',
left: 0,
padding: '20px',
backgroundColor: '#999',
transform: 'translate3d(200px, 0, 0)',
});
container1.appendChild(document.createTextNode('DIV'));
await snapshot();

requestAnimationFrame(() => {
setElementStyle(container1, {
transition: 'all 1s linear',
});
});
});

it('set the transition property style after node layout does work', async (done) => {
const container1 = document.createElement('div');
document.body.appendChild(container1);
setElementStyle(container1, {
position: 'absolute',
top: '100px',
left: 0,
padding: '20px',
backgroundColor: '#999',
transition: 'all 1s linear',
});
container1.appendChild(document.createTextNode('DIV'));
await snapshot();

requestAnimationFrame(() => {
setElementStyle(container1, {
transform: 'translate3d(200px, 0, 0)',
});

setTimeout(async () => {
await snapshot();
}, 500);

// Wait for animation finished.
setTimeout(async () => {
await snapshot();
done();
}, 1100);
});
});

it('set the transition style and the transition property style at the same time after node layout does work', async (done) => {
const container1 = document.createElement('div');
document.body.appendChild(container1);
setElementStyle(container1, {
position: 'absolute',
top: '100px',
left: 0,
padding: '20px',
backgroundColor: '#999',
});
container1.appendChild(document.createTextNode('DIV'));
await snapshot();

requestAnimationFrame(() => {
setElementStyle(container1, {
transition: 'all 1s linear',
transform: 'translate3d(200px, 0, 0)',
});

setTimeout(async () => {
await snapshot();
}, 500);

// Wait for animation finished.
setTimeout(async () => {
await snapshot();
done();
}, 1100);
});
});

it('move the transition property style before the transition style after node layout also works', async (done) => {
const container1 = document.createElement('div');
document.body.appendChild(container1);
setElementStyle(container1, {
position: 'absolute',
top: '100px',
left: 0,
padding: '20px',
backgroundColor: '#999',
});
container1.appendChild(document.createTextNode('DIV'));
await snapshot();

requestAnimationFrame(() => {
setElementStyle(container1, {
transform: 'translate3d(200px, 0, 0)',
transition: 'all 1s linear',
});

setTimeout(async () => {
await snapshot();
}, 500);

// Wait for animation finished.
setTimeout(async () => {
await snapshot();
done();
}, 1100);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ describe('Transition events', () => {
transitionTimingFunction: 'linear',
});

container1.style.transform = 'translate(10px, 10px)';
requestAnimationFrame(() => {
container1.style.transform = 'translate(10px, 10px)';
});
});

it('basic transitionstart', (done) => {
Expand Down
10 changes: 10 additions & 0 deletions kraken/lib/src/bridge/to_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ void flushUICommand() {
PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END);
}

List<List<String>> _renderStyleCommands = [];

// For new ui commands, we needs to tell engine to update frames.
for (int i = 0; i < commandLength; i++) {
UICommand command = commands[i];
Expand Down Expand Up @@ -429,6 +431,7 @@ void flushUICommand() {
String key = command.args[0];
String value = command.args[1];
controller.view.setStyle(id, key, value);
_renderStyleCommands.add([id.toString(), key, value]);
break;
case UICommandType.setProperty:
String key = command.args[0];
Expand All @@ -446,5 +449,12 @@ void flushUICommand() {
print('$e\n$stack');
}
}

for (int i = 0; i < _renderStyleCommands.length; i ++) {
var pair = _renderStyleCommands[i];
controller.view.setRenderStyle(int.parse(pair[0]), pair[1], pair[2]);
}

_renderStyleCommands.clear();
}
}
140 changes: 134 additions & 6 deletions kraken/lib/src/css/style_declaration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class CSSStyleDeclaration {
List<StyleChangeListener> _styleChangeListeners = [];

Map<String, String> _properties = {};
Map<String, String> _prevProperties = {};
Map<String, String> _animationProperties = {};

Map<String, List> _transitions = {};
Expand All @@ -139,13 +140,15 @@ class CSSStyleDeclaration {
_transitions = value;
}

bool _shouldTransition(String property, String? prevValue, String nextValue) {
bool _shouldTransition(String property, String? prevValue, String nextValue, RenderBoxModel? renderBoxModel) {
// When begin propertyValue is AUTO, skip animation and trigger style update directly.
if ((prevValue == null && CSSLength.isAuto(CSSInitialValues[property])) || CSSLength.isAuto(prevValue) || CSSLength.isAuto(nextValue)) {
if (CSSLength.isAuto(prevValue) || CSSLength.isAuto(nextValue)) {
return false;
}

if (CSSTransformHandlers[property] != null &&
// Transition does not work when renderBoxModel has not been layouted yet.
if (renderBoxModel != null && renderBoxModel.firstLayouted &&
CSSTransformHandlers[property] != null &&
(_transitions.containsKey(property) || _transitions.containsKey(ALL))) {
bool shouldTransition = false;
// Transtion will be disabled when all transition has transitionDuration as 0.
Expand Down Expand Up @@ -401,6 +404,101 @@ class CSSStyleDeclaration {
}
}

void _setShorthandRenderStyle(String propertyName, Size? viewportSize) {
Map<String, String?> longhandProperties = {};
switch(propertyName) {
case PADDING:
longhandProperties[PADDING_TOP] = _properties[PADDING_TOP];
longhandProperties[PADDING_RIGHT] = _properties[PADDING_RIGHT];
longhandProperties[PADDING_BOTTOM] = _properties[PADDING_BOTTOM];
longhandProperties[PADDING_LEFT] = _properties[PADDING_LEFT];
break;
case MARGIN:
longhandProperties[MARGIN_TOP] = _properties[MARGIN_TOP];
longhandProperties[MARGIN_RIGHT] = _properties[MARGIN_RIGHT];
longhandProperties[MARGIN_BOTTOM] = _properties[MARGIN_BOTTOM];
longhandProperties[MARGIN_LEFT] = _properties[MARGIN_LEFT];
break;
case BACKGROUND:
longhandProperties[BACKGROUND_COLOR] = _properties[BACKGROUND_COLOR];
longhandProperties[BACKGROUND_IMAGE] = _properties[BACKGROUND_IMAGE];
longhandProperties[BACKGROUND_REPEAT] = _properties[BACKGROUND_REPEAT];
longhandProperties[BACKGROUND_ATTACHMENT] = _properties[BACKGROUND_ATTACHMENT];
longhandProperties[BACKGROUND_POSITION_X] = _properties[BACKGROUND_POSITION_X];
longhandProperties[BACKGROUND_POSITION_Y] = _properties[BACKGROUND_POSITION_Y];
longhandProperties[BACKGROUND_SIZE] = _properties[BACKGROUND_SIZE];
break;
case BACKGROUND_POSITION:
longhandProperties[BACKGROUND_POSITION_X] = _properties[BACKGROUND_POSITION_X];
longhandProperties[BACKGROUND_POSITION_Y] = _properties[BACKGROUND_POSITION_Y];
break;
case BORDER_RADIUS:
longhandProperties[BORDER_TOP_LEFT_RADIUS] = _properties[BORDER_TOP_LEFT_RADIUS];
longhandProperties[BORDER_TOP_RIGHT_RADIUS] = _properties[BORDER_TOP_RIGHT_RADIUS];
longhandProperties[BORDER_BOTTOM_RIGHT_RADIUS] = _properties[BORDER_BOTTOM_RIGHT_RADIUS];
longhandProperties[BORDER_BOTTOM_LEFT_RADIUS] = _properties[BORDER_BOTTOM_LEFT_RADIUS];
break;
case OVERFLOW:
longhandProperties[OVERFLOW_X] = _properties[OVERFLOW_X];
longhandProperties[OVERFLOW_Y] = _properties[OVERFLOW_Y];
break;
case FONT:
longhandProperties[FONT_STYLE] = _properties[FONT_STYLE];
longhandProperties[FONT_WEIGHT] = _properties[FONT_WEIGHT];
longhandProperties[FONT_SIZE] = _properties[FONT_SIZE];
longhandProperties[LINE_HEIGHT] = _properties[LINE_HEIGHT];
longhandProperties[FONT_FAMILY] = _properties[FONT_FAMILY];
break;
case FLEX:
longhandProperties[FLEX_GROW] = _properties[FLEX_GROW];
longhandProperties[FLEX_SHRINK] = _properties[FLEX_SHRINK];
longhandProperties[FLEX_BASIS] = _properties[FLEX_BASIS];
break;
case FLEX_FLOW:
longhandProperties[FLEX_DIRECTION] = _properties[FLEX_DIRECTION];
longhandProperties[FLEX_WRAP] = _properties[FLEX_WRAP];
break;
case BORDER:
case BORDER_TOP:
case BORDER_RIGHT:
case BORDER_BOTTOM:
case BORDER_LEFT:
case BORDER_COLOR:
case BORDER_STYLE:
case BORDER_WIDTH:
longhandProperties[BORDER_TOP_COLOR] = _properties[BORDER_TOP_COLOR];
longhandProperties[BORDER_RIGHT_COLOR] = _properties[BORDER_RIGHT_COLOR];
longhandProperties[BORDER_BOTTOM_COLOR] = _properties[BORDER_BOTTOM_COLOR];
longhandProperties[BORDER_LEFT_COLOR] = _properties[BORDER_LEFT_COLOR];
longhandProperties[BORDER_TOP_STYLE] = _properties[BORDER_TOP_STYLE];
longhandProperties[BORDER_RIGHT_STYLE] = _properties[BORDER_RIGHT_STYLE];
longhandProperties[BORDER_BOTTOM_STYLE] = _properties[BORDER_BOTTOM_STYLE];
longhandProperties[BORDER_LEFT_STYLE] = _properties[BORDER_LEFT_STYLE];
longhandProperties[BORDER_TOP_WIDTH] = _properties[BORDER_TOP_WIDTH];
longhandProperties[BORDER_RIGHT_WIDTH] = _properties[BORDER_RIGHT_WIDTH];
longhandProperties[BORDER_BOTTOM_WIDTH] = _properties[BORDER_BOTTOM_WIDTH];
longhandProperties[BORDER_LEFT_WIDTH] = _properties[BORDER_LEFT_WIDTH];
break;
case TRANSITION:
longhandProperties[TRANSITION_PROPERTY] = _properties[TRANSITION_PROPERTY];
longhandProperties[TRANSITION_DURATION] = _properties[TRANSITION_DURATION];
longhandProperties[TRANSITION_TIMING_FUNCTION] = _properties[TRANSITION_TIMING_FUNCTION];
longhandProperties[TRANSITION_DELAY] = _properties[TRANSITION_DELAY];
break;
case TEXT_DECORATION:
longhandProperties[TEXT_DECORATION_LINE] = _properties[TEXT_DECORATION_LINE];
longhandProperties[TEXT_DECORATION_COLOR] = _properties[TEXT_DECORATION_COLOR];
longhandProperties[TEXT_DECORATION_STYLE] = _properties[TEXT_DECORATION_STYLE];
break;
}

if (longhandProperties.isNotEmpty) {
longhandProperties.forEach((String propertyName, String? value) {
setRenderStyle(propertyName, value, viewportSize);
});
}
}

String _replacePattern(String string, String lowerCase, String startString, String endString, [int start = 0]) {
int startIndex = lowerCase.indexOf(startString, start);
if (startIndex >= 0) {
Expand Down Expand Up @@ -581,8 +679,20 @@ class CSSStyleDeclaration {
break;
}

if (prevValue != null) {
_prevProperties[propertyName] = prevValue;
}
_properties[propertyName] = normalizedValue;

switch (propertyName) {
case TRANSITION_DELAY:
case TRANSITION_DURATION:
case TRANSITION_TIMING_FUNCTION:
case TRANSITION_PROPERTY:
CSSTransition.updateTransition(this);
break;
}

// https://github.com/WebKit/webkit/blob/master/Source/WebCore/animation/AnimationTimeline.cpp#L257
// Any animation found in previousAnimations but not found in newAnimations is not longer current and should be canceled.
// @HACK: There are no way to get animationList from styles(Webkit will create an new Style object when style changes, but Kraken not).
Expand All @@ -593,11 +703,25 @@ class CSSStyleDeclaration {
}
_propertyRunningTransition.clear();
}
}

if (_shouldTransition(propertyName, prevValue, normalizedValue)) {
_transition(propertyName, prevValue, normalizedValue, viewportSize, renderStyle);
/// Modify RenderStyle after property is set in CSS declaration.
void setRenderStyle(String propertyName, value, [Size? viewportSize, RenderBoxModel? renderBoxModel]) {
if (CSSShorthandProperty[propertyName] != null) {
return _setShorthandRenderStyle(propertyName, viewportSize);
}

String? currentValue = _properties[propertyName];
String? prevValue = _prevProperties[propertyName];

if (currentValue == null || currentValue == prevValue) {
return;
}
RenderStyle? renderStyle = renderBoxModel?.renderStyle;
if (_shouldTransition(propertyName, prevValue, currentValue, renderBoxModel)) {
_transition(propertyName, prevValue, currentValue, viewportSize, renderStyle);
} else {
setRenderStyleProperty(propertyName, prevValue, normalizedValue);
setRenderStyleProperty(propertyName, prevValue, currentValue);
}
}

Expand All @@ -624,6 +748,10 @@ class CSSStyleDeclaration {
for (int i = 0; i < _styleChangeListeners.length; i++) {
StyleChangeListener listener = _styleChangeListeners[i];
listener(property, original, present);
// Override previous property after property is set.
if (_properties[property] != null) {
_prevProperties[property] = _properties[property]!;
}
}
}

Expand Down
8 changes: 2 additions & 6 deletions kraken/lib/src/css/transition.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ enum CSSTransitionEvent {
cancel,
}

mixin CSSTransitionMixin on Node {
void updateTransition(CSSStyleDeclaration style) {
class CSSTransition {
static void updateTransition(CSSStyleDeclaration style) {
Map<String, List> transitions = {};

List<String> transitionProperty = CSSStyleProperty.getMultipleValues(style[TRANSITION_PROPERTY]) ?? [ALL];
Expand All @@ -59,12 +59,8 @@ mixin CSSTransitionMixin on Node {
String delay = transitionDelay.length == 1 ? transitionDelay[0] : transitionDelay[i];
transitions[property] = [duration, function, delay];
}

style.transitions = transitions;
}
}

class CSSTransition {

static bool isValidTransitionPropertyValue(String value) {
return value == ALL || value == NONE || CSSTextual.isCustomIdent(value);
Expand Down
Loading

0 comments on commit 8dad11a

Please sign in to comment.