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 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 chart = useRef();
const [tooltip, setTooltip] = useState({});
@ -75,7 +82,7 @@ export default function PageviewsChart({ websiteId, data, unit, className, child
},
options: {
animation: {
duration: 300,
duration: animationDuration,
},
tooltips: {
enabled: false,
@ -125,6 +132,7 @@ export default function PageviewsChart({ websiteId, data, unit, className, child
datasets[1].data = data.pageviews;
options.scales.xAxes[0].time.unit = unit;
options.scales.xAxes[0].ticks.callback = renderLabel;
options.animation.duration = animationDuration;
chart.current.update();
}
@ -133,6 +141,7 @@ export default function PageviewsChart({ websiteId, data, unit, className, child
useEffect(() => {
if (data) {
draw();
setTooltip(null);
}
}, [data]);

View File

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

View File

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

View File

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

View File

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

View File

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