block by EE2dev 55205b9934f43e1bef5680ce8dbffc55

Animated Intro - 7

Full Screen

Animating text based on this amazing codepen from Blake Bowen. (This bl.ock is just tested with Chrome)

Full screen version

See also:

index.html

<!DOCTYPE html>
<html>
    <meta charset="utf-8">
    <head>    
        <title>stars</title>
        <link href="https://fonts.googleapis.com/css?family=Indie+Flower" rel="stylesheet">   
        <link href="style.css" rel="stylesheet">
        <script src="https://d3js.org/d3.v5.min.js"></script>
        <script src="https://ee2dev.github.io/startext/lib/backgroundImage.js"></script> 
        <script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
    </head>

    <body>
        <div class="chart"></div>
        <aside style="display:none">
            <img id="star-texture" src="https://ee2dev.github.io/startext/lib/stars-02.png">
            <img id="star-texture-white" src="https://ee2dev.github.io/startext/lib/stars-02w.png">
        </aside> 

        <script src="stars.js"></script>
        <script src="animateStars.js"></script>
    </body>
</html>

animateStars.js

let pathDurations = [];
let pathEndpoints = [];
let app; // the main class in stars.js to create the particles

const containerDiv = "div.chart";
const explosionStrength = 0.002;
const transitionSpeed = 7;
const showPaths = false;
const starOptions = {
	mouseListener: false,
	texture: document.querySelector("#star-texture-white"),
	frames: createFrames(5, 80, 80),
	maxParticles: 2000,
	backgroundColor: "#111111",
	blendMode: "lighter",
	filterBlur: 50,
	filterContrast: 300,
	useBlurFilter: true,
	useContrastFilter: true
};

const myText = ["My favorite dish is", "pizza", "and", "ice cream"];
animate(myText);

function animate(myText){
	WebFont.load({
		google: { families: ['Indie Flower']},
		fontactive: function(familyName, fvd){ //This is called once font has been rendered in browser
			createTextpaths(myText);
			createPaths();
			calculatePathEndpoints();
			intializeStars();
			animateStars();
		},
	});
}

function createTextpaths(_textArray) {
	let sel = d3.select(containerDiv)
		.append("div")
		.attr("class", "header")
		.append("svg")
		.attr("width", document.body.clientWidth)
		.attr("height", document.body.clientHeight-8);

	createGradient(sel, "grad1");
  
	// add textpaths for all strings 
	for (let i = 0; i < _textArray.length; i++) {
		sel.append("text")
			.attr("class", "headline")
			.attr("class", () => (i < _textArray.length - 1) ? "headline no-effect h" + i : "headline effect h" + i)
			.attr("dy", "0.7em")
			.append("textPath")
			.attr("class", () => (i < _textArray.length - 1) ? "trans no-effect" : "trans effect")
			.attr("href", "#textpath" + i)
			.style("text-anchor","middle")
			.attr("startOffset","50%")
			.text(_textArray[i]); 
	}
}

function createGradient(selection, idValue) {
	let lg = selection.append("defs")
		.append("linearGradient")
		.attr("id", idValue);

	lg.append("stop")
		.attr("offset", "0%")
		.style("stop-color", "rgb(255, 0, 171)")
		.style("stop-opacity", 1);
  
	lg.append("stop")
		.attr("offset", "25%")
		.style("stop-color", "rgb(0, 168, 255)")
		.style("stop-opacity", 1);

	lg.append("stop")
		.attr("offset", "50%")
		.style("stop-color", "rgb(171, 0, 255)")
		.style("stop-opacity", 1);

	lg.append("stop")
		.attr("offset", "75%")
		.style("stop-color", "rgb(255, 171, 0)")
		.style("stop-opacity", 1);

	lg.append("stop")
		.attr("offset", "100%")
		.style("stop-color", "rgb(168, 255, 0)")
		.style("stop-opacity", 1);
}

function createPaths() {
	const container = d3.select(containerDiv).node().getBoundingClientRect();
	const selTP =  d3.selectAll(containerDiv + " text.headline");
	let selS =  d3.selectAll(containerDiv + " svg");

	selTP.each(function(d, i){
		let pos = {};
		let str;
		pos.width = container.width;
		pos.xStart = 0;
		pos.yStart = 100 + i * 50;

		selS.append("path")
			.attr("class", "headline trans" + " h" + i)
			.attr("id", "textpath" + i)
			.attr("d", () => {
				if (i < selTP.size() - 1) {
					str =  "M" + pos.xStart + ", " + pos.yStart + " L" + (pos.width + pos.xStart) + ", " + pos.yStart;
				} else {
					const controlPoint = 50;
					let curveHeight = 30;
					let iTimes = Math.floor(pos.width / controlPoint);
					if (iTimes % 2 === 0) { iTimes -= 1;}
					const r = (pos.width - iTimes * controlPoint) / 2;

					str =  "M" + pos.xStart + ", " + pos.yStart + " l " + r + ", 0";
					for (let i = 1; i <= iTimes; i++) {
						curveHeight = curveHeight * -1;
						str += " q " + (controlPoint / 2) + " " + curveHeight + " " + controlPoint + " 0 ";
					}
					str += " l " + r + ", 0";
				}
				return str;
			});
	});
}

function calculatePathEndpoints() {
	d3.selectAll("text.headline")
		.each(function(d,i) {
			let selPath = d3.select("path.headline.h" + i).node();
			let pathLength = selPath.getTotalLength();
			let textLength = d3.select(this).node().getComputedTextLength();
			let start = pathLength * .5 - textLength / 2;
			let length = textLength;
			pathEndpoints.push({start, length});
			let duration = textLength * transitionSpeed;
			pathDurations.push(duration);
			if (showPaths) { // show points for illustration
				showEndpoints(selPath, pathLength, textLength); 
			}
		});
}

function showEndpoints(_selection, pathLength, textLength) {
	d3.selectAll("path")
		.style("stroke", "yellow")
		.style("stroke-width", "2px")
		.style("stroke-dasharray", 5);
	let startPoint = _selection.getPointAtLength(pathLength * .5 - textLength / 2);
	let endPoint = _selection.getPointAtLength(pathLength * .5 + textLength / 2);
	d3.select("svg").append("circle").attr("cx", startPoint.x).attr("cy", startPoint.y).attr("r", 10).attr("fill", "lightblue").attr("opacity", .6);      
	d3.select("svg").append("circle").attr("cx", endPoint.x).attr("cy", endPoint.y).attr("r", 10).attr("fill", "lightblue").attr("opacity", .6);
}

function intializeStars() {
	let bBox = d3.select("div.header").node().getBoundingClientRect();
	let divh = d3.select(containerDiv)
		.insert("div", "div.header")
		.attr("class", "stars");

	divh.append("canvas")
		.attr("id", "view")
		.attr("width", bBox.width)
		.attr("height", bBox.height);

	starOptions.view = document.querySelector("#view");
  
	let background = new Image();
	background.src = backgroundImage;
	starOptions.backgroundImage = background;

	app = new App(starOptions);
	window.addEventListener("load", app.start());
	window.focus();
}

function starsAlongPath(path, index) {
	return function(d, i, a) {
		return function(t) {
			let p = path.getPointAtLength(pathEndpoints[index].start + t * pathEndpoints[index].length);
			app.spawn(p.x , p.y);
			return "translate(0,0)";
		};
	};
}

function animateStars() {
	let durations = pathDurations.concat(pathDurations);
	let chainedSel = d3.selectAll(".trans").data(durations);
	chainedTransition(chainedSel);

	function chainedTransition(_chainedSel, _index = 0) {
		const num_headers = _chainedSel.size() / 2;
		let nextSel = _chainedSel.filter((d,i) => (i % num_headers) === _index);
		transitionNext(nextSel);
    
		function transitionNext(_selection){
			console.log(_index);
			if (_index === num_headers - 1) {
				setTimeout( function() { 
					app.setActivate(false); // disable requestAnimationFrame calls
					transitionLast(_selection);
				}, 1000);  
			}
			else {
				// the text
				_selection.filter((d,i) => i === 0)
					.transition()
					.duration(d => d)
					.delay(1000)
					.ease(d3.easeLinear)
					.style("opacity", 1);
				// the path
				let sel = _selection.filter((d,i) => i === 1);
				sel.transition()
					.duration(d => d)
					.delay(1000)
					.ease(d3.easeLinear)
					.attrTween("transform", starsAlongPath(sel.node(), _index))
					.on ("end", function() {
						_index = _index + 1;
						if (num_headers > _index) { 
							nextSel = _chainedSel.filter((d,i) => (i % num_headers) === _index);
							transitionNext(nextSel);
						} 
					});
			}
		}

		function transitionLast(_selection){
			starOptions.texture = document.querySelector("#star-texture");
			let app2 = new App(starOptions);
			window.addEventListener("load", app2.start());
			window.focus();
			// the text
			_selection.filter((d,i) => i === 0)
				.transition()
				.duration(0)
				.style("opacity", 1);

			// the path
			let path = _selection.filter((d,i) => i === 1).node();
			let start = pathEndpoints[pathEndpoints.length-1].start;
			let length = pathEndpoints[pathEndpoints.length-1].length;
      
			for (let t = 0; t < 1; t = t + explosionStrength) { 
				let p = path.getPointAtLength(start + t * length);
				app2.spawn(p.x , p.y);        
			}

			setTimeout( function() {
				app2.setActivate(false); // disable requestAnimationFrame calls
			}, 3000);
		}
	}
}

stars.js

// from https://codepen.io/osublake/pen/RLOzxo by Blake Bowen
// adjusted by some lines 
// - to optionally switch off requestAnimationFrame calls after animation is finished
// - to remove options panel
// - to remove mouse listener
// - to add background image

console.clear();
var log = console.log.bind(console);
var TAU = Math.PI * 2;
//
// PARTICLE
// ===========================================================================
var Particle = /** @class */ (function () {
	function Particle(texture, frame) {
		this.texture = texture;
		this.frame = frame;
		this.alive = false;
		this.width = frame.width;
		this.height = frame.height;
		this.originX = frame.width / 2;
		this.originY = frame.height / 2;
	}
	Particle.prototype.init = function (x, y) {
		if (x === void 0) { x = 0; }
		if (y === void 0) { y = 0; }
		var angle = random(TAU);
		var force = random(2, 6);
		this.x = x;
		this.y = y;
		this.alpha = 1;
		this.alive = true;
		this.theta = angle;
		this.vx = Math.sin(angle) * force;
		this.vy = Math.cos(angle) * force;
		this.rotation = Math.atan2(this.vy, this.vx);
		this.drag = random(0.82, 0.97);
		this.scale = random(0.1, 1);
		this.wander = random(0.5, 1.0);
		this.matrix = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
		return this;
	};
	Particle.prototype.update = function () {
		var matrix = this.matrix;
		this.x += this.vx;
		this.y += this.vy;
		this.vx *= this.drag;
		this.vy *= this.drag;
		this.theta += random(-0.5, 0.5) * this.wander;
		this.vx += Math.sin(this.theta) * 0.1;
		this.vy += Math.cos(this.theta) * 0.1;
		this.rotation = Math.atan2(this.vy, this.vx);
		this.alpha *= 0.98;
		this.scale *= 0.985;
		this.alive = this.scale > 0.06 && this.alpha > 0.06;
		var cos = Math.cos(this.rotation) * this.scale;
		var sin = Math.sin(this.rotation) * this.scale;
		matrix.a = cos;
		matrix.b = sin;
		matrix.c = -sin;
		matrix.d = cos;
		matrix.tx = this.x - ((this.originX * matrix.a) + (this.originY * matrix.c));
		matrix.ty = this.y - ((this.originX * matrix.b) + (this.originY * matrix.d));
		return this;
	};
	Particle.prototype.draw = function (context) {
		var m = this.matrix;
		var f = this.frame;
		context.globalAlpha = this.alpha;
		context.setTransform(m.a, m.b, m.c, m.d, m.tx, m.ty);
		context.drawImage(this.texture, f.x, f.y, f.width, f.height, 0, 0, this.width, this.height);
		return this;
	};
	return Particle;
}());
//
// APP
// ===========================================================================
var App = /** @class */ (function () {
	function App(options) {
		var _this = this;
		this.pool = [];
		this.particles = [];
		this.pointer = {
			x: -9999,
			y: -9999
		};
		this.buffer = document.createElement("canvas");
		this.bufferContext = this.buffer.getContext("2d");
		this.supportsFilters = (typeof this.bufferContext.filter !== "undefined");
		_this.activate = true;
		this.setActivate = function(b) {_this.activate = b;}; // setter for activate
		// this.setTexture = function(t) {_this.texture = t;}
		this.AnimationId;
		this.pointerMove = function (event) {
			event.preventDefault();
			var pointer = event.targetTouches ? event.targetTouches[0] : event;
			_this.pointer.x = pointer.clientX;
			_this.pointer.y = pointer.clientY;
            
			for (var i = 0; i < random(2, 7); i++) {
				_this.spawn(_this.pointer.x, _this.pointer.y);
			}
            
		};
		this.resize = function (event) {
			_this.width = _this.buffer.width = _this.view.width; // = window.innerWidth;
			_this.height = _this.buffer.height = _this.view.height; // = window.innerHeight;
		};
		this.render = function (time) {
			var context = _this.context;
			var particles = _this.particles;
			var bufferContext = _this.bufferContext;
			// context.fillStyle = _this.backgroundColor;
			// context.fillRect(0, 0, _this.width, _this.height);
			// new: draw background image
			var ptrn = context.createPattern(_this.backgroundImage, "repeat"); // Create a pattern with this image, and set it to "repeat".
			context.fillStyle = ptrn;
			context.fillRect(0, 0, _this.width, _this.height); 

			bufferContext.globalAlpha = 1;
			bufferContext.setTransform(1, 0, 0, 1, 0, 0);
			bufferContext.clearRect(0, 0, _this.width, _this.height);
			bufferContext.globalCompositeOperation = _this.blendMode;
			for (var i = 0; i < particles.length; i++) {
				var particle = particles[i];
				if (particle.alive) {
					particle.update();
				}
				else {
					_this.pool.push(particle);
					removeItems(particles, i, 1);
				}
			}
			for (var _i = 0, particles_1 = particles; _i < particles_1.length; _i++) {
				var particle = particles_1[_i];
				particle.draw(bufferContext);
			}
			if (_this.supportsFilters) {
				if (_this.useBlurFilter) {
					context.filter = "blur(" + _this.filterBlur + "px)";
				}
				context.drawImage(_this.buffer, 0, 0);
				if (_this.useContrastFilter) {
					context.filter = "drop-shadow(4px 4px 4px rgba(0,0,0,1)) contrast(" + _this.filterContrast + "%)";
				}
			}
			context.drawImage(_this.buffer, 0, 0);
			context.filter = "none";
            
			if (_this.activate) { //  call requestAnimateFrame just if flag is true
				this.AnimationId = requestAnimationFrame(_this.render); 
			} 
			else { 
				cancelAnimationFrame(this.AnimationId);
			}
		};
		Object.assign(this, options);
		this.context = this.view.getContext("2d", { alpha: false });
	}
	App.prototype.spawn = function (x, y) {
		var particle;
		if (this.particles.length > this.maxParticles) {
			particle = this.particles.shift();
		}
		else if (this.pool.length) {
			particle = this.pool.pop();
		}
		else {
			particle = new Particle(this.texture, sample(this.frames));
		}
		particle.init(x, y);
		this.particles.push(particle);
		return this;
	};
	App.prototype.start = function () {
		this.resize();
		this.render();
		this.view.style.visibility = "visible";
		if (this.mouseListener) {
			if (window.PointerEvent) {
				window.addEventListener("pointermove", this.pointerMove);
			}
			else {
				window.addEventListener("mousemove", this.pointerMove);
				window.addEventListener("touchmove", this.pointerMove);
			}
		}
		window.addEventListener("resize", this.resize);
		requestAnimationFrame(this.render);
		return this;
	};
	return App;
}());
//
// CREATE FRAMES
// ===========================================================================
function createFrames(numFrames, width, height) {
	var frames = [];
	for (var i = 0; i < numFrames; i++) {
		frames.push({
			x: width * i,
			y: 0,
			width: width,
			height: height
		});
	}
	return frames;
}
//
// REMOVE ITEMS
// ===========================================================================
function removeItems(array, startIndex, removeCount) {
	var length = array.length;
	if (startIndex >= length || removeCount === 0) {
		return;
	}
	removeCount = (startIndex + removeCount > length ? length - startIndex : removeCount);
	var len = length - removeCount;
	for (var i = startIndex; i < len; ++i) {
		array[i] = array[i + removeCount];
	}
	array.length = len;
}
//
// RANDOM
// ===========================================================================
function random(min, max) {
	if (max == null) {
		max = min;
		min = 0;
	}
	if (min > max) {
		var tmp = min;
		min = max;
		max = tmp;
	}
	return min + (max - min) * Math.random();
}
function sample(array) {
	return array[(Math.random() * array.length) | 0];
}

style.css

body {
  margin: 0;
}

div.chart {
  position: relative;
  height: 100vh;
  width: 100vw;
}

div.header, div.stars {
  position: absolute;
  top: 0px;
  left: 0px;
  z-index: -1;  
}

path {
  fill: none;
}

textPath {
  opacity: 0;
  font-size: 2em;
  font-family: 'Indie Flower', cursive;
}

text.no-effect {
  fill: lightgrey;
}

text.effect {
  fill: url(#grad1);
}