Check visibility performance optimization.

This commit is contained in:
Mike Cao 2020-08-01 23:37:46 -07:00
parent 418793feaf
commit cb7f267212
7 changed files with 172 additions and 67 deletions

View File

@ -0,0 +1,34 @@
import React, { useState, useRef, useEffect } from 'react';
function isInViewport(node) {
return (
window.pageYOffset < node.offsetTop + node.clientHeight ||
window.pageXOffset < node.offsetLeft + node.clientWidth
);
}
export default function CheckVisible({ children }) {
const [visible, setVisible] = useState(false);
const ref = useRef();
useEffect(() => {
const checkPosition = () => {
if (ref.current) {
const state = isInViewport(ref.current);
if (state !== visible) {
setVisible(state);
}
}
};
checkPosition();
window.addEventListener('scroll', checkPosition);
return () => {
window.removeEventListener('scroll', checkPosition);
};
}, [visible]);
return <div ref={ref}>{typeof children === 'function' ? children(visible) : children}</div>;
}

View File

@ -5,7 +5,14 @@ import ChartJS from 'chart.js';
import { format } from 'date-fns'; import { format } from 'date-fns';
import styles from './PageviewsChart.module.css'; import styles from './PageviewsChart.module.css';
export default function PageviewsChart({ websiteId, data, unit, className, children }) { export default function PageviewsChart({
websiteId,
data,
unit,
animationDuration = 300,
className,
children,
}) {
const canvas = useRef(); const canvas = useRef();
const chart = useRef(); const chart = useRef();
const [tooltip, setTooltip] = useState({}); const [tooltip, setTooltip] = useState({});
@ -75,7 +82,7 @@ export default function PageviewsChart({ websiteId, data, unit, className, child
}, },
options: { options: {
animation: { animation: {
duration: 300, duration: animationDuration,
}, },
tooltips: { tooltips: {
enabled: false, enabled: false,
@ -125,6 +132,7 @@ export default function PageviewsChart({ websiteId, data, unit, className, child
datasets[1].data = data.pageviews; datasets[1].data = data.pageviews;
options.scales.xAxes[0].time.unit = unit; options.scales.xAxes[0].time.unit = unit;
options.scales.xAxes[0].ticks.callback = renderLabel; options.scales.xAxes[0].ticks.callback = renderLabel;
options.animation.duration = animationDuration;
chart.current.update(); chart.current.update();
} }
@ -133,6 +141,7 @@ export default function PageviewsChart({ websiteId, data, unit, className, child
useEffect(() => { useEffect(() => {
if (data) { if (data) {
draw(); draw();
setTooltip(null);
} }
}, [data]); }, [data]);

View File

@ -14,6 +14,7 @@ export default function RankingsChart({
heading, heading,
className, className,
dataFilter, dataFilter,
animate = true,
onDataLoad = () => {}, onDataLoad = () => {},
}) { }) {
const [data, setData] = useState(); const [data, setData] = useState();
@ -45,7 +46,7 @@ export default function RankingsChart({
}, [websiteId, startDate, endDate, type]); }, [websiteId, startDate, endDate, type]);
if (!data) { if (!data) {
return <h1>loading...</h1>; return null;
} }
return ( return (
@ -54,14 +55,29 @@ export default function RankingsChart({
<div className={styles.title}>{title}</div> <div className={styles.title}>{title}</div>
<div className={styles.heading}>{heading}</div> <div className={styles.heading}>{heading}</div>
</div> </div>
{rankings.map(({ x, y, z }) => ( {rankings.map(({ x, y, z }) =>
<Row key={x} label={x} value={y} percent={z} /> animate ? (
))} <AnimatedRow key={x} label={x} value={y} percent={z} />
) : (
<Row key={x} label={x} value={y} percent={z} />
),
)}
</div> </div>
); );
} }
const Row = ({ label, value, percent }) => { const Row = ({ label, value, percent }) => (
<div className={styles.row}>
<div className={styles.label}>{label}</div>
<div className={styles.value}>{value.toFixed(0)}</div>
<div className={styles.percent}>
<div>{`${percent.toFixed(0)}%`}</div>
<div className={styles.bar} style={{ width: percent }} />
</div>
</div>
);
const AnimatedRow = ({ label, value, percent }) => {
const props = useSpring({ width: percent, y: value, from: { width: 0, y: 0 } }); const props = useSpring({ width: percent, y: value, from: { width: 0, y: 0 } });
return ( return (

View File

@ -13,6 +13,7 @@ export default function WebsiteChart({
websiteId, websiteId,
defaultDateRange = '7day', defaultDateRange = '7day',
stickHeader = false, stickHeader = false,
animate = true,
onDateChange = () => {}, onDateChange = () => {},
}) { }) {
const [data, setData] = useState(); const [data, setData] = useState();
@ -80,6 +81,7 @@ export default function WebsiteChart({
websiteId={websiteId} websiteId={websiteId}
data={{ pageviews, uniques }} data={{ pageviews, uniques }}
unit={unit} unit={unit}
animationDuration={animate ? 300 : 0}
> >
<QuickButtons value={value} onChange={handleDateChange} /> <QuickButtons value={value} onChange={handleDateChange} />
</PageviewsChart> </PageviewsChart>

View File

@ -3,6 +3,7 @@ import classNames from 'classnames';
import WebsiteChart from './WebsiteChart'; import WebsiteChart from './WebsiteChart';
import RankingsChart from './RankingsChart'; import RankingsChart from './RankingsChart';
import WorldMap from './WorldMap'; import WorldMap from './WorldMap';
import CheckVisible from './CheckVisible';
import { getDateRange } from 'lib/date'; import { getDateRange } from 'lib/date';
import { get } from 'lib/web'; import { get } from 'lib/web';
import { browserFilter, urlFilter, refFilter, deviceFilter, countryFilter } from 'lib/filters'; import { browserFilter, urlFilter, refFilter, deviceFilter, countryFilter } from 'lib/filters';
@ -32,7 +33,7 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
}, [websiteId]); }, [websiteId]);
if (!data) { if (!data) {
return <h1>loading...</h1>; return null;
} }
return ( return (
@ -40,82 +41,121 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
<div className="row"> <div className="row">
<div className={classNames(styles.chart, 'col')}> <div className={classNames(styles.chart, 'col')}>
<h1>{data.label}</h1> <h1>{data.label}</h1>
<WebsiteChart websiteId={data.website_id} onDateChange={handleDateChange} stickHeader /> <CheckVisible>
{visible => (
<WebsiteChart
websiteId={data.website_id}
onDateChange={handleDateChange}
animate={visible}
stickHeader
/>
)}
</CheckVisible>
</div> </div>
</div> </div>
<div className={classNames(styles.row, 'row justify-content-between')}> <div className={classNames(styles.row, 'row')}>
<div className={pageviewClasses}> <div className={pageviewClasses}>
<RankingsChart <CheckVisible>
title="Top URLs" {visible => (
type="url" <RankingsChart
heading="Views" title="Top URLs"
websiteId={data.website_id} type="url"
startDate={startDate} heading="Views"
endDate={endDate} websiteId={data.website_id}
dataFilter={urlFilter} startDate={startDate}
/> endDate={endDate}
dataFilter={urlFilter}
animate={visible}
/>
)}
</CheckVisible>
</div> </div>
<div className={pageviewClasses}> <div className={pageviewClasses}>
<RankingsChart <CheckVisible>
title="Top referrers" {visible => (
type="referrer" <RankingsChart
heading="Views" title="Top referrers"
websiteId={data.website_id} type="referrer"
startDate={startDate} heading="Views"
endDate={endDate} websiteId={data.website_id}
dataFilter={refFilter} startDate={startDate}
/> endDate={endDate}
dataFilter={refFilter}
animate={visible}
/>
)}
</CheckVisible>
</div> </div>
</div> </div>
<div className={classNames(styles.row, 'row justify-content-between')}> <div className={classNames(styles.row, 'row')}>
<div className={sessionClasses}> <div className={sessionClasses}>
<RankingsChart <CheckVisible>
title="Browsers" {visible => (
type="browser" <RankingsChart
heading="Visitors" title="Browsers"
websiteId={data.website_id} type="browser"
startDate={startDate} heading="Visitors"
endDate={endDate} websiteId={data.website_id}
dataFilter={browserFilter} startDate={startDate}
/> endDate={endDate}
dataFilter={browserFilter}
animate={visible}
/>
)}
</CheckVisible>
</div> </div>
<div className={sessionClasses}> <div className={sessionClasses}>
<RankingsChart <CheckVisible>
title="Operating system" {visible => (
type="os" <RankingsChart
heading="Visitors" title="Operating system"
websiteId={data.website_id} type="os"
startDate={startDate} heading="Visitors"
endDate={endDate} websiteId={data.website_id}
/> startDate={startDate}
endDate={endDate}
animate={visible}
/>
)}
</CheckVisible>
</div> </div>
<div className={sessionClasses}> <div className={sessionClasses}>
<RankingsChart <CheckVisible>
title="Devices" {visible => (
type="screen" <RankingsChart
heading="Visitors" title="Devices"
websiteId={data.website_id} type="screen"
startDate={startDate} heading="Visitors"
endDate={endDate} websiteId={data.website_id}
dataFilter={deviceFilter} startDate={startDate}
/> endDate={endDate}
dataFilter={deviceFilter}
animate={visible}
/>
)}
</CheckVisible>
</div> </div>
</div> </div>
<div className={classNames(styles.row, 'row justify-content-between')}> <div className={classNames(styles.row, 'row')}>
<div className="col-12 col-md-12 col-lg-8"> <div className="col-12 col-md-12 col-lg-8">
<WorldMap data={countryData} /> <WorldMap data={countryData} />
</div> </div>
<div className="col-12 col-md-12 col-lg-4"> <div className="col-12 col-md-12 col-lg-4">
<RankingsChart <CheckVisible>
title="Countries" {visible => (
type="country" <RankingsChart
heading="Visitors" title="Countries"
websiteId={data.website_id} type="country"
startDate={startDate} heading="Visitors"
endDate={endDate} websiteId={data.website_id}
dataFilter={countryFilter} startDate={startDate}
onDataLoad={data => setCountryData(data)} endDate={endDate}
/> dataFilter={countryFilter}
onDataLoad={data => setCountryData(data)}
animate={visible}
/>
)}
</CheckVisible>
</div> </div>
</div> </div>
</> </>

View File

@ -15,3 +15,7 @@
border-left: 0; border-left: 0;
padding-left: 0; padding-left: 0;
} }
.row > [class*='col-']:last-child {
padding-right: 0;
}

View File

@ -209,7 +209,7 @@ const isoCountries = {
QA: 'Qatar', QA: 'Qatar',
RE: 'Reunion', RE: 'Reunion',
RO: 'Romania', RO: 'Romania',
RU: 'Russian Federation', RU: 'Russia',
RW: 'Rwanda', RW: 'Rwanda',
BL: 'Saint Barthelemy', BL: 'Saint Barthelemy',
SH: 'Saint Helena', SH: 'Saint Helena',