<div id="sky_div">
<img id="sky" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/sky.png">
</div>
<div id="svg_container">
<svg id="svg" viewBox="0 0 100 100";></svg>
</div>
<div id="viewbox_overlay">
<div id="flagpole"></div>
</div>
#sky_div {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
overflow: hidden;
}
#sky {
position: absolute;
height: 100%;
right: 0;
}
#svg_container {
width: 80%;
height: 80%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
svg {
width: 100%;
height: 100%;
}
#viewbox_overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#flagpole {
position: relative;
width: 2%;
top: 34%;
left: 26.5%;
background: hsl(20, 100%, 15%);
border: 1px solid black;
border-bottom: 0;
border-radius: 4px 4px 0 0;
}
////////////////////////////////////////////////
//////////// ANIMATED SVG FLAG ///////////
////////////////////////////////////////////////
///---INITIATION---///
//HTML variables
var svg = document.getElementById("svg");
var skyDiv = document.getElementById("sky_div");
var shadowsAndFlagpoleDiv = document.getElementById("viewbox_overlay");
var flagpole = document.getElementById("flagpole");
//settings
var displayPoints = false;
var displaySpans = false;
var displaySkins = true;
var changeColor = true;
var fw = 45; // flag width (as percentage of svg viewbox width)
var fh = 30; // flag height (as percentage of svg viewbox height)
var fwtc = fw+1; // flag width thread count
var fhtc = fh+1; // flag height thread count
var rigidity = 50; // (iterations of position-accuracy refinement)
var gravity = 0.001; // (rate of y-velocity increase per frame)
var wind = 0.1; // (rate of x-velocity increase per frame)
//flag components
var pointCount = 0;
var points = [];
var spanCount = 0;
var spans = [];
var skinCount = 0;
var skins = [];
var sgf = 0.1; // skin gap fill (fills gaps between skins)
//other components
var frameCount = 0;
var skyPosition = 0;
scaleHtmlElements();
///---OBJECTS---///4
//POINTS
//point constructor
function Point(current_x, current_y) {
this.cx = current_x;
this.cy = current_y;
this.px = this.cx; // previous x value
this.py = this.cy; // previous y value
this.pinned = false;
this.id = pointCount;
pointCount += 1;
}
//adds a new point object instance to points array
function addPt(x,y) {
points.push( new Point(x,y) );
}
//creates flag points
for (i=0; i<fwtc; i++) {
var x = (i*fh/(fhtc-1))+(100-fw)/2; // centers flag horizontally
for (j=0; j<fhtc; j++) {
var y = (j*fw/(fwtc-1))+(100-fh)/2; // centers flag vertically
addPt(x,y);
}
}
//pins points' left column
for (i=0; i<fhtc; i++) { points[i].pinned = true; }
//adds points to viewbox
placePoints();
//SPANS
//span constructor
function Span(point_1, point_2) {
this.p1 = point_1;
this.p2 = point_2;
this.l = distanceBetween(this.p1,this.p2); // length
this.id = spanCount;
spanCount += 1;
}
//adds a new span object instance to spans array
function addSp(p1,p2) {
spans.push( new Span( getPt(p1), getPt(p2) ) );
}
//creates flag spans
for (i=0; i<points.length-1; i++) {
if ( (i+1) % fhtc !== 0 ) {
addSp(i,i+1); // vertical spans
if ( (i+2) % fhtc !== 0) {
addSp(i,i+2); // vertical span reinforcements
}
}
if ( i < points.length-fhtc) { addSp(i,i+fhtc); } // horizontal spans
}
//adds spans to viewbox
placeSpans();
//SKINS
//skin constructor
function Skin(points_array) {
this.pa = points_array;
this.lh = 0; // left side hue
this.rh = 0; // right side hue
this.ll = 50; // left side lightness
this.rl = 50; // right side lightness
this.id = skinCount;
skinCount += 1;
}
//adds a new skin object instance to skins array
function addSk(points_array) {
skins.push( new Skin(points_array) );
}
//creates flag skins
for (i=1; i<=fwtc-1; i++) { // creates skins as columns
var points_array = [];
for (j=fhtc*(i-1); j<fhtc*i; j++) { points_array.push( points[j] ); } // adds all left side points
for (k=fhtc*(i+1)-1; k>=fhtc*i; k--) { points_array.push( points[k] ); } // adds all right side points
points_array.push( points[fhtc*(i-1)] ); // connects path back to first point
addSk(points_array);
points_array = [];
}
//adds skin hues
var hcr = 0.25; // hue change rate (per frame)
var hr = 30;//0.1; // hue range (from flag's left to right sides; full spectrum = 360)
var hsw = hr/(skins.length-1); // hue stop width (hue increase from skins' left to right sides)
var clh = 0; // column left hue
var crh = 0; // column right hue
if (changeColor) {
for (i=0; i<skins.length; i++) {
clh = crh;
crh += hsw;
skins[i].lh = clh;
skins[i].rh = crh;
}
}
//adds skins to viewbox
placeSkins();
///---FUNCTIONS---///
//scales html elements to match SVG viewbox scaling
function scaleHtmlElements() {
if (window.innerWidth > window.innerHeight) {
shadowsAndFlagpoleDiv.style.height = window.innerHeight*0.8+"px";
shadowsAndFlagpoleDiv.style.width = shadowsAndFlagpoleDiv.style.height;
skyDiv.style.height = window.innerHeight*0.8+"px";
skyDiv.style.width = shadowsAndFlagpoleDiv.style.height;
} else {
shadowsAndFlagpoleDiv.style.width = window.innerWidth*0.8+"px";
shadowsAndFlagpoleDiv.style.height = shadowsAndFlagpoleDiv.style.width;
skyDiv.style.width = window.innerWidth*0.8+"px";
skyDiv.style.height = shadowsAndFlagpoleDiv.style.width;
}
var fbcr = flagpole.getBoundingClientRect();
flagpole.style.height = (window.innerHeight - fbcr.top) + "px";
}
//generates random number between a minimum and maximum value
function randNumBetween(min,max) {
return Math.floor(Math.random()*(max-min+1))+min;
}
//gets a point by id number
function getPt(id) {
for (var i=0; i<points.length; i++) {
if (points[i].id == id) { return points[i]; }
}
}
//gets distance between two points (pythogorian theorum)
function distanceBetween(point_1, point_2) {
var x_difference = point_2.cx - point_1.cx;
var y_difference = point_2.cy - point_1.cy;
return Math.sqrt( x_difference*x_difference + y_difference*y_difference);
}
//adds points to the viewbox as svg circle elements
function placePoints() {
if (displayPoints) {
for (var i=0; i<points.length; i++) {
var p = points[i];
var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.classList.add("point");
circle.id = "p"+p.id;
circle.style.r = ".2";
circle.style.cx = p.cx+"px";
circle.style.cy = p.cy+"px";
circle.style.fill = "green";
svg.appendChild(circle);
}
}
}
//adds spans to the viewbox as svg path elements
function placeSpans() {
if (displaySpans) {
for (var i=0; i<spans.length; i++) {
var sp = spans[i];
var line = document.createElementNS("http://www.w3.org/2000/svg", "path");
line.classList.add("span");
line.id = "sp"+sp.id;
line.setAttribute("d", "M" + sp.p1.cx + "," + sp.p1.cy + " L" + sp.p2.cx + "," + sp.p2.cy);
line.style.stroke = "black";
line.style.strokeWidth = ".1";
line.style.strokeLinecap = "round";
svg.appendChild(line);
}
}
}
//adds skins to the viewbox as svg path elements
function placeSkins() {
if (displaySkins) {
for (var i=0; i<skins.length; i++) {
var sk = skins[i];
var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
var linearGradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
var stop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
var stop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
var skinPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
linearGradient.id = "gradient"+sk.id;
linearGradient.setAttribute( "style", "x1:0%; y1:50%; x2:100%; y2:50%" );
stop1.id = "st1_"+sk.id;
stop1.setAttribute( "offset", "0%" );
stop1.setAttribute( "stop-color", `hsl(${sk.lh}, 100%, ${sk.ll}%)` );
stop2.id = "st2_"+sk.id;
stop2.setAttribute( "offset", "100%" );
stop2.setAttribute( "stop-color", `hsl(${sk.rh}, 100%, ${sk.rl}%)` );
skinPath.classList.add("skin");
skinPath.id = "sk"+sk.id;
var path_string = "M" + (sk.pa[0].cx-sgf) + "," + sk.pa[0].cy + " ";
for (j=1; j<sk.pa.length; j++) {
if ( j < (sk.pa.length-1)/2 || j == sk.pa.length ) {
path_string += "L" + (sk.pa[j].cx-sgf) + "," + sk.pa[j].cy + " ";
} else {
path_string += "L" + (sk.pa[j].cx+sgf) + "," + sk.pa[j].cy + " ";
}
}
skinPath.setAttribute("d", path_string);
skinPath.style.fill = "url(#gradient"+sk.id+")";
svg.appendChild(defs);
defs.appendChild(linearGradient);
linearGradient.appendChild(stop1);
linearGradient.appendChild(stop2);
svg.appendChild(skinPath);
}
}
}
//updates points using verlet velocity
function updatePoints() {
for(var i=0; i<points.length; i++) {
var p = points[i]; // point object
if (!p.pinned) {
var xv = (p.cx - p.px); // x velocity
var yv = (p.cy - p.py); // y velocity
p.px = p.cx; // updates previous x as current x
p.py = p.cy; // updates previous y as current y
p.cx += xv; // updates current x with new velocity
p.cy += yv; // updates current y with new velocity
p.cx += wind; // add wind to x
p.cy += gravity; // add gravity to y
}
}
}
//updates spans by reconciling new point positions with span length
function updateSpans() {
for (var i=0; i<spans.length; i++) {
var sp = spans[i];
var dx = sp.p2.cx - sp.p1.cx; // distance between x values
var dy = sp.p2.cy - sp.p1.cy; // distance between y values
var d = Math.sqrt( dx*dx + dy*dy); // distance between the points
var r = sp.l / d; // ratio (span length over distance between points)
var mx = sp.p1.cx + dx / 2; // midpoint between x values
var my = sp.p1.cy + dy / 2; // midpoint between y values
var ox = dx / 2 * r; // offset of each x value (compared to span length)
var oy = dy / 2 * r; // offset of each y value (compared to span length)
if (!sp.p1.pinned) {
sp.p1.cx = mx - ox; // updates span's first point x value
sp.p1.cy = my - oy; // updates span's first point y value
}
if (!sp.p2.pinned) {
sp.p2.cx = mx + ox; // updates span's second point x value
sp.p2.cy = my + oy; // updates span's second point y value
}
}
}
//updates sky background
function moveClouds() {
var skyImage = document.getElementById("sky");
var siw = skyImage.width; // sky image width
var sdw = parseInt(skyDiv.style.width); // sky div width
if (skyPosition >= sdw - siw) {
skyPosition -= siw/6000;
} else {
skyPosition = -(siw/4);
}
skyImage.style.right = skyPosition+"px";
}
//updates svg elements
function updateSvg() {
//(updates points)
if (displayPoints) {
for (i=0; i<points.length; i++) {
var p = points[i];
var svgElement = document.getElementById("p"+p.id); // gets svg element by id value
svgElement.style.cx = p.cx; // updates svg circle element's "center x" value
svgElement.style.cy = p.cy; // updates svg circle element's "center y" value
}
}
//(updates spans)
if (displaySpans) {
for (j=0; j<spans.length; j++) {
var sp = spans[j];
var svgElement = document.getElementById("sp"+sp.id); // gets svg element by id value
svgElement.setAttribute("d",
"M" + sp.p1.cx + "," + sp.p1.cy + " " +
"L" + sp.p2.cx + "," + sp.p2.cy);
}
}
//(updates skins)
if (displaySkins) {
for (k=0; k<skins.length; k++) {
//(shape)
var sk = skins[k];
var skinPath = document.getElementById("sk"+sk.id); // gets svg element by id value
var path_string = "M" + (sk.pa[0].cx-sgf) + "," + sk.pa[0].cy + " ";
for (j=1; j<sk.pa.length; j++) {
if ( j<(sk.pa.length-1)/2 || j==sk.pa.length ) {
path_string += "L" + (sk.pa[j].cx-sgf) + "," + sk.pa[j].cy + " ";
} else {
path_string += "L" + (sk.pa[j].cx+sgf) + "," + sk.pa[j].cy + " ";
}
}
skinPath.setAttribute("d", path_string);
//(color)
if (changeColor) {
sk.lh < 360 ? sk.lh -= hcr : sk.lh = 0;
sk.rh < 360 ? sk.rh -= hcr : sk.rh = 0;
}
//(shading)
var bskw = fw/(fwtc-1); // base skin width
var cskw = Math.abs(sk.pa[sk.pa.length-2].cx - sk.pa[sk.pa.length-1].cx); // current skin width
var lpct = cskw*50/bskw; // lightness percentage
if (lpct > 49) {lpct = 49;} // maximizes lightness percentage
if (lpct < 35) {lpct = 35;} // minimizes lightness percentage
if (k === 0) { // sets leftmost skin with solid lightness
sk.ll = lpct;
sk.rl = lpct;
} else { // then sets gradient from previous skin to current skin's lightness based on width
sk.ll = skins[k-1].rl;
sk.rl = lpct;
}
//(updates gradients)
document.getElementById("st1_"+sk.id).setAttribute( "stop-color", `hsl(${sk.lh}, 100%, ${sk.ll}%)` );
document.getElementById("st2_"+sk.id).setAttribute( "stop-color", `hsl(${sk.rh}, 100%, ${sk.rl}%)` );
skinPath.style.fill = "url(#gradient"+sk.id+")";
}
}
}
//waves flag
function waveFlag() {
var ry = Math.random()*2.5-1.25; // random y-velocity change
if (frameCount % 8 === 0) { // frequency of y-velocity change
for (var i=0; i<points.length/5; i++) { // number of points' y-velocities changed
//(random point near left of flag)
var rp = randNumBetween( Math.round(points.length*0.15), Math.round(points.length*0.5) );
points[rp].py += ry; // updates selected points' y-velocity
}
}
}
//updates coordinates and frames continuously
function updateViewbox() {
updatePoints();
for (var i=0; i<rigidity; i++) { updateSpans(); }
updateSvg();
waveFlag();
moveClouds();
window.requestAnimationFrame(updateViewbox);
frameCount += 1;
}
///---EVENTS---///
//updates objects & renders svg
window.addEventListener("load", updateViewbox);
//scales viewbox shadow on window resize
window.addEventListener('resize', scaleHtmlElements);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.