Jump to content
Search Community

GSAP splitText wraps characters in divs instead of span?

nightlock82 test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

I'm doing a splitText specifically on separate characters on react. The issue is splitText uses divs to separate the characters instead of spans, this becomes a problem for screen-readers. I've tried applying the effect on the container and inline text but the output is still the same. Is there a way to make it into spans? 

 

image.png.92964d87d9a7de8e1b2a7938dbd85b68.png

Link to comment
Share on other sites

Just so you know, the reason SplitText wraps things in <div> instead of <span> by default is because most browsers will not render transforms on elements that have display: inline (which is what spans do). So just be careful about that - if you try animating x/y/rotation/scale, for example, and it’s not working with <span>, that’s why.  

  • Thanks 1
Link to comment
Share on other sites

  • 3 weeks later...

@GreenSock I noticed this happening. Are there any possible workarounds I saw some samples from websites on awwwards for example but I couldn't understand how they were doing this. My main concern with <div>'s is the accessibility, a screen reader would read it on a per-letter basis and that's a real head-scratcher.

Link to comment
Share on other sites

Hi,

 

I'm not an a11y expert at all, but you could check if the user is on a screen reader and revert the SplitText instance or perhaps never create it? I would try that. Maybe I'm wrong in my consideration, but a text being animated letter by letter is not something super useful on a screen reader, right?

 

Again I know virtually nothing about a11y so I'm just proposing an idea based on zero knowledge and just what I consider kind of useful on the particular scenario of a screen reader.

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

Quote

My main concern with <div>'s is the accessibility, a screen reader would read it on a per-letter basis and that's a real head-scratcher.

Nah there's tons of ways round this!

 

One route is doubling up content. You visually hide one  so sighted people don't see it, but screenreaders read it out still, then whack aria-hidden on the one that's being split.

 

https://www.a11yproject.com/posts/how-to-hide-content/#:~:text=visually-hidden class is applied,focus indicator had gone to.

<h2 class="visually-hidden">This is a sentence</h2>
<h2 class="split" aria-hidden="true">This is a sentence</h2>
.visually-hidden {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

 

Another option is using aria hidden on the header and an aria label and role on a containing div

<div role="heading" aria-level="2" aria-label="word">
  <h2 class="title" aria-hidden="true">
    <div>w</div>
    <div>o</div>
    <div>r</div>
    <div>d</div>
  </h2>
</div>


Another one would be to loop round the children that splittext creates and whack aria-hidden on them and an aria label on the text element.

 

I raised this casually with Jack the other day about seeing if we can bake an option in so it's a little easier for people.

 

Either way though, plenty of ways around this!

  • Like 3
Link to comment
Share on other sites

Here's a demo of the best (but more fiddly) way.

Ideally you'd have an aria label on the text element itself rather than assigning aria attributes to a non semantic element like a div.

So something like this.

I would go for this route with headings, but I'd likely duplicate text for long sections of text like paragraphs.

   

Another route I've gone in the past is adding aria-hidden on split text elements, then calling revert on them once they've animated in and removing the aria-hidden. Like so...

See the Pen poZaJQa?editors=1010 by GreenSock (@GreenSock) on CodePen



Obviously all these routes are worth testing with folks that actually use screenreaders if you have the capacity to do so.

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

  • 3 months later...
On 1/3/2023 at 12:23 PM, Rodrigo said:

Hi @nightlock82 and welcome to the GreenSock forums!

 

Your can specify the tag in the SplitText config object

 

tag

String - by default, SplitText wraps things in <div> elements, but you can define any tag like tag: "span"

 

Happy Tweening!

There is no option for "tag", Chrome is complaining that i cannot have Divs inside a <p> tag, so it need it to be a span

Link to comment
Share on other sites

1 hour ago, Rodrigo said:

Hi @Levin Riegner,

 

In the config object passed to the SplitText constuctor, from the SplitText docs:

tag

String - by default, SplitText wraps things in <div> elements, but you can define any tag like tag: "span"

 

const split = new SplitText(yourElement, {
  tag: "span",
});

Hopefully this helps.

Happy Tweening!

This doesnt work for me

Link to comment
Share on other sites

Heres my example code:
 

// Imports
// ------------
import React, { useRef, useLayoutEffect } from 'react';
import { gsap } from 'gsap';
import { SplitText } from 'gsap/all';

// Styles
// ------------
import { Jacket } from './styles';

// Component
// ------------
const ScrollAnimatedText = ({ children }) => {
	// NOTE • refs
	const comp = useRef();

	// NOTE • Animation
	useLayoutEffect(() => {
		let timer;
		let split;

		const ctx = gsap.context(() => {
			timer = setTimeout(() => {
				const childText = comp.current.firstChild;

				split = new SplitText(childText, {
					tag: 'span',
					type: 'lines,words,chars',
					linesClass: 'line',
				});

				const lines = comp.current.querySelectorAll('.line');

				for (const line of lines) {
					gsap.to(line, {
						ease: 'none',
						opacity: 1,
						y: `0%`,
						scrollTrigger: {
							trigger: line,
							start: 'top bottom',
							end: 'top top+=60%',
							scrub: true,
							markers: false,
						},
						stagger: {
							each: 0.08,
							from: 'start',
						},
					});
				}
			}, 150);
		}, comp);

		return () => {
			clearTimeout(timer);
			ctx.revert();
		};
	}, []);

	return <Jacket ref={comp}>{children}</Jacket>;
};

export default React.memo(ScrollAnimatedText);

 

Link to comment
Share on other sites

Hi,

 

Without a minimal demo there is not a lot we can do. This starter template has the GSAP Trial package so you can run the bonus plugins and tools:

https://stackblitz.com/edit/react-iqmjfx

 

Also I'm curious about why you're running that setTimeout inside the effect hook 🤷‍♂️

 

Also you can replace this:

const lines = comp.current.querySelectorAll('.line');

With this:

const lines = gsap.utils.toArray('.line');

Inside the GSAP Context instance you already defined a scope, to the toArray() method knows exactly where to look for that selector.

 

Happy Tweening!

Link to comment
Share on other sites

Hi,

 

If you check the template I linked before:

https://stackblitz.com/edit/react-iqmjfx

 

You'll see that the import comes from the GSAP Trial package:

import gsap from 'gsap-trial';
import { ScrollTrigger } from 'gsap-trial/ScrollTrigger';
import { SplitText } from 'gsap-trial/SplitText';

Just fork that example and a minimal representation of your setup. Please don't include all your code as it will take longer to find the issue, just the pertinent part of it that's causing the problem you're facing.

 

Happy Tweening!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...