Richard Knight před 5 roky
revize
70e328a829
10 změnil soubory, kde provedl 628 přidání a 0 odebrání
  1. 1 0
      .gitignore
  2. 10 0
      README.md
  3. 106 0
      css/style.css
  4. 7 0
      css/style.css.map
  5. 22 0
      index.html
  6. 115 0
      js/index.js
  7. 1 0
      js/index.js.map
  8. 216 0
      sass/style.scss
  9. 134 0
      ts/index.ts
  10. 16 0
      tsconfig.json

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+.sass-cache/*

+ 10 - 0
README.md

@@ -0,0 +1,10 @@
+# Flipper #
+
+**Flippin' Typescript & SASS Flippy Card**
+
+## Based on... ##
+
+A Pen created at CodePen.io.
+https://codepen.io/averyvery/pen/IvDLB.
+
+ Read more abvout the original version in the blog post here: http://viget.com/inspire/make-a-flippin-3d-countdown-with-css-and-javascript#

+ 106 - 0
css/style.css

@@ -0,0 +1,106 @@
+.flipper-card {
+  box-shadow: 0 10px 5px -5px rgba(0, 0, 0, 0.2);
+  height: 300px;
+  left: 50%;
+  line-height: 300px;
+  margin: -150px 0 -100px;
+  perspective: 500px;
+  position: absolute;
+  text-align: center;
+  top: 50%;
+  transform: translateZ(0);
+  width: 200px; }
+  .flipper-card span {
+    background: #202020;
+    color: #f8f8f8;
+    display: block;
+    font-size: 250px;
+    left: 0;
+    position: absolute;
+    top: 0;
+    text-shadow: 0 1px 0 #282828, 0 2px 0 #1e1e1e, 0 3px 0 #141414, 0 4px 0 #0a0a0a, 0 5px 0 #000, 0 0 10px rgba(0, 0, 0, 0.8);
+    transform-origin: 0 150px 0;
+    width: 100%; }
+    .flipper-card span:before {
+      border-bottom: 2px solid #000;
+      content: '';
+      left: 0;
+      position: absolute;
+      width: 100%; }
+    .flipper-card span:after {
+      box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.35);
+      content: '';
+      height: 100%;
+      left: 0;
+      position: absolute;
+      top: 0;
+      width: 100%; }
+  .flipper-card .small {
+    font-size: 175px; }
+  .flipper-card .top {
+    border-top-left-radius: 11px;
+    border-top-right-radius: 11px;
+    box-shadow: inset 0 2px rgba(0, 0, 0, 0.9), inset 0 3px 0 rgba(255, 255, 255, 0.4);
+    height: 50%;
+    overflow: hidden; }
+    .flipper-card .top:before {
+      bottom: 0; }
+    .flipper-card .top:after {
+      background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+      border-top-left-radius: 11px;
+      border-top-right-radius: 11px; }
+  .flipper-card .bottom {
+    border-radius: 10px;
+    height: 100%; }
+    .flipper-card .bottom:before {
+      top: 50%; }
+    .flipper-card .bottom:after {
+      border-radius: 10px;
+      background: linear-gradient(rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0)); }
+  .flipper-card.down .top {
+    border-top-left-radius: 11px;
+    border-top-right-radius: 11px;
+    height: 50%; }
+    .flipper-card.down .top.current {
+      transform-style: flat;
+      z-index: 3; }
+    .flipper-card.down .top.next {
+      transform: rotate3d(1, 0, 0, -90deg);
+      z-index: 4; }
+  .flipper-card.down .bottom {
+    border-radius: 10px; }
+    .flipper-card.down .bottom.current {
+      z-index: 2; }
+    .flipper-card.down .bottom.next {
+      z-index: 1; }
+  .flipper-card.down.changing .bottom.current {
+    box-shadow: 0 75px 5px -20px rgba(0, 0, 0, 0.3);
+    transform: rotate3d(1, 0, 0, 90deg);
+    transition: transform 0.35s ease-in, box-shadow 0.35s ease-in; }
+  .flipper-card.down.changing .top.next, .flipper-card.down.changed .top.next {
+    transition: transform 0.35s ease-out 0.35s;
+    transform: none; }
+  .flipper-card.up .top {
+    height: 50%; }
+    .flipper-card.up .top.current {
+      z-index: 4; }
+    .flipper-card.up .top.next {
+      z-index: 3; }
+  .flipper-card.up .bottom.current {
+    z-index: 1; }
+  .flipper-card.up .bottom.next {
+    box-shadow: 0 75px 5px -20px rgba(0, 0, 0, 0.3);
+    transform: rotate3d(1, 0, 0, 90deg);
+    z-index: 2; }
+  .flipper-card.up.changing .top.current {
+    transform: rotate3d(1, 0, 0, -90deg);
+    transition: transform 0.2625s ease-in, box-shadow 0.2625s ease-in; }
+  .flipper-card.up.changing .bottom.next, .flipper-card.up.changed .bottom.next {
+    box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
+    transition: box-shadow 0.175s cubic-bezier(0.375, 1.495, 0.61, 0.78) 0.35s, transform 0.35s cubic-bezier(0.375, 1.495, 0.61, 0.78) 0.35s;
+    transform: rotate3d(1, 0, 0, 0); }
+  .flipper-card.changed .top.current,
+  .flipper-card.changed .bottom.current {
+    display: none; }
+
+/*# sourceMappingURL=style.css.map */

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 7 - 0
css/style.css.map


+ 22 - 0
index.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en" >
+<head>
+	<meta charset="UTF-8">
+	<title>Flippin' Countdown</title>
+	<link rel="stylesheet" href="css/style.css">
+</head>
+<body>
+
+<div class="flipper-card"></div>
+
+<template id="flipper-card-template">
+	<span class="current top {{ curSize }}">{{ curVal }}</span>
+	<span class="next top {{ nextSize }}">{{ nextVal }}</span>
+	<span class="current bottom {{ curSize }}">{{ curVal }}</span>
+	<span class="next bottom {{ nextSize }}">{{ nextVal }}</span>
+</template>
+
+<script  src="js/index.js"></script>
+
+</body>
+</html>

+ 115 - 0
js/index.js

@@ -0,0 +1,115 @@
+class Filpper {
+    constructor(el, template, startVal = 0, startDir = 'up') {
+        this.el = el;
+        this.template = template;
+        this.startVal = startVal;
+        this.startDir = startDir;
+        this.templateData = {
+            curSize: '',
+            curVal: '0',
+            nextSize: '',
+            nextVal: '0'
+        };
+        this.templateAsStr = template.innerHTML;
+        this.tokens = getSubstitutionTokens(this.templateAsStr);
+        this.templateData.curVal = startVal.toString();
+        this.setDirection(startDir);
+        this.update(startVal);
+    }
+    update(newVal) {
+        this.templateData.nextVal = newVal.toString();
+        this.setSizes();
+        this.runAnimation();
+    }
+    setDirection(direction) {
+        if (direction.toLowerCase() === 'up') {
+            this.el.classList.add('up');
+            this.el.classList.remove('down');
+        }
+        else {
+            this.el.classList.add('down');
+            this.el.classList.remove('up');
+        }
+    }
+    setSizes() {
+        const td = this.templateData;
+        this.templateData.curSize = this.getSize(td.curVal);
+        this.templateData.nextSize = this.getSize(td.nextVal);
+    }
+    getSize(val) {
+        return val.toString().length > 1 ? 'small' : '';
+    }
+    runAnimation() {
+        const inner = processTemplateSubstitutions(this.templateAsStr, this.tokens, this.templateData);
+        this.el.innerHTML = inner;
+        window.requestAnimationFrame(() => {
+            this.el.classList.remove('changed');
+            window.requestAnimationFrame(() => this.el.classList.add('changing'));
+        });
+        setTimeout(() => {
+            this.el.classList.add('changed');
+            this.el.classList.remove('changing');
+            this.templateData.curVal = this.templateData.nextVal;
+        }, this.duration * 0.9);
+    }
+}
+;
+// --------------------------------------------------------------------------------------------------------------------
+// Teplating Helpers
+const processTemplateSubstitutions = (template, tokens, data) => {
+    const processedTemplate = tokens.reduce((str, token) => str.replace(new RegExp(`{{\\s${token}\\s+}}`, 'gm'), path(data, token)), template);
+    return processedTemplate;
+};
+const getSubstitutionTokens = (template) => {
+    let resArr = [];
+    let tempArr;
+    const regex = new RegExp('{{\\s+(.*?)\\s+}}', 'g');
+    while ((tempArr = regex.exec(template)) !== null) {
+        resArr.push(tempArr[1]);
+    }
+    return Array.from(new Set(resArr));
+};
+const path = (obj, path, defaultval = '') => {
+    // Safely get nested properties without triggering type errors
+    // Usage: fetch(obj, 'a.b.c.d')
+    // Returns d if d exists, else defaultval or an empty string
+    if (typeof defaultval === 'undefined')
+        defaultval = null;
+    if (typeof obj !== 'object')
+        return defaultval;
+    if (typeof path !== 'string')
+        return defaultval;
+    var props = path.split('.');
+    var test_first = (obj, props) => {
+        if (props.length) {
+            var first = props[0];
+            if (obj.hasOwnProperty(first)) {
+                var rest = props.slice(1);
+                return test_first(obj[first], rest); // Recursive chomp
+            }
+            else {
+                return defaultval; // The default value, or null
+            }
+        }
+        else {
+            return obj; // The chomped object's last primitive property if it exists
+        }
+    };
+    return test_first(obj, props);
+};
+// --------------------------------------------------------------------------------------------------------------------
+// Run the flipper
+var card = document.querySelector('.flipper-card');
+var template = document.querySelector('#flipper-card-template');
+var c = new Filpper(card, template);
+c.update(1);
+setTimeout(() => c.update(2), 1000);
+setTimeout(() => c.update(3), 2000);
+setTimeout(() => c.update(4), 3000);
+setTimeout(() => c.update(5), 4000);
+setTimeout(() => c.update('A'), 5000);
+setTimeout(() => c.update('B'), 6000);
+setTimeout(() => c.update('C'), 7000);
+setTimeout(() => c.update('D'), 8000);
+setTimeout(() => c.update('E'), 9000);
+//# sourceMappingURL=index.js.map

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
js/index.js.map


+ 216 - 0
sass/style.scss

@@ -0,0 +1,216 @@
+// animation vars
+$duration: 0.35s;
+$bounce: cubic-bezier(0.375, 1.495, 0.610, 0.780);
+
+// dimensions
+$height: 300px;
+$width: 200px;
+
+.flipper-card {
+	box-shadow: 0 10px 5px -5px rgba(#000, 0.2);
+	height: $height;
+	left: 50%;
+	line-height: $height;
+	margin: -($height / 2) 0 0 -($width / 2);
+	perspective: 500px;
+	position: absolute;
+	text-align: center;
+	top: 50%;
+	transform: translateZ(0);
+	width: $width;
+
+	// the basic "card"
+	//    there are four of these: top current, top next, bottom current, and bottom next
+	span {
+		background: #202020;
+		color: #f8f8f8;
+		display: block;
+		font-size: 250px;
+		left: 0;
+		position: absolute;
+		top: 0;
+		text-shadow: 0 1px 0 (#000 + 40), 0 2px 0 (#000 + 30), 0 3px 0 (#000 + 20), 0 4px 0 (#000 + 10), 0 5px 0 #000, 0 0 10px rgba(#000, 0.8);
+		transform-origin: 0 150px 0;
+		width: 100%;
+
+		// the dividing line in the center
+		&:before {
+			border-bottom: 2px solid #000;
+			content: '';
+			left: 0;
+			position: absolute;
+			width: 100%;
+		}
+
+		// a shadow fill that adds some convexity on the card surfaces
+		&:after {
+			box-shadow: inset 0 0 60px rgba(#000, 0.35);
+			content: '';
+			height: 100%;
+			left: 0;
+			position: absolute;
+			top: 0;
+			width: 100%;
+		}
+	}
+
+
+	// two-digit numbers get the 'small' class
+	.small {
+		font-size: 175px;
+	}
+
+	.top {
+		// top card sit above the bottom ones, so if we give them the same
+		//   border radius they'll create some crunchiness.
+		//   instead, go one pixel smaller
+		border-top-left-radius: 11px;
+		border-top-right-radius: 11px;
+
+		// creating a light shine on the top of the card
+		box-shadow: inset 0 2px rgba(#000, 0.9), inset 0 3px 0 rgba(#fff, 0.4);
+
+		// top cards are only 50% height, and overflow-hidden
+		//   so they only show the top of their number
+		height: 50%;
+		overflow: hidden;
+
+		&:before {
+			bottom: 0;
+		}
+
+		&:after {
+			// top card needs to get darker as it curves downward
+			background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+			border-top-left-radius: 11px;
+			border-top-right-radius: 11px;
+		}
+	}
+
+	.bottom {
+		// bottom cards are 100% height, but their top half is hidden by "top" cards
+		//   this was the best way I could think of to show the bottom cards in half, but
+		//   there's probably another way using display: table-cell and vertical-align.
+		//   ew.
+		border-radius: 10px;
+		height: 100%;
+
+		&:before {
+			top: 50%;
+		}
+
+		&:after {
+			border-radius: 10px;
+			background: linear-gradient(rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0));
+		}
+	}
+
+	// styles that only apply when counting "down"
+	&.down {
+		.top {
+			// use a higher number than the bottoms to prevent crunchy border radiuses
+			border-top-left-radius: 11px;
+			border-top-right-radius: 11px;
+			height: 50%;
+
+			&.current {
+				// required to prevent safari bug: https://bugs.webkit.org/show_bug.cgi?id=61824
+				transform-style: flat;
+				z-index: 3;
+			}
+
+			&.next {
+				// when counting down, the next top card is rotated towards the user (and invisible)
+				transform: rotate3d(1, 0, 0, -90deg);
+				z-index: 4;
+			}
+		}
+
+		.bottom {
+			border-radius: 10px;
+
+			&.current {
+				z-index: 2;
+			}
+
+			&.next {
+				z-index: 1;
+			}
+		}
+
+		&.changing {
+			.bottom.current {
+				box-shadow: 0 75px 5px -20px rgba(#000, 0.3);
+				transform: rotate3d(1, 0, 0, 90deg);
+
+				// the current bottom card rotates up to hide itself, and reveal the next one
+				transition: transform 0.35s ease-in, box-shadow 0.35s ease-in;
+			}
+		}
+
+		&.changing,
+		&.changed {
+			.top.next {
+				// and the next top card rotates into view (after $duration)
+				transition: transform 0.35s ease-out 0.35s;
+				transform: none;
+			}
+		}
+	}
+
+	&.up {
+		.top {
+			height: 50%;
+
+			&.current {
+				z-index: 4;
+			}
+
+			&.next {
+				z-index: 3
+			}
+		}
+
+		.bottom {
+			&.current {
+				z-index: 1;
+			}
+
+			&.next {
+				box-shadow: 0 75px 5px -20px rgba(#000, 0.3);
+
+				// when counting "up", the next bottom card begins pointed at the user...
+				transform: rotate3d(1, 0, 0, 90deg);
+				z-index: 2;
+			}
+		}
+
+		&.changing {
+			.top.current {
+				// and the current top card does the rotating
+				transform: rotate3d(1, 0, 0, -90deg);
+
+				// when the card is "dropping" it should be faster
+				transition: transform 0.2625s ease-in, box-shadow 0.2625s ease-in;
+			}
+		}
+
+		&.changing,
+		&.changed {
+			.bottom.next {
+				box-shadow: 0 0 0 0 rgba(#000, 0);
+
+				// add a little bounce at the moment the card finishes falling
+				transition: box-shadow 0.175s cubic-bezier(0.375, 1.495, 0.61, 0.78) 0.35s, transform 0.35s cubic-bezier(0.375, 1.495, 0.61, 0.78) 0.35s;
+				transform: rotate3d(1, 0, 0, 0);
+			}
+		}
+	}
+
+	&.changed {
+		.top.current,
+		.bottom.current {
+			display: none;
+		}
+	}
+}

+ 134 - 0
ts/index.ts

@@ -0,0 +1,134 @@
+
+class Filpper {
+
+	duration: 1000;
+
+	templateAsStr: string;
+	tokens: string[];
+
+	templateData = {
+		curSize: '',
+		curVal: '0',
+		nextSize: '',
+		nextVal: '0'
+	};
+
+	constructor(
+		private el: HTMLElement,
+		private template: HTMLTemplateElement,
+		private startVal = 0,
+		private startDir: 'up'|'down' = 'up'
+	) {
+		this.templateAsStr = template.innerHTML as string;
+		this.tokens = getSubstitutionTokens(this.templateAsStr);
+		this.templateData.curVal = startVal.toString();
+		this.setDirection(startDir);
+		this.update(startVal);
+	}
+
+	update(newVal: number|string): void {
+		this.templateData.nextVal = newVal.toString();
+		this.setSizes();
+		this.runAnimation();
+	}
+
+	setDirection(direction: 'up'|'down'): void {
+		if (direction.toLowerCase() === 'up') {
+			this.el.classList.add('up');
+			this.el.classList.remove('down');
+		} else {
+			this.el.classList.add('down');
+			this.el.classList.remove('up');
+		}
+	}
+
+	setSizes(): void {
+		const td = this.templateData;
+		this.templateData.curSize = this.getSize(td.curVal);
+		this.templateData.nextSize = this.getSize(td.nextVal);
+	}
+
+	getSize(val: number|string): string {
+		return val.toString().length > 1 ? 'small' : '';
+	}
+
+	runAnimation(): void {
+		const inner = processTemplateSubstitutions(this.templateAsStr, this.tokens, this.templateData);
+		this.el.innerHTML = inner;
+		window.requestAnimationFrame(() => {
+			this.el.classList.remove('changed');
+			window.requestAnimationFrame(() => this.el.classList.add('changing'));
+		});
+		setTimeout(() => {
+			this.el.classList.add('changed');
+			this.el.classList.remove('changing');
+			this.templateData.curVal = this.templateData.nextVal;
+		}, this.duration * 0.9);
+	}
+
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// Teplating Helpers
+
+const processTemplateSubstitutions = (template: string, tokens: string[], data: { [key: string]: any }): string => {
+	const processedTemplate = tokens.reduce(
+		(str, token) => str.replace(new RegExp(`{{\\s${token}\\s+}}`, 'gm'), path(data, token)),
+		template
+	);
+	return processedTemplate;
+}
+
+const getSubstitutionTokens = (template: string) => {
+	let resArr = [];
+	let tempArr;
+	const regex = new RegExp('{{\\s+(.*?)\\s+}}', 'g');
+	while ((tempArr = regex.exec(template)) !== null) {
+		resArr.push(tempArr[1]);
+	}
+	return Array.from(new Set(resArr));
+}
+
+const path = (obj: { [key: string]: any }, path: string, defaultval = '') => {
+	// Safely get nested properties without triggering type errors
+	// Usage: fetch(obj, 'a.b.c.d')
+	// Returns d if d exists, else defaultval or an empty string
+
+	if (typeof defaultval === 'undefined') defaultval = null;
+	if (typeof obj   !== 'object') return defaultval;
+	if (typeof path  !== 'string') return defaultval;
+	var props = path.split('.');
+	var test_first = (obj: { [key: string]: any }, props: string[]): any => {
+		if (props.length) {
+			var first = props[0];
+			if (obj.hasOwnProperty(first)) {
+				var rest = props.slice(1);
+				return test_first(obj[first], rest); // Recursive chomp
+			} else {
+				return defaultval; // The default value, or null
+			}
+		} else {
+			return obj; // The chomped object's last primitive property if it exists
+		}
+	}
+	return test_first(obj, props);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Run the flipper
+
+var card = document.querySelector('.flipper-card');
+var template = document.querySelector('#flipper-card-template');
+
+var c = new (Filpper as any)(card, template);
+
+c.update(1);
+setTimeout(() => c.update(2), 1000);
+setTimeout(() => c.update(3), 2000);
+setTimeout(() => c.update(4), 3000);
+setTimeout(() => c.update(5), 4000);
+setTimeout(() => c.update('A'), 5000);
+setTimeout(() => c.update('B'), 6000);
+setTimeout(() => c.update('C'), 7000);
+setTimeout(() => c.update('D'), 8000);
+setTimeout(() => c.update('E'), 9000);

+ 16 - 0
tsconfig.json

@@ -0,0 +1,16 @@
+{
+	"compilerOptions": {
+		"outDir": "./js/",
+		"sourceMap": true,
+		"noImplicitAny": true,
+		"module": "ES2015",
+		"esModuleInterop": true,
+		"experimentalDecorators": true,
+		"target": "es6",
+		"allowJs": true,
+		"allowSyntheticDefaultImports": true
+	},
+	"include": [
+		"ts/**/*"
+	]
+}