diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 1c0a3d1254..d9177bebb5 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -1,7 +1,7 @@ # autogenerated file: run scripts/generate-eslint-error-ignore-file to update. src/Markdown.js -src/Velociraptor.js +src/NodeAnimator.js src/components/structures/RoomDirectory.js src/components/views/rooms/MemberList.js src/ratelimitedfunc.js diff --git a/package.json b/package.json index 07975eafd7..7c190c68bf 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.2", "url": "^0.11.0", - "velocity-animate": "^2.0.6", "what-input": "^5.2.10", "zxcvbn": "^4.4.2" }, diff --git a/res/css/_common.scss b/res/css/_common.scss index 87fa4578b1..d6f85edb86 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -28,6 +28,16 @@ $MessageTimestamp_width_hover: calc($MessageTimestamp_width - 2 * $EventTile_e2e :root { font-size: 10px; + + --transition-short: .1s; + --transition-standard: .3s; +} + +@media (prefers-reduced-motion) { + :root { + --transition-short: 0; + --transition-standard: 0; + } } html { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 50adaea305..2b3e179c54 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -283,6 +283,10 @@ $left-gutter: 64px; display: inline-block; height: $font-14px; width: $font-14px; + + transition: + left var(--transition-short) ease-out, + top var(--transition-standard) ease-out; } .mx_EventTile_readAvatarRemainder { diff --git a/src/Velociraptor.js b/src/NodeAnimator.js similarity index 59% rename from src/Velociraptor.js rename to src/NodeAnimator.js index 2da54babe5..8456e6e9fd 100644 --- a/src/Velociraptor.js +++ b/src/NodeAnimator.js @@ -1,16 +1,15 @@ import React from "react"; import ReactDom from "react-dom"; -import Velocity from "velocity-animate"; import PropTypes from 'prop-types'; /** - * The Velociraptor contains components and animates transitions with velocity. + * The NodeAnimator contains components and animates transitions. * It will only pick up direct changes to properties ('left', currently), and so * will not work for animating positional changes where the position is implicit * from DOM order. This makes it a lot simpler and lighter: if you need fully * automatic positional animation, look at react-shuffle or similar libraries. */ -export default class Velociraptor extends React.Component { +export default class NodeAnimator extends React.Component { static propTypes = { // either a list of child nodes, or a single child. children: PropTypes.any, @@ -20,14 +19,10 @@ export default class Velociraptor extends React.Component { // a list of state objects to apply to each child node in turn startStyles: PropTypes.array, - - // a list of transition options from the corresponding startStyle - enterTransitionOpts: PropTypes.array, }; static defaultProps = { startStyles: [], - enterTransitionOpts: [], }; constructor(props) { @@ -41,6 +36,18 @@ export default class Velociraptor extends React.Component { this._updateChildren(this.props.children); } + /** + * + * @param {HTMLElement} node element to apply styles to + * @param {object} styles a key/value pair of CSS properties + * @returns {void} + */ + _applyStyles(node, styles) { + Object.entries(styles).forEach(([property, value]) => { + node.style[property] = value; + }); + } + _updateChildren(newChildren) { const oldChildren = this.children || {}; this.children = {}; @@ -50,17 +57,8 @@ export default class Velociraptor extends React.Component { const oldNode = ReactDom.findDOMNode(this.nodes[old.key]); if (oldNode && oldNode.style.left !== c.props.style.left) { - Velocity(oldNode, { left: c.props.style.left }, this.props.transition).then(() => { - // special case visibility because it's nonsensical to animate an invisible element - // so we always hidden->visible pre-transition and visible->hidden after - if (oldNode.style.visibility === 'visible' && c.props.style.visibility === 'hidden') { - oldNode.style.visibility = c.props.style.visibility; - } - }); - //console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); - } - if (oldNode && oldNode.style.visibility === 'hidden' && c.props.style.visibility === 'visible') { - oldNode.style.visibility = c.props.style.visibility; + this._applyStyles(oldNode, { left: c.props.style.left }); + // console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); } // clone the old element with the props (and children) of the new element // so prop updates are still received by the children. @@ -94,33 +92,22 @@ export default class Velociraptor extends React.Component { this.props.startStyles.length > 0 ) { const startStyles = this.props.startStyles; - const transitionOpts = this.props.enterTransitionOpts; const domNode = ReactDom.findDOMNode(node); // start from startStyle 1: 0 is the one we gave it // to start with, so now we animate 1 etc. - for (var i = 1; i < startStyles.length; ++i) { - Velocity(domNode, startStyles[i], transitionOpts[i-1]); - /* - console.log("start:", - JSON.stringify(transitionOpts[i-1]), - "->", - JSON.stringify(startStyles[i]), - ); - */ + for (let i = 1; i < startStyles.length; ++i) { + this._applyStyles(domNode, startStyles[i]); + // console.log("start:" + // JSON.stringify(startStyles[i]), + // ); } // and then we animate to the resting state - Velocity(domNode, restingStyle, - transitionOpts[i-1]) - .then(() => { - // once we've reached the resting state, hide the element if - // appropriate - domNode.style.visibility = restingStyle.visibility; - }); + setTimeout(() => { + this._applyStyles(domNode, restingStyle); + }, 0); // console.log("enter:", - // JSON.stringify(transitionOpts[i-1]), - // "->", // JSON.stringify(restingStyle)); } this.nodes[k] = node; @@ -128,9 +115,7 @@ export default class Velociraptor extends React.Component { render() { return ( - - { Object.values(this.children) } - + <>{ Object.values(this.children) } ); } } diff --git a/src/VelocityBounce.js b/src/VelocityBounce.js deleted file mode 100644 index ffbf7de829..0000000000 --- a/src/VelocityBounce.js +++ /dev/null @@ -1,17 +0,0 @@ -import Velocity from "velocity-animate"; - -// courtesy of https://github.com/julianshapiro/velocity/issues/283 -// We only use easeOutBounce (easeInBounce is just sort of nonsensical) -function bounce( p ) { - let pow2; - let bounce = 4; - - while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) { - // just sets pow2 - } - return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); -} - -Velocity.Easings.easeOutBounce = function(p) { - return 1 - bounce(1 - p); -}; diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 7473aac7cd..709e6a0db0 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -17,22 +17,13 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import '../../../VelocityBounce'; import { _t } from '../../../languageHandler'; import {formatDate} from '../../../DateUtils'; -import Velociraptor from "../../../Velociraptor"; +import NodeAnimator from "../../../NodeAnimator"; import * as sdk from "../../../index"; import {toPx} from "../../../utils/units"; import {replaceableComponent} from "../../../utils/replaceableComponent"; -let bounce = false; -try { - if (global.localStorage) { - bounce = global.localStorage.getItem('avatar_bounce') == 'true'; - } -} catch (e) { -} - @replaceableComponent("views.rooms.ReadReceiptMarker") export default class ReadReceiptMarker extends React.PureComponent { static propTypes = { @@ -115,7 +106,18 @@ export default class ReadReceiptMarker extends React.PureComponent { // we've already done our display - nothing more to do. return; } + this._animateMarker(); + } + componentDidUpdate(prevProps) { + const differentLeftOffset = prevProps.leftOffset !== this.props.leftOffset; + const visibilityChanged = prevProps.hidden !== this.props.hidden; + if (differentLeftOffset || visibilityChanged) { + this._animateMarker(); + } + } + + _animateMarker() { // treat new RRs as though they were off the top of the screen let oldTop = -15; @@ -139,42 +141,18 @@ export default class ReadReceiptMarker extends React.PureComponent { } const startStyles = []; - const enterTransitionOpts = []; if (oldInfo && oldInfo.left) { // start at the old height and in the old h pos - startStyles.push({ top: startTopOffset+"px", left: toPx(oldInfo.left) }); - - const reorderTransitionOpts = { - duration: 100, - easing: 'easeOut', - }; - - enterTransitionOpts.push(reorderTransitionOpts); } - // then shift to the rightmost column, - // and then it will drop down to its resting position - // - // XXX: We use a small left value to trick velocity-animate into actually animating. - // This is a very annoying bug where if it thinks there's no change to `left` then it'll - // skip applying it, thus making our read receipt at +14px instead of +0px like it - // should be. This does cause a tiny amount of drift for read receipts, however with a - // value so small it's not perceived by a user. - // Note: Any smaller values (or trying to interchange units) might cause read receipts to - // fail to fall down or cause gaps. - startStyles.push({ top: startTopOffset+'px', left: '1px' }); - enterTransitionOpts.push({ - duration: bounce ? Math.min(Math.log(Math.abs(startTopOffset)) * 200, 3000) : 300, - easing: bounce ? 'easeOutBounce' : 'easeOutCubic', - }); + startStyles.push({ top: startTopOffset+'px', left: '0' }); this.setState({ suppressDisplay: false, startStyles: startStyles, - enterTransitionOpts: enterTransitionOpts, }); } @@ -187,7 +165,6 @@ export default class ReadReceiptMarker extends React.PureComponent { const style = { left: toPx(this.props.leftOffset), top: '0px', - visibility: this.props.hidden ? 'hidden' : 'visible', }; let title; @@ -210,9 +187,8 @@ export default class ReadReceiptMarker extends React.PureComponent { } return ( - + - + ); } } diff --git a/yarn.lock b/yarn.lock index cfae85608a..66329cfa89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8144,11 +8144,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -velocity-animate@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/velocity-animate/-/velocity-animate-2.0.6.tgz#1811ca14df7fbbef05740256f6cec0fd1b76575f" - integrity sha512-tU+/UtSo3GkIjEfk2KM4e24DvpgX0+FzfLr7XqNwm9BCvZUtbCHPq/AFutx/Mkp2bXlUS9EcX8yxu8XmzAv2Kw== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"