diff --git a/src/components/audio-trimmer/audio-trimmer.css b/src/components/audio-trimmer/audio-trimmer.css index fe276c3819eed948b33d34cde90da60f19bca77f..3d9fa65a190ee08844f2079730346973e9bfd54a 100644 --- a/src/components/audio-trimmer/audio-trimmer.css +++ b/src/components/audio-trimmer/audio-trimmer.css @@ -12,6 +12,9 @@ $hover-scale: 2; left: 0; width: 100%; height: 100%; + + /* Force the browser to paint separately to avoid composite cost with waveform */ + transform: translateZ(0); } .trim-background { @@ -51,7 +54,15 @@ $hover-scale: 2; } .playhead { - border: 1px solid $motion-primary; + /* + Even though playhead is just a line, it is 100% width (the width of the waveform) + so that we can use transform: translateX() using percentages. + */ + width: 100%; + border-left: 1px solid $motion-primary; + border-top: none; + border-bottom: none; + border-right: none; } .start-trim-line { diff --git a/src/components/audio-trimmer/audio-trimmer.jsx b/src/components/audio-trimmer/audio-trimmer.jsx index d1b21c92664cebc586212c9833629ba90da8f701..e2ad295fc09ff22c5725eb0285234f5f419a212f 100644 --- a/src/components/audio-trimmer/audio-trimmer.jsx +++ b/src/components/audio-trimmer/audio-trimmer.jsx @@ -35,7 +35,7 @@ const AudioTrimmer = props => ( <Box className={classNames(styles.trimLine, styles.playhead)} style={{ - left: `${100 * props.playhead}%` + transform: `translateX(${100 * props.playhead}%)` }} /> ) : null} diff --git a/src/components/waveform/waveform.jsx b/src/components/waveform/waveform.jsx index 1b2abe96ebf5ed306bde9e3ba9d54a202dd846be..48a0a4935c376995624e4cf4c41c194539b27ec8 100644 --- a/src/components/waveform/waveform.jsx +++ b/src/components/waveform/waveform.jsx @@ -2,52 +2,65 @@ import React from 'react'; import PropTypes from 'prop-types'; import styles from './waveform.css'; -const Waveform = props => { - const { - width, - height, - data - } = props; - - const cappedData = [0, ...data, 0]; - - const points = [ - ...cappedData.map((v, i) => - [width * i / cappedData.length, height * v / 2] - ), - ...cappedData.reverse().map((v, i) => - [width * (cappedData.length - i - 1) / cappedData.length, -height * v / 2] - ) - ]; - - const pathComponents = points.map(([x, y], i) => { - const [nx, ny] = points[i < points.length - 1 ? i + 1 : 0]; - return `Q${x} ${y} ${(x + nx) / 2} ${(y + ny) / 2}`; - }); - - return ( - <svg - className={styles.container} - viewBox={`-1 0 ${width} ${height}`} - > - <line - className={styles.baseline} - x1={-1} - x2={width} - y1={height / 2} - y2={height / 2} - /> - <g transform={`scale(1, -1) translate(0, -${height / 2})`}> - <path - className={styles.waveformPath} - d={`M0 0${pathComponents.join(' ')}Z`} - strokeLinejoin={'round'} - strokeWidth={1} +// Waveform is expensive to compute, make sure it only updates when data does +// by using PureComponent. In future can be changed back to function with React.memo +// eslint-disable-next-line react/prefer-stateless-function +class Waveform extends React.PureComponent { + render () { + const { + width, + height, + data + } = this.props; + + // Never want a density of points higher than the number of pixels + // This is very conservative, could be far fewer points because of curve smoothing. + // Drawing too many points seems to cause an explosion in browser + // composite time when animating the playhead + const takeEveryN = Math.ceil(data.length / width); + + const filteredData = takeEveryN === 1 ? data : + data.filter((_, i) => i % takeEveryN === 0); + + const cappedData = [0, ...filteredData, 0]; + + const points = [ + ...cappedData.map((v, i) => + [width * i / cappedData.length, height * v / 2] + ), + ...cappedData.reverse().map((v, i) => + [width * (cappedData.length - i - 1) / cappedData.length, -height * v / 2] + ) + ]; + const pathComponents = points.map(([x, y], i) => { + const [nx, ny] = points[i < points.length - 1 ? i + 1 : 0]; + return `Q${x} ${y} ${(x + nx) / 2} ${(y + ny) / 2}`; + }); + + return ( + <svg + className={styles.container} + viewBox={`-1 0 ${width} ${height}`} + > + <line + className={styles.baseline} + x1={-1} + x2={width} + y1={height / 2} + y2={height / 2} /> - </g> - </svg> - ); -}; + <g transform={`scale(1, -1) translate(0, -${height / 2})`}> + <path + className={styles.waveformPath} + d={`M0 0${pathComponents.join(' ')}Z`} + strokeLinejoin={'round'} + strokeWidth={1} + /> + </g> + </svg> + ); + } +} Waveform.propTypes = { data: PropTypes.arrayOf(PropTypes.number), diff --git a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap index 93ebc2cf68c794ad31bbf93e6f61b6ec5ac440b5..77df3c54966667bf29f37063ce62d857ed95c2c9 100644 --- a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap +++ b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap @@ -224,7 +224,7 @@ exports[`Sound Editor Component matches snapshot 1`] = ` "flexWrap": undefined, "height": undefined, "justifyContent": undefined, - "left": "50%", + "transform": "translateX(50%)", "width": undefined, } }