Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: transition execution timing #559

Merged
merged 14 commits into from Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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);
});
});
});
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
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
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
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