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 12 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
24 changes: 24 additions & 0 deletions kraken/lib/src/bridge/to_native.dart
Expand Up @@ -426,5 +426,29 @@ void flushUICommand() {
print('$e\n$stack');
}
}

// Set RenderStyle after all styles have been set cause
// some styles such as `transition` do not care about style setting order.
// `transtion: all 1s ease; width: 100px;` and `width: 100px; transtion: all 1s ease;`
// should be considered equal.
for (int i = 0; i < commandLength; i++) {
UICommand command = commands[i];
UICommandType commandType = command.type;
int id = command.id;

try {
switch (commandType) {
case UICommandType.setStyle:
andycall marked this conversation as resolved.
Show resolved Hide resolved
String key = command.args[0];
String value = command.args[1];
controller.view.setRenderStyle(id, key, value);
break;
default:
break;
}
} catch (e, stack) {
print('$e\n$stack');
}
}
}
}
147 changes: 138 additions & 9 deletions kraken/lib/src/css/style_declaration.dart
Expand Up @@ -117,6 +117,7 @@ class CSSStyleDeclaration {
List<StyleChangeListener> _styleChangeListeners = [];

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

Map<String, List> _transitions = {};
Expand All @@ -138,12 +139,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;
}
return CSSTransformHandlers[property] != null &&

// Transition does not work when renderBoxModel has not been layouted yet.
return renderBoxModel != null && renderBoxModel.firstLayouted &&
CSSTransformHandlers[property] != null &&
(_transitions.containsKey(property) || _transitions.containsKey(ALL));
}

Expand Down Expand Up @@ -389,6 +393,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 @@ -487,7 +586,7 @@ class CSSStyleDeclaration {
rootFontSize = renderBoxModel.elementDelegate.getRootElementFontSize();
fontSize = renderStyle.fontSize;
}

switch (propertyName) {
case WIDTH:
case HEIGHT:
Expand Down Expand Up @@ -559,8 +658,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 @@ -571,11 +682,25 @@ class CSSStyleDeclaration {
}
_propertyRunningTransition.clear();
}
}

/// 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 (_shouldTransition(propertyName, prevValue, normalizedValue)) {
_transition(propertyName, prevValue, normalizedValue, viewportSize, renderStyle);
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 @@ -602,6 +727,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 All @@ -611,7 +740,7 @@ class CSSStyleDeclaration {
setRenderStyleProperty(key, null, normalizedValue);
});
}

/// Set all style properties with em unit.
void setEmProperties() {
_properties.forEach((key, value) {
Expand All @@ -621,7 +750,7 @@ class CSSStyleDeclaration {
}
});
}

/// Set all style properties with rem unit.
void setRemProperties() {
_properties.forEach((key, value) {
Expand Down