shshaw / Splitting

JavaScript microlibrary to split an element by words, characters, children and more, populated with CSS variables!
https://splitting.js.org
MIT License
1.68k stars 67 forks source link

Next.js runtime error #84

Open Chandler-Zhu opened 2 years ago

Chandler-Zhu commented 2 years ago

I tried to use splitting in my next.js app, but it gives me an error

import React, { useState, useRef, useEffect } from 'react'

import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'
import Splitting from 'splitting'

export const SomePage = () => {

  const slideRef = useRef(null)

  useEffect(() => {
    // double checking we actually have a reference (and the value is not null)
    if (slideRef) {
     slideRef.current.Splitting();
    }
  }, [slideRef])

  return (
    <p
      ref={slideRef}
      data-splitting='true'
    >
      split some text here
    </p>
  )
}

image

draZer0 commented 2 years ago

I combined all the answers from previous issues and I managed to get it working in Next.js with the following code:

import "splitting/dist/splitting.css";
import "splitting/dist/splitting-cells.css";

const Component = () => {

    let target;

    setTimeout(() => {
        if ( window && document && target ) {
            const Splitting = require('Splitting');
            Splitting({ by: "chars", target: target,  });
        }
    });

    return (
        <>
            <span ref={(el) => { target = el; }}>Lorem ipsum dolor sit amet</span>
        </>
    )
}

export default Component;
Chandler-Zhu commented 2 years ago

if the ref is an arrow function, when exactly does it run? After component mounted ?

outdatedx commented 1 year ago

Im having a similar issue cause I'm using it in a hook

'use client'

import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'

import { useEffect, useRef } from 'react'
import Splitting from 'splitting'

import { randomNumber } from '~/lib/utils'

interface CellOptions {
  position: number
  previousCellPosition: number
}

class Line {
  position = -1
  cells: Cell[] = []

  constructor(linePosition: number) {
    this.position = linePosition
  }
}

class Cell {
  DOM: {
    el: HTMLElement | null
  } = {
    el: null
  }
  position = -1
  previousCellPosition = -1
  original: string
  state: string
  color: string
  originalColor: string
  cache: any

  constructor(DOM_el: HTMLElement, options: CellOptions) {
    this.DOM.el = DOM_el
    this.original = this.DOM.el.innerHTML
    this.state = this.original
    this.color = this.originalColor = getComputedStyle(
      document.documentElement
    ).getPropertyValue('--color-text')
    this.position = options.position
    this.previousCellPosition = options.previousCellPosition
  }

  set(value: string) {
    this.state = value
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    this.DOM.el!.innerHTML = this.state
  }
}

class TypeShuffle {
  DOM: {
    el: HTMLElement | null
  } = {
    el: null
  }
  lines: Line[] = []
  lettersAndSymbols: string[] = [
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
    'G',
    'H',
    'I',
    'J',
    'K',
    'L',
    'M',
    'N',
    'O',
    'P',
    'Q',
    'R',
    'S',
    'T',
    'U',
    'V',
    'W',
    'X',
    'Y',
    'Z',
    '!',
    '@',
    '#',
    '$',
    '&',
    '*',
    '(',
    ')',
    '-',
    '_',
    '+',
    '=',
    '/',
    '[',
    ']',
    '{',
    '}',
    ';',
    ':',
    '<',
    '>',
    ',',
    '0',
    '1',
    '2',
    '3',
    '4',
    '5',
    '6',
    '7',
    '8',
    '9'
  ]
  effects: Record<string, () => void> = {
    fx: () => this.fx()
  }
  totalChars = 0
  isAnimating = false

  constructor(DOM_el: HTMLElement) {
    this.DOM.el = DOM_el

    const results = Splitting({ target: this.DOM.el, by: 'lines' })
    results.forEach((s: any) => Splitting({ target: s.words }))

    for (const [linePosition, lineArr] of results[0].lines.entries()) {
      const line = new Line(linePosition)
      const cells: Cell[] = []
      let charCount = 0

      for (const word of lineArr) {
        for (const char of Array.from(
          word.querySelectorAll('.char')
        ) as HTMLElement[]) {
          cells.push(
            new Cell(char, {
              position: charCount,
              previousCellPosition: charCount === 0 ? -1 : charCount - 1
            })
          )
          ++charCount
        }
      }

      line.cells = cells
      this.lines.push(line)
      this.totalChars += charCount
    }
  }

  clearCells() {
    for (const line of this.lines) {
      for (const cell of line.cells) {
        cell.set('&nbsp;')
      }
    }
  }

  getRandomChar() {
    return this.lettersAndSymbols[
      Math.floor(Math.random() * this.lettersAndSymbols.length)
    ]
  }

  fx() {
    const MAX_CELL_ITERATIONS = 10
    let finished = 0
    this.clearCells()

    const loop = (line: Line, cell: Cell, iteration = 0) => {
      if (iteration === MAX_CELL_ITERATIONS - 1) {
        cell.set(cell.original)
        ++finished
        if (finished === this.totalChars) {
          this.isAnimating = false
        }
      } else {
        const randomChar = this.getRandomChar()
        if (randomChar) {
          cell.set(randomChar)
        }
      }

      ++iteration
      if (iteration < MAX_CELL_ITERATIONS) {
        setTimeout(() => loop(line, cell, iteration), 50)
      }
    }

    for (const line of this.lines) {
      for (const cell of line.cells) {
        setTimeout(() => loop(line, cell), randomNumber(0, 100))
      }
    }
  }

  trigger(effect = 'fx') {
    if (this.effects && effect in this.effects && !this.isAnimating) {
      this.isAnimating = true
      const selectedEffect = this.effects[effect]
      selectedEffect && selectedEffect()
    }
  }
}

export const useTypeShuffle = (selector: string) => {
  const typeShuffleRef = useRef<TypeShuffle | null>(null)

  useEffect(() => {
    if (window && document) {
      const mainTextElement = document.querySelector(selector) as HTMLElement

      if (!mainTextElement) {
        return
      }

      const typeShuffleInstance = new TypeShuffle(mainTextElement)
      typeShuffleRef.current = typeShuffleInstance

      return () => {
        typeShuffleRef.current = null
      }
    }
  }, [selector])

  return typeShuffleRef
}

Its basically a typeShuffle Effect that uses splitting and spits out the error

- error node_modules/splitting/dist/splitting.js (7:0) @ eval
- error ReferenceError: document is not defined
brett-sprad commented 1 year ago

This worked for me:

'use client'

import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'

import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { ReactNode, useEffect } from 'react'

gsap.registerPlugin(ScrollTrigger)

interface Props {
  children: ReactNode
}

const AnimateText = ({ children }: Props) => {
  let target: any = null

  const scroll = (fx17Titles: any) => {
    fx17Titles.forEach((title: HTMLElement) => {
      const chars = title.querySelectorAll('.char')
      chars.forEach((char) =>
        gsap.set(char.parentNode, {
          perspective: 1000,
        })
      )
      gsap.fromTo(
        // targets:
        chars,
        // from:
        {
          y: 10,
          'will-change': 'opacity, transform',
          opacity: 0.7,
          rotateX: () => gsap.utils.random(-120, 120),
          z: () => gsap.utils.random(-100, 200),
        },
        // to:
        {
          y: 0,
          z: -12.2,
          repeat: 0,
          ease: 'none',
          opacity: 1,
          rotateX: 10,
          stagger: 0.1,
          scrollTrigger: {
            trigger: title,
            start: 'top bottom',
            end: 'bottom top',
            scrub: true,
          },
        }
      )
    })
  }

  useEffect(() => {
    const fx17Titles = document.querySelectorAll('.animate-text[data-splitting][data-effect17]')
    const splitting = require('Splitting')
    splitting({ by: 'chars', target: target })
    scroll(fx17Titles)
  })

  return (
    <>
      <div
        ref={(el) => {
          target = el
        }}
        className={'animate-text'}
        data-splitting
        data-effect17
      >
        {children}
      </div>
    </>
  )
}

export default AnimateText

...and then you use it like so:

<AnimateText>Some text here</AnimateText>
AlexandraKlein commented 9 months ago

I am using this:

  React.useEffect(() => {
        const splitText = async () => {
            const { default: Splitting } = await import('splitting');

            if (ref.current) {
                Splitting({ target: ref.current });
            }
        };

        splitText();
    }, []);
Baraff24 commented 7 months ago

This worked for me:

'use client'

import 'splitting/dist/splitting.css'
import 'splitting/dist/splitting-cells.css'

import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { ReactNode, useEffect } from 'react'

gsap.registerPlugin(ScrollTrigger)

interface Props {
  children: ReactNode
}

const AnimateText = ({ children }: Props) => {
  let target: any = null

  const scroll = (fx17Titles: any) => {
    fx17Titles.forEach((title: HTMLElement) => {
      const chars = title.querySelectorAll('.char')
      chars.forEach((char) =>
        gsap.set(char.parentNode, {
          perspective: 1000,
        })
      )
      gsap.fromTo(
        // targets:
        chars,
        // from:
        {
          y: 10,
          'will-change': 'opacity, transform',
          opacity: 0.7,
          rotateX: () => gsap.utils.random(-120, 120),
          z: () => gsap.utils.random(-100, 200),
        },
        // to:
        {
          y: 0,
          z: -12.2,
          repeat: 0,
          ease: 'none',
          opacity: 1,
          rotateX: 10,
          stagger: 0.1,
          scrollTrigger: {
            trigger: title,
            start: 'top bottom',
            end: 'bottom top',
            scrub: true,
          },
        }
      )
    })
  }

  useEffect(() => {
    const fx17Titles = document.querySelectorAll('.animate-text[data-splitting][data-effect17]')
    const splitting = require('Splitting')
    splitting({ by: 'chars', target: target })
    scroll(fx17Titles)
  })

  return (
    <>
      <div
        ref={(el) => {
          target = el
        }}
        className={'animate-text'}
        data-splitting
        data-effect17
      >
        {children}
      </div>
    </>
  )
}

export default AnimateText

...and then you use it like so:

<AnimateText>Some text here</AnimateText>

Error: Prevent writing to file that only differs in casing or query string from already written file. This will lead to a race-condition and corrupted files on case-insensitive file systems. /.../.next/server/vendor-chunks/Splitting.js /.../.next/server/vendor-chunks/splitting.js at checkSimilarFile (/.../node_modules/next/dist/compiled/webpack/bundle5.js:28:142411) at writeOut (/.../node_modules/next/dist/compiled/webpack/bundle5.js:28:144419) at /.../node_modules/next/dist/compiled/webpack/bundle5.js:28:1369729 at FSReqCallback.oncomplete (node:fs:191:23)

I have this error with the same code as you, this is my package.json: { "name": "x-website", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "gsap": "^3.12.5", "next": "14.1.0", "react": "^18", "react-dom": "^18", "sass": "^1.71.0", "splitting": "^1.0.6" }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "14.1.0", "jarallax": "^2.2.0", "paroller.js": "^1.4.7", "swiper": "^9.4.1", "typescript": "^5" } }