UNPKG

react-native-ghostwriter

Version:

A React Native module that types strings on demand. Set up your Ghostwriter with custom options, and watch it do its magic. Inspired by Typed.js.

273 lines (234 loc) 6.97 kB
import React, { Component } from 'react'; import { StyleSheet, ScrollView, View, Text } from 'react-native'; import util from './util'; let typeTimeout = null; let cursorInterval = null; class Ghostwriter extends Component { /** * Class constructor * * @param props */ constructor(props) { super(props); this.state = { startDelay: 0, string: '', stringStyles: null, containerStyles: null, clearEverySequence: false, sequences: [], sequenceDuration: 1750, writeSpeed: 0, showCursor: true, cursorChar: '|', cursorSpeed: 400, cursorIndex: 0, writing: false, onComplete: () => {} }; } /** * Component mounted. */ componentDidMount() { this.setState(util.extend(this.state, this.props.options)); this.ghostwriterTimeout = setTimeout(() => { this.initGhostwriter(); if (this.state.showCursor) { cursorInterval = setInterval(() => { this.flashCursor(); }, this.state.cursorSpeed); } }, this.state.startDelay); } /** * Component preparing to unmount. */ componentWillUnmount() { clearTimeout(this.ghostwriterTimeout) clearTimeout(this.beforeNextSequenceTimeout) clearTimeout(typeTimeout); clearInterval(cursorInterval); } /** * Render the component. * * @returns {XML} */ render() { return ( <View style={this.containerStyles()}> <Text style={this.stringStyles()}> {this.state.string} {this.cursorIsDiplayed() && <Text> {this.state.cursorChar} </Text>} </Text> </View> ); } /** * The styles for the scroll view. * * @returns {*} */ containerStyles() { return this.state.containerStyles ? this.state.containerStyles : styles.container; } /** * The styles for our string. * * @returns {*} */ stringStyles() { return this.state.stringStyles ? this.state.stringStyles : styles.string; } /** * The state of the cursor (displayed or not) * * @returns {boolean} */ cursorIsDiplayed() { return this.state.cursorIndex % 2 ? true : false; } /** * Flash cursor functionality (increase index). */ flashCursor() { this.setState({ cursorIndex: this.state.cursorIndex + 1 }); } /** * Start typing the sequences provided by user. */ initGhostwriter() { let sequences = this.getSequences(), seqId = 0, charId = 0; this.write(sequences, seqId, charId, util.humanSpeed(this.state.writeSpeed)); } /** * More forward in the sequence. Adds one the next letter, or moves to the next sentence. * * @param sequences * @param seqId * @param charId * @param speed */ write(sequences, seqId, charId, speed) { // Clear typeTimeout at beginning of all ticks to clear any previous ticks. clearTimeout(typeTimeout); let finished = seqId === sequences.length; // All sequences complete if (finished) { return this.state.onComplete(); } typeTimeout = setTimeout(() => { let char = sequences[seqId].string[charId]; // There are still chars in this sequence if (!util.isUndefined(char)) { // Move to next char charId++; // Add new letter to string this.setState({ string: this.state.string + char, writing: true }); this.write(sequences, seqId, charId, util.humanSpeed(this.state.writeSpeed)); } else { // Call the callback function of the sequence this.callback(sequences, seqId); let duration; // Get the duration for the next sequence. Use custom duration // if provided by user, else use default. if (util.has(sequences[seqId], 'duration')) { duration = sequences[seqId].duration; } else { duration = this.state.sequenceDuration; } // If this is not the last sequence in the list of sequences... if (seqId !== sequences.length - 1) { this.beforeNextSequence(duration - 100); } this.nextSequence(sequences, seqId, charId, duration); } }, speed); } /** * Prepare the string for the next sequence (e.g., add a space, or clear the string) * * @param duration */ beforeNextSequence(duration) { this.setState({ writing: false }); this.beforeNextSequenceTimeout = setTimeout(() => { this.setState({ string: this.state.clearEverySequence ? '' : this.state.string + ' ' }); }, duration); } /** * Jump to the next sequence and start at the first char. * * @param sequences * @param seqId * @param charId * @param duration */ nextSequence(sequences, seqId, charId, duration) { // Move to next sequence seqId++; // Reset char index to start on first char of next sequence charId = 0; this.write(sequences, seqId, charId, duration); } /** * Get an array of sequences, each having their string split into an array of chars. * * @returns {Array} * @private */ getSequences() { let sequences = this.state.sequences; util.arrayEach(sequences, (sequence, i) => { if (util.has(sequence, 'string')) { sequences[i].string = sequence.string.split(''); } else { throw new Error("Your sequences must all contain a 'string' property."); } }); return sequences; } /** * Call the callback function of a specific sequence. * * @param sequences * @param seqId */ callback(sequences, seqId) { if (util.has(sequences[seqId], 'callback')) { if (util.isFunction(sequences[seqId].callback)) { sequences[seqId].callback(); } else { throw new Error(`The callback for sequence #${seqId} must be a function.`); } } } } /** * Styles */ const styles = StyleSheet.create({ container: {}, string: { fontSize: 18, fontWeight: "300" } }); export default Ghostwriter