Skip to content

Instantly share code, notes, and snippets.

@EE2dev
Last active April 1, 2018 00:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EE2dev/55205b9934f43e1bef5680ce8dbffc55 to your computer and use it in GitHub Desktop.
Save EE2dev/55205b9934f43e1bef5680ce8dbffc55 to your computer and use it in GitHub Desktop.
Animated Intro - 7
license: mit

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

Full screen version

See also:

  • Animated Intro (1) with
    • text elements: <h1>
    • animation: stars
    • color: individual letters
  • Animated Intro (2) with
    • text elements: <h1>
    • animation: stars + rotation
    • color: individual letters
  • Animated Intro (3) with
    • text elements: <h1>
    • animation: rotation + stars
    • color: individual letters
  • Animated Intro (4) with
    • text elements: <h1>
    • animation: stars
    • color: gradient
  • Animated Intro (5) with
    • text elements: SVG <textpath>
    • animation: stars
    • color: gradient
  • Animated Intro (6) with
    • text elements: SVG <textpath>
    • animation: stars
    • color: gradient
    • other: background image, show paths + textlength
  • Animated Intro (7) with
    • text elements: SVG <textpath>
    • animation: stars
    • color: gradient
    • other: background image, full size
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);
}
}
}
<!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>
// 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];
}
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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment