My latest "flute" music video, "Sunset Over a Distant Horizon"

Jon, I’m on the case. The first thing I’ve done is actually to simplify the HTML:

  • You don’t need the first (outer) <div> as the <body> element can do that job.
  • I’ve got rid of all the IDs and CLASSes by changing the “text_container” <div> to a <main> element.
  • I’ve created a new CSS variable “–N” that has as its value the ordinal (1, 2, …,11) of the <div>. I do this by using the “div:nth-child(N)” css construct.
  • I now have only 1 line to specify all the “animation” properties (using var(–N)).

The new HTML file is here:

<!DOCTYPE html>
	<title>Subtitles Fade in and out</title>

html { width: 100%; height: 100%; }
body {
height          : 100%; 
width           : 100%;
margin          : 0; 
padding         : 0;
display         : flex;
align-items     : flex-end;
justify-content : center;
background-color: #808080;
overflow        : hidden;
opacity         : 1;
main {
	display         : flex;
	align-items     : center;
	justify-content : center;
	height          : 200px;
	width           : 100%;
	overflow        : hidden;

div {
	position        : absolute;
	height          : 90px;
	width           : 100%;
	margin-top      : 0;
	text-align      : center;
	font-family     : arial;
	color           : white;
	font-size       : 60px;
	background-color: transparent;
	text-shadow     : 4px 4px black;
	opacity         :0;

	--length:  4s;
	--L     : -0.5s;	

div:nth-child( 1) {--N:  1;}
div:nth-child( 2) {--N:  2;}
div:nth-child( 3) {--N:  3;}
div:nth-child( 4) {--N:  4;}
div:nth-child( 5) {--N:  5;}
div:nth-child( 6) {--N:  6;}
div:nth-child( 7) {--N:  7;}
div:nth-child( 8) {--N:  8;}
div:nth-child( 9) {--N:  9;}
div:nth-child(10) {--N: 10;}
div:nth-child(11) {--N: 11;}

div {animation: down-in 1.5s ease forwards calc((var(--N) - 1) * var(--length)), fade-out 1s  ease forwards calc(var(--L) + (var(--N) * var(--length)));		}

@keyframes fade-in {
	0% 		{  opacity:0	}
	100% 	{  opacity:1    }

@keyframes fade-out {
	0% 		{  opacity:1	}
	100% 	{  opacity:0    }

@keyframes down-in {
	0% 		{ margin-top: -90px; opacity:0;	}
	100% 	{ margin-top:   0px; opacity:1;}

@keyframes down-out {
	0% 		{ margin-top: 0px; opacity:1;	}
	100% 	{ margin-top: 90px; opacity:0;}

@keyframes up {
	0% 		{ margin-top: 0px; opacity:1;	}
	100% 	{ margin-top: -90px; opacity:0;}

@keyframes down_then_down {
	0% { margin-top: -90px; opacity:0;	}
	10% { margin-top: 0px;	opacity:0.8;}
	20% { margin-top: 0px;	opacity:1;}
	90% { margin-top: 0px;	opacity:1;}
		100% {margin-top:90px; opacity:0;}

@keyframes up_then_up {
	0% { margin-top: 90px; opacity:0;	}
	10% { margin-top: 0px;	opacity:0.8;}
	20% { margin-top: 0px;	opacity:1;}
	90% { margin-top: 0px;	opacity:1;}
		100% {margin-top:-90px; opacity:0;}

@keyframes down_then_up {
	0% { margin-top: -90px; opacity:0;	}
	10% { margin-top: 0px;	opacity:0.8;}
	20% { margin-top: 0px;	opacity:1;}
	90% { margin-top: 0px;	opacity:1;}
		100% {margin-top:-90px; opacity:0;}

@keyframes up_then_down {
	0% { margin-top: 90px; opacity:0;	}
	10% { margin-top: 0px;	opacity:0.8;}
	20% { margin-top: 0px;	opacity:1;}
	90% { margin-top: 0px;	opacity:1;}
		100% {margin-top:90px; opacity:0;}

@keyframes fadein_then_fadeout {
	0% { margin-top: 0px; opacity:0;	}
	10% { margin-top: 0px;	opacity:0.8;}
	20% { margin-top: 0px;	opacity:1;}
	90% { margin-top: 0px;	opacity:1;}
		100% {margin-top:0px; opacity:0;}
<body >
		<div>At first I was afraid, I was petrified,</div>
		<div>Kept thinking I could never live without you by my side,</div>
		<div>But then I spent so many nights thinking how you did me wrong -</div>
		<div>And I grew strong,</div>
		<div>And I learned how to get along ...</div>
		<div>And so you're back - </div>
		<div>- from outer space ...</div>
		<div>I just walked in to find you here with that sad look upon your face</div>
		<div>I should have changed that stupid lock,</div>
		<div>I should have made you leave your key,</div>
		<div>If I'd known for just one second you'd be back to bother me</div>

[I’ve had to include the text here, since, when I try to upload the file, it actually replaces it on the forum with the file you posted - weird.]

After dinner I’ll look into doing the animation of the characters and also move the creation of “–N” into the javascript, so it will cater for any number of lines, not just 11.

1 Like

The version with the individual characters animated.

I_will_survive.html.txt (4.9 KB)

I don’t think this works so well, mainly because lines of different lengths appear on screen for the same amount of time. I’ll give this some thought, but it will have to wait till tomorrow as I have a very early game of golf :golfing_man:in the morning.

1 Like

Brilliant, brilliant, @elusien. Not had time to try it yet but I will asap. Thank you so much!!!

Just clicked on it and it has great promise. I think though that for subtitles it would look better if the span animations ran from left-to-right, quickly, not appearing at random. I presume this would be easy to fix?

Hi @elusien, It would be great to have the option for random or left-to-right but the crucial thing is for them to appear faster than at present. I amended the span animation to

 span {animation:    fade-in-fast calc(0.7s * var(--i) / var(--m)) 
                     cubic-bezier(0.1, 0, 1.0, 1.0) 
                     forwards calc((var(--N) - 1) * var(--length));}

… and I think this is a better speed for the fade. I think it would look great left-to-right!

I also gave an option for no vertical movement if desired by simply adding this:

 @keyframes no-movement{
0% 		{ margin-top:   0px; opacity:1;	}
100% 	{ margin-top:   0px; opacity:1;}

Fantastic work, @elusien. I am so grateful.

I_will_survive random FASTER amended by jonray.html.txt (5.2 KB)

Hi @elusien, just wondering if you had a chance to tweak the JS code make the animation run left-to-right instead of randomly? I’m hoping it will be an easy job for you, rather like me playing a Grade 1 flute piece… :wink:
No worries if you are busy scoring holes-in-one … no rush! :smiley:

Oops, sorry @elusien, I just noticed you’re busy with your TTY code. No pressure from me - I don’t want to inundate you! Seriously, no rush at all.

Jon, don’t worry, I haven’t forgotten. Unfortunately we had a major electrical fault recently in our small close of villas (the neutral suddenly became live with 450 volts instead of zero) and it fried loads of equipment, including 2 aircon units of mine and the motor/controller for our sun awning. I spent quite a bit of time today trying to get the electricity authority to accept responsibility and pay for replacements. This and a couple of other issues took up most of my time today and tomorrow I have to go to Paphos for a Covid booster jab and an appointment with my financial advisor because of Brexit issues, so I doubt I’ll have much time to look into it tomorrow. But I’m free all of Wednesday.

However, as a stopgap you could do the following: replace the “shuffled” function that says:

shuffled = (Array.from({length: line_characters.length}, (_, index) => index + 1)).sort((a, b) => 0.5 - Math.random());


shuffled = (Array.from({length: line_characters.length}, (_, index) => index + 1));
1 Like

Great production! This video looks so beautiful!

1 Like

Thank you, @SallySunny !!

Hi @Elusien - it worked straight away! THANK YOU. Now I have to get my head around how to change the timings of the animation. I have to work out what all the variables --i, --m and --N mean here:

 fade-in-fast calc(2s * var(--i) / var(--m))   ease       forwards calc((var(--N) - 1) * var(--length));}

I made a quick demo with the above animation timings. Looking good! :grinning_face_with_smiling_eyes:

Oh my, you had a very bad day!! Sorry to hear that. I was going to say “shocking” but that would be terrible …! :wink: :grinning_face_with_smiling_eyes:

Bravo pour cette animation, effectivement ça fonctionne bien.

Bravo for this animation, indeed it works well.

A mon avis, mais je peux me tromper ce sont des variables que l’on ne peut pas modifier pour ajuster les durées des animations. le --i semble être l’ordre d’apparition des lettres suite à la fonction shuffled, le --m doit-être le nombre de caractère de la ligne, et le --N le numéro de la ligne
D’ailleurs à ce propos, pour cette utilisation particulière en tant que sous-titrage de paroles de chansons, le regroupement de tout le texte dans une textarea n’est peut-être pas la meilleure solution, cela ne permet pas de fixer la durée de chaque ligne pour coller parfaitement à la vidéo (ou l’audio).
La version précédente ou chaque ligne était indépendante me parait préférable pour cette utilisation.
A moins bien sûr que notre ami @Elusien nous ponde une solution.

In my opinion, but I could be wrong, these are variables that cannot be modified to adjust the duration of the animations. The --i seems to be the order of appearance of the letters following the shuffled function, the --m must be the number of characters of the line, and the --N the number of the line
By the way, for this particular use as song lyrics subtitle, the grouping of all the text in a textarea is maybe not the best solution, it doesn’t allow to fix the length of each line to fit perfectly with the video (or audio).
The previous version where each line was independent seems to me preferable for this use.
Unless of course our friend @Elusien comes up with a solution.

1 Like

This is exactly what I was going to look into. The best place to do this would be in the Javascript. It could create a --D (delay) parameter to specify the delay for each specific animation and a --L (length) parameter to specify how long the specific animation runs for. The calculation for this in the Javascript can be as simple or complex as you like.

Or you could do away with the CSS and do it all in Javascript using the GSAP library.

1 Like

You could be right!

I tried some calculations but I THINK I’M WRONG. Am I on the right lines though? I’m an amateur…
–i = order of appearance of the letters
–m = number of characters of the line
–N = number of the line
–length = duration of the animation (specified by the user)
fade-in-fast :
DURATION = calc(2s * var(–i) / var(–m)) (Calculation A)
EASING = ease
DIRECTION = forwards
DELAY = calc((var(–N) - 1) * var(–length));} (Calculation B)

So, taking the FIRST letter in a string of 10 letters (LINE no. 1):
–i = 1
–m = 10
–N = 1
–length = 4 seconds (specified by the user) …

Calculation A (DURATION) would be 2s * 1/10 = 2/10 or 0.5 seconds

Calculation A (DELAY) would be 1 - 1 (0) * 4s = 0 seconds

… and the SECOND letter in a string of 10 letters (LINE no. 1):
–i = 2
–m = 10
–N = 1
–length = 4 seconds (specified by the user) …

Calculation A (DURATION) would be 2s * 2/10 = (2*0.2) =0.4 seconds

Calculation A (DELAY) would be 2 - 1 (1) * 4s = 4 seconds - ??? This doesn’t seem right!! Help!

Yes, it would be great to have control on the duration and delay of the animation of each letter, and also on the duration of each line! Personally I’d prefer to be able to do this in CSS rather than JS alone, purely because I understand a little CSS but JS is still mostly gobbeldy-gook to me! :wink:

1 Like

If DELAY is Calculation B [ calc((var(–N) - 1) * var(–length)) ]
For the 2° letter of line 1 => (1 - 1) / 4s = 0

You have taken --i (2) instead of --N (1)

1 Like

Pour ma part je pense que le plus simple serait d’affecter un coefficient à la durée totale de la ligne et un autre à la durée de chaque lettre.
Chacun des coefficient est modifiable par l’utilisateur, mais au départ il a une valeur de 1 qui correspond:
C1 (total) = 1 => Durée totale ligne = 2 secondes pour 50 caractères
C2 (Unitaire) = 1 => Durée d’une lettre = 10 images
Ainsi, si la ligne fait 25 caractères elle s’affiche 2 fois plus vite qu’une ligne de 50
Si l’utilisateur entre C1 = 2, la ligne de 50 lettres mettra 4 secondes.

For my part I think the simplest way would be to assign a coefficient to the total duration of the line and another to the duration of each letter.
Each of the coefficients can be modified by the user, but at the beginning it has a value of 1 which corresponds:
C1 (Total) = 1 => Total line duration = 2 seconds for 50 characters
C2 (Unit) = 1 => Duration of a letter = 10 frames
So, if the line is 25 characters long it is displayed 2 times faster than a line of 50
If the user enters C1 = 2, the line of 50 letters will take 4 seconds.


This is what I was thinking. Though it is often better to use the metric “words per minute” rather than characters per minute. Many people can read the word “house” as quickly as they can the word “am”, because they do not read the individual letters comprising the words, but instead “recognise” the word as a whole. Adults in general can read beween 220 to 350 words per minute.

[C’est ce que je pensais. Bien qu’il soit souvent préférable d’utiliser la métrique “mots par minute” plutôt que caractères par minute. Beaucoup de gens peuvent lire le mot « maison » aussi rapidement que le mot « suis », car ils ne lisent pas les lettres individuelles qui composent les mots, mais « reconnaissent » le mot dans son ensemble. Les adultes en général peuvent lire entre 220 et 350 mots par minute.]


Oh yes! Must be age!!

@Elusien , @Namna - thank you for the observations. Clever stuff!

Comment puis-je modifier l’animation du “div” ou du “span” du fichier html de @jonray pour ajouter une petite translation horizontale des lettres de la ligne, comme dans cet exemple:

How can I modify the animation of the “div” or “span” in @jonray’s html file to add a small horizontal translation of the letters in the line, as in this example:

Hi @Namna ,
Quick answer: try this:

 @keyframes MOVE_RIGHT {
   0%    {    transform:translate(-10px, 0px)  }
   100%  {    transform:translate(0px, 0px)  }

Change the -10px value for less or more movement.

1 Like