Rework marquee

pull/965/head
ta264 4 years ago
parent 5714f1c913
commit f884a2689a

@ -78,7 +78,6 @@
.toggleMonitoredContainer { .toggleMonitoredContainer {
align-self: center; align-self: center;
margin-right: 10px;
} }
.monitorToggleButton { .monitorToggleButton {

@ -223,7 +223,7 @@ class AuthorDetails extends Component {
overviewHeight overviewHeight
} = this.state; } = this.state;
const marqueeWidth = (titleWidth - 170); const marqueeWidth = (titleWidth - 165);
const continuing = status === 'continuing'; const continuing = status === 'continuing';

@ -65,7 +65,6 @@
.toggleMonitoredContainer { .toggleMonitoredContainer {
align-self: center; align-self: center;
margin-right: 10px;
} }
.monitorToggleButton { .monitorToggleButton {

@ -158,7 +158,7 @@ class BookDetails extends Component {
overviewHeight overviewHeight
} = this.state; } = this.state;
const marqueeWidth = (titleWidth - 170); const marqueeWidth = (titleWidth - 165);
return ( return (
<PageContent title={title}> <PageContent title={title}>

@ -0,0 +1,17 @@
.container {
position: relative;
overflow: hidden;
padding-left: 10px;
white-space: nowrap;
/* stylelint-disable-next-line property-no-vendor-prefix */
-webkit-mask-image: linear-gradient(to right, transparent, $white 10px, $white 90%, transparent), linear-gradient(to left, transparent, $white 10px, $white 90%, transparent);
mask-image: linear-gradient(to right, transparent, $white 10px, $white 90%, transparent), linear-gradient(to left, transparent, $white 10px, $white 90%, transparent);
}
.inner {
transition: transform var(--duration) ease-in-out;
}
.toLeft {
transform: translateX(var(--distance));
}

@ -1,181 +1,144 @@
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Measure from './Measure';
import styles from './Marquee.css';
const FPS = 20; const SPEED = 50; // pixels per second
const STEP = 1;
const TIMEOUT = 1 / FPS * 1000;
class Marquee extends Component { class Marquee extends Component {
static propTypes = { //
text: PropTypes.string, // Lifecycle
title: PropTypes.string,
hoverToStop: PropTypes.bool,
loop: PropTypes.bool,
className: PropTypes.string
};
static defaultProps = { constructor(props) {
text: '', super(props);
title: '',
hoverToStop: true,
loop: false
};
state = { this.state = {
animatedWidth: 0, containerWidth: 0,
overflowWidth: 0, overflowWidth: 0,
direction: 0 animationState: null,
key: 0
}; };
componentDidMount() {
this.measureText();
if (this.props.hoverToStop) {
this.startAnimation();
}
} }
componentWillReceiveProps(nextProps) { componentDidUpdate(prevProps) {
if (this.props.text.length !== nextProps.text.length) { if (this.props.text !== prevProps.text) {
clearTimeout(this.marqueeTimer); // reset the component, set a new key to force re-render so new text isn't in old position
this.setState({ animatedWidth: 0, direction: 0 }); this.setState({
} overflowWidth: 0,
animationState: null,
key: this.state.key + 1
});
return;
} }
componentDidUpdate() { const containerWidth = this.state.containerWidth;
this.measureText(); const node = this.text;
if (this.props.hoverToStop) { if (containerWidth && node) {
this.startAnimation(); const textWidth = node.offsetWidth;
} // eslint-disable-next-line no-bitwise
} const overflowWidth = (textWidth - containerWidth + 10) | 0; // 10 margin, round towards 0
componentWillUnmount() { if (overflowWidth !== this.state.overflowWidth) {
clearTimeout(this.marqueeTimer); const triggerUpdate = overflowWidth > 0 && this.state.overflowWidth === 0;
}
onHandleMouseEnter = () => { this.setState({ overflowWidth }, () => {
if (this.props.hoverToStop) { if (triggerUpdate) {
clearTimeout(this.marqueeTimer); this.onHandleMouseEnter();
} else if (this.state.overflowWidth > 0) {
this.startAnimation();
} }
});
} }
onHandleMouseLeave = () => {
if (this.props.hoverToStop && this.state.overflowWidth > 0) {
this.startAnimation();
} else {
clearTimeout(this.marqueeTimer);
this.setState({ animatedWidth: 0 });
} }
} }
startAnimation = () => { //
clearTimeout(this.marqueeTimer); // Listeners
const isLeading = this.state.animatedWidth === 0;
const timeout = isLeading ? 0 : TIMEOUT;
const animate = () => { onHandleMouseEnter = () => {
const { overflowWidth } = this.state; const {
let animatedWidth = this.state.animatedWidth; animationState,
let direction = this.state.direction; overflowWidth
} = this.state;
if (direction === 0) { if (animationState === null && overflowWidth > 0) {
animatedWidth = this.state.animatedWidth + STEP; this.setState({ animationState: 'toLeft' });
} else { }
animatedWidth = this.state.animatedWidth - STEP;
} }
const isRoundOver = animatedWidth < 0; onTransitionEnd = (payload) => {
const endOfText = animatedWidth > overflowWidth; const {
animationState
} = this.state;
if (endOfText) { if (animationState === 'toLeft') {
direction = direction === 1; this.setState({ animationState: 'toRight' });
} }
if (isRoundOver) { if (animationState === 'toRight') {
if (this.props.loop) { this.setState({ animationState: null });
direction = direction === 0;
} else {
return;
} }
} }
this.setState({ animatedWidth, direction }); onContainerMeasure = ({ width }) => {
this.marqueeTimer = setTimeout(animate, TIMEOUT); this.setState({ containerWidth: width });
};
this.marqueeTimer = setTimeout(animate, timeout);
} }
measureText = () => { //
const container = this.container; // Render
const node = this.text;
if (container && node) { render() {
const containerWidth = container.offsetWidth; const {
const textWidth = node.offsetWidth; text
const overflowWidth = textWidth - containerWidth; } = this.props;
if (overflowWidth !== this.state.overflowWidth) { const {
this.setState({ overflowWidth }); key,
} overflowWidth,
} animationState
} } = this.state;
const moveDist = -overflowWidth - 10;
const duration = -moveDist / SPEED;
render() {
const style = { const style = {
position: 'relative', '--duration': `${duration}s`,
right: this.state.animatedWidth, '--distance': `${moveDist}px`
whiteSpace: 'nowrap'
}; };
if (this.state.overflowWidth < 0) {
return ( return (
<div <Measure
ref={(el) => { key={key}
this.container = el; className={styles.container}
}} onMeasure={this.onContainerMeasure}
className={`ui-marquee ${this.props.className}`} onMouseEnter={this.onHandleMouseEnter}
style={{ overflow: 'hidden' }} onTouchStart={this.onHandleMouseEnter}
>
<span
ref={(el) => {
this.text = el;
}}
style={style}
title={(this.props.title && (this.props.text !== this.props.title)) ? `Original Title: ${this.props.title}` : this.props.text}
> >
{this.props.text}
</span>
</div>
);
}
return (
<div <div
ref={(el) => { className={classNames(
this.container = el; styles.inner,
}} animationState === 'toLeft' && styles.toLeft
className={`ui-marquee ${this.props.className}`.trim()} )}
style={{ overflow: 'hidden' }} style={style}
onMouseEnter={this.onHandleMouseEnter} onTransitionEnd={this.onTransitionEnd}
onMouseLeave={this.onHandleMouseLeave}
> >
<span <span
ref={(el) => { ref={(el) => {
this.text = el; this.text = el;
}} }}
style={style} title={text}
title={(this.props.title && (this.props.text !== this.props.title)) ? `Original Title: ${this.props.title}` : this.props.text}
> >
{this.props.text} {text}
</span> </span>
</div> </div>
</Measure>
); );
} }
} }
Marquee.propTypes = {
text: PropTypes.string.isRequired
};
export default Marquee; export default Marquee;

Loading…
Cancel
Save