From bcb287cda585af78a08ff2720624ebd3738412e2 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Sat, 19 Jan 2019 09:57:31 -0500 Subject: [PATCH] Optimize Waveform component to reduce renders and cap max nodes in svg. The waveform component was being re-rendered while the playhead changed, which is a costly operation because it has to calculate the full svg path data string. --- src/components/waveform/waveform.jsx | 95 ++++++++++++++++------------ 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/src/components/waveform/waveform.jsx b/src/components/waveform/waveform.jsx index 1b2abe96e..48a0a4935 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; +// 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; - const cappedData = [0, ...data, 0]; + // 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 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 filteredData = takeEveryN === 1 ? data : + data.filter((_, i) => i % takeEveryN === 0); - 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}`; - }); + const cappedData = [0, ...filteredData, 0]; - return ( - - - - + [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 ( + + - - - ); -}; + + + + + ); + } +} Waveform.propTypes = { data: PropTypes.arrayOf(PropTypes.number),