diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..af2d2f9 Binary files /dev/null and b/.DS_Store differ diff --git a/game.html b/game.html index 1b04695..ec2749d 100644 --- a/game.html +++ b/game.html @@ -51,7 +51,7 @@
cpu score: - ? + ?

diff --git a/game.js b/game.js index b6c4364..0b0091e 100644 --- a/game.js +++ b/game.js @@ -19,24 +19,18 @@ function getRandomCpu() { } function btnLowerClick() { - if (currentCpu.score < nextCpu.score) { - showResult(true); - return; - } - showResult(false); + currentCpu.score < nextCpu.score ? showResult(true) : showResult(false); } function btnHigherClick() { - + currentCpu.score > nextCpu.score ? showResult(true) : showResult(false); } function showResult(isCorrect) { - if (isCorrect) { - document.getElementById("col2").style.backgroundColor = "lightgreen"; - } - else { - document.getElementById("col2").style.backgroundColor = "#FF4444"; - } + document.getElementById("col2").style.backgroundColor = isCorrect ? "lightgreen" : "#FF4444"; + + document.getElementById("nextCpuScore").innerHTML = nextCpu.score.toString(); + countUp(); } function updateLayout() { @@ -48,6 +42,23 @@ function updateLayout() { document.getElementById("nextCpuTitle").innerText = nextCpu.name; } +async function countUp() { + document.getElementById("btnHigher").setAttribute("disabled", ""); + document.getElementById("btnLower").setAttribute("disabled", ""); + + const options = { + startVal: 7000, + separator: '.', + decimal: ',', + }; + let counter = new CountUp('nextCpuScore', nextCpu.score, options); + if (!counter.error) { + await counter.start(); + } else { + counter.error(demo.error); + } +} + function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); diff --git a/gst.js b/gst.js new file mode 100644 index 0000000..f6329a4 --- /dev/null +++ b/gst.js @@ -0,0 +1,24 @@ +var cpuList; +async function main() { + await fetch('./data.json') + .then((response) => response.json()) + .then((json) => cpuList = json); + + console.log(getRandomCpu().name) +} + +function getRandomCpu() { + let randomIndex = getRandomInt(0, cpuList.length) + return { + name: cpuList[randomIndex]["name"].split('@')[0], + score: cpuList[randomIndex]["cpuScore"] + } +} + +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive +} + +main(); \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..3952b16 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "CpuHigherLowerGame", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/countup.js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.6.0.tgz", + "integrity": "sha512-GeORCrCcaFUHP3RNf0/dWK+XQX+fsdtrMO31mNvsbKXNNG+DMTcgZ4dWpIG9BnOS8t5+iJbaRXgaaG9oLs0N4g==" + } + } +} diff --git a/node_modules/countup.js/.editorconfig b/node_modules/countup.js/.editorconfig new file mode 100644 index 0000000..e09b844 --- /dev/null +++ b/node_modules/countup.js/.editorconfig @@ -0,0 +1,12 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 diff --git a/node_modules/countup.js/.eslintrc.js b/node_modules/countup.js/.eslintrc.js new file mode 100644 index 0000000..897cfde --- /dev/null +++ b/node_modules/countup.js/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint', + ], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'no-prototype-builtins': 'off', + }, + env: { + browser: true, + es6: true, + } +}; diff --git a/node_modules/countup.js/.github/ISSUE_TEMPLATE.md b/node_modules/countup.js/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..8c875bd --- /dev/null +++ b/node_modules/countup.js/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ + +``` +[ ] Bug +[ ] Feature request + +CountUp.js version: + +``` + +## Description + + + diff --git a/node_modules/countup.js/.github/PULL_REQUEST_TEMPLATE.md b/node_modules/countup.js/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c867e30 --- /dev/null +++ b/node_modules/countup.js/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## I'm submitting a... + +``` +[ ] Bug Fix +[ ] Feature +[ ] Other (Refactoring, Added tests, Documentation, ...) +``` + +## Checklist + +- [ ] Test your changes +- [ ] Followed the build steps + + +## Description + +_please describe the changes that you are making_ + +_for features, please describe how to use the new feature_ + +_please include a reference to an existing issue, if applicable_ + + +## Does this PR introduce a breaking change? + +``` +[ ] Yes +[ ] No +``` diff --git a/node_modules/countup.js/.vscode/tasks.json b/node_modules/countup.js/.vscode/tasks.json new file mode 100644 index 0000000..4398771 --- /dev/null +++ b/node_modules/countup.js/.vscode/tasks.json @@ -0,0 +1,47 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "gulp", + "task": "build", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Open in Chrome Mac", + "command": "Chrome", + "osx": { + "command": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + }, + "args": [ + "${file}" + ], + "problemMatcher": [] + }, + { + "label": "Open in Chrome Linux", + "command": "Chrome", + "linux": { + "command": "google-chrome" + }, + "args": [ + "${file}" + ], + "problemMatcher": [] + }, + { + "label": "Open in Chrome Windows", + "command": "Chrome", + "windows": { + "command": "start chrome" + }, + "args": [ + "${file}" + ] + } + ] +} diff --git a/node_modules/countup.js/LICENSE.md b/node_modules/countup.js/LICENSE.md new file mode 100644 index 0000000..c7d9150 --- /dev/null +++ b/node_modules/countup.js/LICENSE.md @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/countup.js/README.md b/node_modules/countup.js/README.md new file mode 100644 index 0000000..ecc1b61 --- /dev/null +++ b/node_modules/countup.js/README.md @@ -0,0 +1,238 @@ +# CountUp.js +CountUp.js is a dependency-free, lightweight Javascript class that can be used to quickly create animations that display numerical data in a more interesting way. + +Despite its name, CountUp can count in either direction, depending on the start and end values that you pass. + +CountUp.js supports all browsers. MIT license. + +## [Try the demo](https://inorganik.github.io/countUp.js) + +Or tinker with CountUp in [Stackblitz](https://stackblitz.com/edit/countup-typescript) + +## Jump to: + +- **[Features](#features)** +- **[Usage](#usage)** +- **[Including CountUp](#including-countup)** +- **[Contributing](#contributing)** +- **[Creating Animation Plugins](#creating-animation-plugins)** + + +## CountUp for frameworks and plugins: + +- **[CountUp.js with Angular 2+](https://github.com/inorganik/ngx-countUp)** +- **[CountUp.js with Angular 1.x](https://github.com/inorganik/countUp.js-angular1)** +- **[CountUp.js with React](https://gist.github.com/inorganik/2cf776865a4c65c12857027870e9898e)** +- **[CountUp.js with Svelte](https://gist.github.com/inorganik/85a66941ab88cc10c5fa5b26aead5f2a)** +- **[CountUp.js Vue component wrapper](https://github.com/xlsdg/vue-countup-v2)** +- **[CountUp.js WordPress Plugin](https://wordpress.org/plugins/countup-js/)** +- **[CountUp.js with jQuery](https://gist.github.com/inorganik/b63dbe5b3810ff2c0175aee4670a4732)** + + +## Features +- **Animate when element scrolls into view** - new in v2.1.0. Use option `enableScrollSpy`. +- **Highly customizeable** with a large range of options, you can even substitute numerals. +- **Smart easing**: CountUp intelligently defers easing until it gets close enough to the end value for easing to be visually noticeable. Configureable in the [options](#options). +- **Separate bundles** for modern and legacy browsers, with and without the requestAnimationFrame polyfill. Choose `countUp.min.js` for modern browsers or `countUp.withPolyfill.min.js` for IE9 and older, and Opera mini. + +## Usage: + +**On npm** as: `countup.js`. You can import as a module or include the UMD script and access CountUp as a global. See [detailed instructions](#including-countup) below. + +**Params**: +- `target: string | HTMLElement | HTMLInputElement` - id of html element, input, svg text element, or DOM element reference where counting occurs +- `endVal: number` - the value you want to arrive at +- `options?: CountUpOptions` - optional configuration object for fine-grain control + +**Options** (defaults in parentheses): + +```ts +interface CountUpOptions { + startVal?: number; // number to start at (0) + decimalPlaces?: number; // number of decimal places (0) + duration?: number; // animation duration in seconds (2) + useGrouping?: boolean; // example: 1,000 vs 1000 (true) + useIndianSeparators?: boolean; // example: 1,00,000 vs 100,000 (false) + useEasing?: boolean; // ease animation (true) + smartEasingThreshold?: number; // smooth easing for large numbers above this if useEasing (999) + smartEasingAmount?: number; // amount to be eased for numbers above threshold (333) + separator?: string; // grouping separator (',') + decimal?: string; // decimal ('.') + // easingFn: easing function for animation (easeOutExpo) + easingFn?: (t: number, b: number, c: number, d: number) => number; + formattingFn?: (n: number) => string; // this function formats result + prefix?: string; // text prepended to result + suffix?: string; // text appended to result + numerals?: string[]; // numeral glyph substitution + enableScrollSpy?: boolean; // start animation when target is in view + scrollSpyDelay?: number; // delay (ms) after target comes into view + scrollSpyOnce?: boolean; // run only once + onCompleteCallback?: () => any; // gets called when animation completes + plugin?: CountUpPlugin; // for alternate animations +} +``` + +**Example usage**: + +```js +const countUp = new CountUp('targetId', 5234); +if (!countUp.error) { + countUp.start(); +} else { + console.error(countUp.error); +} +``` + +Pass options: +```js +const countUp = new CountUp('targetId', 5234, options); +``` + +with optional callback: + +```js +const countUp = new CountUp('targetId', 5234, { onCompleteCallback: someMethod }); + +// or (passing fn to start will override options.onCompleteCallback) +countUp.start(someMethod); + +// or +countUp.start(() => console.log('Complete!')); +``` + +**Other methods**: + +Toggle pause/resume: + +```js +countUp.pauseResume(); +``` + +Reset the animation: + +```js +countUp.reset(); +``` + +Update the end value and animate: + +```js +countUp.update(989); +``` + +### Animate when the element is scrolled into view + +Use the scroll spy option to animate when the element is scrolled into view. When using scroll spy, just initialize CountUp but do not call start(); + +```js +const countUp = new CountUp('targetId', 989, { enableScrollSpy: true }); +``` + +**Troubleshooting scroll spy** + +CountUp checks the scroll position as soon as it's initialized. So if you initialize it before the DOM renders and your target element is in view before any scrolling, you'll need to re-check the scroll position after the page renders: + +```js +// after DOM has rendered +countUp.handleScroll(); +``` +--- + +## Including CountUp + +CountUp is distributed as an ES6 module because it is the most standardized and most widely compatible module for browsers, though a UMD module is [also included](#umd-module). + +For the examples below, first install CountUp. This will give you the latest: +``` +npm i countup.js +``` + +### Example with vanilla js +This is what I used in the demo. Checkout index.html and demo.js. + +main.js: +```js +import { CountUp } from './js/countUp.min.js'; + +window.onload = function() { + var countUp = new CountUp('target', 2000); + countUp.start(); +} +``` + +Include in your html. Notice the `type` attribute: +```html + +``` + +To support IE and legacy browsers, use the `nomodule` script tag to include separate scripts that don't use the module syntax: + +```html + + +``` + +To run module-enabled scripts locally, you'll need a simple local server setup like [this](https://www.npmjs.com/package/http-server) (test the demo locally by running `npm run serve`) because otherwise you may see a CORS error when your browser tries to load the script as a module. + +### For Webpack and other build systems +Import from the package, instead of the file location: + +```js +import { CountUp } from 'countup.js'; +``` + +### UMD module + +CountUp is also wrapped as a UMD module in `./dist/countUp.umd.js` and it exposes CountUp as a global variable on the window scope. To use it, include `countUp.umd.js` in a script tag, and invoke it like so: + +```js +var numAnim = new countUp.CountUp('myTarget', 2000); +numAnim.start() +``` + +--- + +## Contributing + +Before you make a pull request, please be sure to follow these instructions: + +1. Do your work on `src/countUp.ts` +1. Lint: `npm run lint` +1. Run tests: `npm t` +1. Build and serve the demo by running `npm start` then check the demo to make sure it counts. + + + +--- + +## Creating Animation Plugins + +CountUp supports plugins as of v2.6.0. Plugins implement their own render method to display each frame's formatted value. A class instance or object can be passed to the `plugin` property of CountUpOptions, and the plugin's render method will be called instead of CountUp's. + +```ts +export declare interface CountUpPlugin { + render(elem: HTMLElement, formatted: string): void; +} +``` + +An example of a plugin: +```ts +export class SomePlugin implements CountUpPlugin { + // ...some properties here + + constructor(options: SomePluginOptions) { + // ...setup code here if you need it + } + + render(elem: HTMLElement, formatted: string): void { + // render DOM here + } +} +``` diff --git a/node_modules/countup.js/demo/demo-nomodule.js b/node_modules/countup.js/demo/demo-nomodule.js new file mode 100644 index 0000000..3fb1c18 --- /dev/null +++ b/node_modules/countup.js/demo/demo-nomodule.js @@ -0,0 +1,221 @@ +// same as demo.js but with a different instantiation of CountUp, +// and no lambdas + +window.onload = function () { + var el = function (id) { + return document.getElementById(id); + }; + var code, stars, endVal, options; + var demo = new countUp.CountUp('myTargetElement', 100); + var codeVisualizer = el('codeVisualizer'); + var errorSection = el('errorSection'); + el('version').innerHTML = demo.version; + + var changeEls = document.querySelectorAll('.updateCodeVis'); + for (var i = 0, len = changeEls.length; i < len; i++) { + changeEls[i].onchange = updateCodeVisualizer; + } + + el('swapValues').onclick = function () { + var oldStartVal = el('startVal').value; + var oldEndVal = el('endVal').value; + el('startVal').value = oldEndVal; + el('endVal').value = oldStartVal; + updateCodeVisualizer(); + }; + el('start').onclick = createCountUp; + el('apply').onclick = createCountUp; + el('pauseResume').onclick = function () { + code += '
demo.pauseResume();'; + codeVisualizer.innerHTML = code; + demo.pauseResume(); + }; + el('reset').onclick = function () { + code += '
demo.reset();'; + codeVisualizer.innerHTML = code; + demo.reset(); + }; + el('update').onclick = function () { + var updateVal = el('updateVal').value; + var num = updateVal ? updateVal : 0; + code += "
demo.update(" + num + ");"; + codeVisualizer.innerHTML = code; + demo.update(num); + }; + el('updateVal').onchange = function () { + var updateVal = el('updateVal').value; + var num = updateVal ? updateVal : 0; + code += '
demo.update(' + num + ');'; + codeVisualizer.innerHTML = code; + }; + // OPTION VALUES + var easingFunctions = { + easeOutExpo: function (t, b, c, d) { + return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b; + }, + outQuintic: function (t, b, c, d) { + var ts = (t /= d) * t; + var tc = ts * t; + return b + c * (tc * ts + -5 * ts * ts + 10 * tc + -10 * ts + 5 * t); + }, + outCubic: function (t, b, c, d) { + var ts = (t /= d) * t; + var tc = ts * t; + return b + c * (tc + -3 * ts + 3 * t); + } + }; + function getEasingFn() { + var fn = el('easingFnsDropdown').value; + if (fn === 'easeOutExpo') { + return null; + } + if (typeof easingFunctions[fn] === 'undefined') { + return undefined; + } + return easingFunctions[fn]; + } + function getEasingFnBody(fn) { + fn = typeof fn === 'undefined' ? getEasingFn() : fn; + if (typeof fn === 'undefined') { + return 'undefined function'; + } + if (fn !== null) { + return fn.toString().replace(/^ {8}/gm, ''); + } + return ''; + } + function getNumerals() { + var numeralsCode = el('numeralsDropdown').value; + // optionally provide alternates for 0-9 + switch (numeralsCode) { + case 'ea': // Eastern Arabic + return ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; + case 'fa': // Farsi + return ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; + default: + return null; + } + } + var stringifyArray = function (arr) { return '[\'' + arr.join('\', \'') + '\']'; }; + + // COUNTUP AND CODE VISUALIZER + + function createCountUp() { + establishOptionsFromInputs(); + demo = new countUp.CountUp('myTargetElement', endVal, options); + if (!demo.error) { + errorSection.style.display = 'none'; + demo.start(); + updateCodeVisualizer(); + } + else { + errorSection.style.display = 'block'; + document.getElementById('error').innerHTML = demo.error; + console.error(demo.error); + } + } + function methodToCallOnComplete() { + console.log('COMPLETE!'); + alert('COMPLETE!'); + } + function establishOptionsFromInputs() { + endVal = Number(el('endVal').value); + options = { + startVal: el('startVal').value, + decimalPlaces: el('decimalPlaces').value, + duration: Number(el('duration').value), + useEasing: el('useEasing').checked, + useGrouping: el('useGrouping').checked, + useIndianSeparators: el('useIndianSeparators').checked, + easingFn: typeof getEasingFn() === 'undefined' ? null : getEasingFn(), + separator: el('separator').value, + decimal: el('decimal').value, + prefix: el('prefix').value, + suffix: el('suffix').value, + numerals: getNumerals(), + onCompleteCallback: el('useOnComplete').checked ? methodToCallOnComplete : null + }; + // unset null values so they don't overwrite defaults + for (var key in options) { + if (options.hasOwnProperty(key)) { + if (options[key] === null) { + delete options[key]; + } + } + } + } + function updateCodeVisualizer() { + establishOptionsFromInputs(); + code = ''; + if (options.useEasing && options.easingFn) { + code += 'const easingFn = '; + var split = getEasingFnBody(options.easingFn).split('\n'); + for (var line in split) { + if (split.hasOwnProperty(line)) { + code += split[line].replace(' ', ' ') + '
'; + } + } + } + function indentedLine(keyPair, singleLine) { + if (singleLine === void 0) { singleLine = false; } + var delimeter = (singleLine) ? ';' : ','; + return "  " + keyPair + delimeter + "
"; + } + var opts = ''; + opts += (options.startVal !== '0') ? indentedLine("startVal: " + options.startVal) : ''; + opts += (options.decimalPlaces !== '0') ? indentedLine("decimalPlaces: " + options.decimalPlaces) : ''; + opts += (options.duration !== 2) ? indentedLine("duration: " + options.duration) : ''; + opts += (options.useEasing) ? '' : indentedLine("useEasing: " + options.useEasing); + opts += (options.useEasing && options.easingFn) ? indentedLine("easingFn") : ''; + opts += (options.useGrouping) ? '' : indentedLine("useGrouping: " + options.useGrouping); + opts += (options.useIndianSeparators) ? indentedLine("useIndianSeparators: " + options.useIndianSeparators) : ''; + opts += (options.separator !== ',') ? indentedLine("separator: '" + options.separator + "'") : ''; + opts += (options.decimal !== '.') ? indentedLine("decimal: '" + options.decimal + "'") : ''; + opts += (options.prefix.length) ? indentedLine("prefix: '" + options.prefix + "'") : ''; + opts += (options.suffix.length) ? indentedLine("suffix: '" + options.suffix + "'") : ''; + opts += (options.numerals && options.numerals.length) ? + indentedLine("numerals: " + stringifyArray(options.numerals)) : ''; + opts += (options.onCompleteCallback) ? indentedLine("onCompleteCallback: methodToCallOnComplete") : ''; + + if (opts.length) { + code += "const options = {
" + opts + "};
"; + code += "let demo = new CountUp('myTargetElement', " + endVal + ", options);
"; + } + else { + code += "let demo = new CountUp('myTargetElement', " + endVal + ");
"; + } + code += 'if (!demo.error) {
'; + code += indentedLine('demo.start()', true); + code += '} else {
'; + code += indentedLine('console.error(demo.error)', true); + code += '}'; + codeVisualizer.innerHTML = code; + } + // get current star count + var repoInfoUrl = 'https://api.github.com/repos/inorganik/CountUp.js'; + var getStars = new XMLHttpRequest(); + getStars.open('GET', repoInfoUrl, true); + getStars.timeout = 5000; + getStars.onreadystatechange = function () { + // 2: received headers, 3: loading, 4: done + if (getStars.readyState === 4) { + if (getStars.status === 200) { + if (getStars.responseText !== 'undefined') { + if (getStars.responseText.length > 0) { + var data = JSON.parse(getStars.responseText); + stars = data.stargazers_count; + // change input values + el('endVal').value = stars; + createCountUp(); + } + } + } + } + }; + getStars.onerror = function () { + console.error('error getting stars:', getStars.status); + stars = getStars.status; + demo.start(); + }; + getStars.send(); +} diff --git a/node_modules/countup.js/demo/demo.js b/node_modules/countup.js/demo/demo.js new file mode 100644 index 0000000..3df311f --- /dev/null +++ b/node_modules/countup.js/demo/demo.js @@ -0,0 +1,220 @@ +import { CountUp } from '../dist/countUp.js'; + +window.onload = function () { + var el = function (id) { + return document.getElementById(id); + }; + var code, stars, endVal, options; + var demo = new CountUp('myTargetElement', 100); + var codeVisualizer = el('codeVisualizer'); + var errorSection = el('errorSection'); + var startTime; + el('version').innerHTML = demo.version; + + document.querySelectorAll('.updateCodeVis').forEach(elem => elem.onchange = updateCodeVisualizer); + + el('swapValues').onclick = function () { + var oldStartVal = el('startVal').value; + var oldEndVal = el('endVal').value; + el('startVal').value = oldEndVal; + el('endVal').value = oldStartVal; + updateCodeVisualizer(); + }; + el('start').onclick = createCountUp; + el('apply').onclick = createCountUp; + el('pauseResume').onclick = function () { + code += '
demo.pauseResume();'; + codeVisualizer.innerHTML = code; + demo.pauseResume(); + }; + el('reset').onclick = function () { + code += '
demo.reset();'; + codeVisualizer.innerHTML = code; + demo.reset(); + }; + el('update').onclick = function () { + var updateVal = el('updateVal').value; + var num = updateVal ? updateVal : 0; + code += "
demo.update(" + num + ");"; + codeVisualizer.innerHTML = code; + demo.update(num); + }; + el('updateVal').onchange = function () { + var updateVal = el('updateVal').value; + var num = updateVal ? updateVal : 0; + code += '
demo.update(' + num + ');'; + codeVisualizer.innerHTML = code; + }; + // OPTION VALUES + var easingFunctions = { + easeOutExpo: function (t, b, c, d) { + return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b; + }, + outQuintic: function (t, b, c, d) { + var ts = (t /= d) * t; + var tc = ts * t; + return b + c * (tc * ts + -5 * ts * ts + 10 * tc + -10 * ts + 5 * t); + }, + outCubic: function (t, b, c, d) { + var ts = (t /= d) * t; + var tc = ts * t; + return b + c * (tc + -3 * ts + 3 * t); + } + }; + function getEasingFn() { + var fn = el('easingFnsDropdown').value; + if (fn === 'easeOutExpo') { + return null; + } + if (typeof easingFunctions[fn] === 'undefined') { + return undefined; + } + return easingFunctions[fn]; + } + function getEasingFnBody(fn) { + fn = typeof fn === 'undefined' ? getEasingFn() : fn; + if (typeof fn === 'undefined') { + return 'undefined function'; + } + if (fn !== null) { + return fn.toString().replace(/^ {8}/gm, ''); + } + return ''; + } + function getNumerals() { + var numeralsCode = el('numeralsDropdown').value; + // optionally provide alternates for 0-9 + switch (numeralsCode) { + case 'ea': // Eastern Arabic + return ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; + case 'fa': // Farsi + return ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; + default: + return null; + } + } + var stringifyArray = function (arr) { return '[\'' + arr.join('\', \'') + '\']'; }; + + // COUNTUP AND CODE VISUALIZER + + function createCountUp() { + establishOptionsFromInputs(); + demo = new CountUp('myTargetElement', endVal, options); + if (!demo.error) { + errorSection.style.display = 'none'; + startTime = Date.now(); + demo.start(); + updateCodeVisualizer(); + } + else { + errorSection.style.display = 'block'; + document.getElementById('error').innerHTML = demo.error; + console.error(demo.error); + } + } + function calculateAnimationTime() { + const duration = Date.now() - startTime; + console.log('actual animation duration (ms):', duration); + alert('COMPLETE!'); + } + function establishOptionsFromInputs() { + endVal = Number(el('endVal').value); + options = { + startVal: el('startVal').value, + decimalPlaces: el('decimalPlaces').value, + duration: Number(el('duration').value), + useEasing: el('useEasing').checked, + useGrouping: el('useGrouping').checked, + useIndianSeparators: el('useIndianSeparators').checked, + easingFn: typeof getEasingFn() === 'undefined' ? null : getEasingFn(), + separator: el('separator').value, + decimal: el('decimal').value, + prefix: el('prefix').value, + suffix: el('suffix').value, + numerals: getNumerals(), + onCompleteCallback: el('useOnComplete').checked ? calculateAnimationTime : null + }; + // unset null values so they don't overwrite defaults + for (var key in options) { + if (options.hasOwnProperty(key)) { + if (options[key] === null) { + delete options[key]; + } + } + } + } + function updateCodeVisualizer() { + establishOptionsFromInputs(); + code = ''; + if (options.useEasing && options.easingFn) { + code += 'const easingFn = '; + var split = getEasingFnBody(options.easingFn).split('\n'); + for (var line in split) { + if (split.hasOwnProperty(line)) { + code += split[line].replace(' ', ' ') + '
'; + } + } + } + function indentedLine(keyPair, singleLine) { + if (singleLine === void 0) { singleLine = false; } + var delimeter = (singleLine) ? ';' : ','; + return "  " + keyPair + delimeter + "
"; + } + var opts = ''; + opts += (options.startVal !== '0') ? indentedLine("startVal: " + options.startVal) : ''; + opts += (options.decimalPlaces !== '0') ? indentedLine("decimalPlaces: " + options.decimalPlaces) : ''; + opts += (options.duration !== 2) ? indentedLine("duration: " + options.duration) : ''; + opts += (options.useEasing) ? '' : indentedLine("useEasing: " + options.useEasing); + opts += (options.useEasing && options.easingFn) ? indentedLine("easingFn") : ''; + opts += (options.useGrouping) ? '' : indentedLine("useGrouping: " + options.useGrouping); + opts += (options.useIndianSeparators) ? indentedLine("useIndianSeparators: " + options.useIndianSeparators) : ''; + opts += (options.separator !== ',') ? indentedLine("separator: '" + options.separator + "'") : ''; + opts += (options.decimal !== '.') ? indentedLine("decimal: '" + options.decimal + "'") : ''; + opts += (options.prefix.length) ? indentedLine("prefix: '" + options.prefix + "'") : ''; + opts += (options.suffix.length) ? indentedLine("suffix: '" + options.suffix + "'") : ''; + opts += (options.numerals && options.numerals.length) ? + indentedLine("numerals: " + stringifyArray(options.numerals)) : ''; + opts += (options.onCompleteCallback) ? indentedLine("onCompleteCallback: methodToCallOnComplete") : ''; + + if (opts.length) { + code += "const options = {
" + opts + "};
"; + code += "let demo = new CountUp('myTargetElement', " + endVal + ", options);
"; + } + else { + code += "let demo = new CountUp('myTargetElement', " + endVal + ");
"; + } + code += 'if (!demo.error) {
'; + code += indentedLine('demo.start()', true); + code += '} else {
'; + code += indentedLine('console.error(demo.error)', true); + code += '}'; + codeVisualizer.innerHTML = code; + } + // get current star count + var repoInfoUrl = 'https://api.github.com/repos/inorganik/CountUp.js'; + var getStars = new XMLHttpRequest(); + getStars.open('GET', repoInfoUrl, true); + getStars.timeout = 5000; + getStars.onreadystatechange = function () { + // 2: received headers, 3: loading, 4: done + if (getStars.readyState === 4) { + if (getStars.status === 200) { + if (getStars.responseText !== 'undefined') { + if (getStars.responseText.length > 0) { + var data = JSON.parse(getStars.responseText); + stars = data.stargazers_count; + // change input values + el('endVal').value = stars; + createCountUp(); + } + } + } + } + }; + getStars.onerror = function () { + console.error('error getting stars:', getStars.status); + stars = getStars.status; + demo.start(); + }; + getStars.send(); +} diff --git a/node_modules/countup.js/dist/countUp.d.ts b/node_modules/countup.js/dist/countUp.d.ts new file mode 100644 index 0000000..628dc18 --- /dev/null +++ b/node_modules/countup.js/dist/countUp.d.ts @@ -0,0 +1,66 @@ +export interface CountUpOptions { + startVal?: number; + decimalPlaces?: number; + duration?: number; + useGrouping?: boolean; + useIndianSeparators?: boolean; + useEasing?: boolean; + smartEasingThreshold?: number; + smartEasingAmount?: number; + separator?: string; + decimal?: string; + easingFn?: (t: number, b: number, c: number, d: number) => number; + formattingFn?: (n: number) => string; + prefix?: string; + suffix?: string; + numerals?: string[]; + enableScrollSpy?: boolean; + scrollSpyDelay?: number; + scrollSpyOnce?: boolean; + onCompleteCallback?: () => any; + plugin?: CountUpPlugin; +} +export declare interface CountUpPlugin { + render(elem: HTMLElement, formatted: string): void; +} +export declare class CountUp { + private endVal; + options?: CountUpOptions; + version: string; + private defaults; + private rAF; + private startTime; + private remaining; + private finalEndVal; + private useEasing; + private countDown; + el: HTMLElement | HTMLInputElement; + formattingFn: (num: number) => string; + easingFn?: (t: number, b: number, c: number, d: number) => number; + error: string; + startVal: number; + duration: number; + paused: boolean; + frameVal: number; + once: boolean; + constructor(target: string | HTMLElement | HTMLInputElement, endVal: number, options?: CountUpOptions); + handleScroll(self: CountUp): void; + /** + * Smart easing works by breaking the animation into 2 parts, the second part being the + * smartEasingAmount and first part being the total amount minus the smartEasingAmount. It works + * by disabling easing for the first part and enabling it on the second part. It is used if + * usingEasing is true and the total animation amount exceeds the smartEasingThreshold. + */ + private determineDirectionAndSmartEasing; + start(callback?: (args?: any) => any): void; + pauseResume(): void; + reset(): void; + update(newEndVal: string | number): void; + count: (timestamp: number) => void; + printValue(val: number): void; + ensureNumber(n: any): boolean; + validateValue(value: string | number): number; + private resetDuration; + formatNumber: (num: number) => string; + easeOutExpo: (t: number, b: number, c: number, d: number) => number; +} diff --git a/node_modules/countup.js/dist/countUp.js b/node_modules/countup.js/dist/countUp.js new file mode 100644 index 0000000..539a7d5 --- /dev/null +++ b/node_modules/countup.js/dist/countUp.js @@ -0,0 +1,300 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +// playground: stackblitz.com/edit/countup-typescript +var CountUp = /** @class */ (function () { + function CountUp(target, endVal, options) { + var _this = this; + this.endVal = endVal; + this.options = options; + this.version = '2.6.0'; + this.defaults = { + startVal: 0, + decimalPlaces: 0, + duration: 2, + useEasing: true, + useGrouping: true, + useIndianSeparators: false, + smartEasingThreshold: 999, + smartEasingAmount: 333, + separator: ',', + decimal: '.', + prefix: '', + suffix: '', + enableScrollSpy: false, + scrollSpyDelay: 200, + scrollSpyOnce: false, + }; + this.finalEndVal = null; // for smart easing + this.useEasing = true; + this.countDown = false; + this.error = ''; + this.startVal = 0; + this.paused = true; + this.once = false; + this.count = function (timestamp) { + if (!_this.startTime) { + _this.startTime = timestamp; + } + var progress = timestamp - _this.startTime; + _this.remaining = _this.duration - progress; + // to ease or not to ease + if (_this.useEasing) { + if (_this.countDown) { + _this.frameVal = _this.startVal - _this.easingFn(progress, 0, _this.startVal - _this.endVal, _this.duration); + } + else { + _this.frameVal = _this.easingFn(progress, _this.startVal, _this.endVal - _this.startVal, _this.duration); + } + } + else { + _this.frameVal = _this.startVal + (_this.endVal - _this.startVal) * (progress / _this.duration); + } + // don't go past endVal since progress can exceed duration in the last frame + var wentPast = _this.countDown ? _this.frameVal < _this.endVal : _this.frameVal > _this.endVal; + _this.frameVal = wentPast ? _this.endVal : _this.frameVal; + // decimal + _this.frameVal = Number(_this.frameVal.toFixed(_this.options.decimalPlaces)); + // format and print value + _this.printValue(_this.frameVal); + // whether to continue + if (progress < _this.duration) { + _this.rAF = requestAnimationFrame(_this.count); + } + else if (_this.finalEndVal !== null) { + // smart easing + _this.update(_this.finalEndVal); + } + else { + if (_this.options.onCompleteCallback) { + _this.options.onCompleteCallback(); + } + } + }; + // default format and easing functions + this.formatNumber = function (num) { + var neg = (num < 0) ? '-' : ''; + var result, x1, x2, x3; + result = Math.abs(num).toFixed(_this.options.decimalPlaces); + result += ''; + var x = result.split('.'); + x1 = x[0]; + x2 = x.length > 1 ? _this.options.decimal + x[1] : ''; + if (_this.options.useGrouping) { + x3 = ''; + var factor = 3, j = 0; + for (var i = 0, len = x1.length; i < len; ++i) { + if (_this.options.useIndianSeparators && i === 4) { + factor = 2; + j = 1; + } + if (i !== 0 && (j % factor) === 0) { + x3 = _this.options.separator + x3; + } + j++; + x3 = x1[len - i - 1] + x3; + } + x1 = x3; + } + // optional numeral substitution + if (_this.options.numerals && _this.options.numerals.length) { + x1 = x1.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; }); + x2 = x2.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; }); + } + return neg + _this.options.prefix + x1 + x2 + _this.options.suffix; + }; + // t: current time, b: beginning value, c: change in value, d: duration + this.easeOutExpo = function (t, b, c, d) { + return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b; + }; + this.options = __assign(__assign({}, this.defaults), options); + this.formattingFn = (this.options.formattingFn) ? + this.options.formattingFn : this.formatNumber; + this.easingFn = (this.options.easingFn) ? + this.options.easingFn : this.easeOutExpo; + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.endVal = this.validateValue(endVal); + this.options.decimalPlaces = Math.max(0 || this.options.decimalPlaces); + this.resetDuration(); + this.options.separator = String(this.options.separator); + this.useEasing = this.options.useEasing; + if (this.options.separator === '') { + this.options.useGrouping = false; + } + this.el = (typeof target === 'string') ? document.getElementById(target) : target; + if (this.el) { + this.printValue(this.startVal); + } + else { + this.error = '[CountUp] target is null or undefined'; + } + // scroll spy + if (typeof window !== 'undefined' && this.options.enableScrollSpy) { + if (!this.error) { + // set up global array of onscroll functions to handle multiple instances + window['onScrollFns'] = window['onScrollFns'] || []; + window['onScrollFns'].push(function () { return _this.handleScroll(_this); }); + window.onscroll = function () { + window['onScrollFns'].forEach(function (fn) { return fn(); }); + }; + this.handleScroll(this); + } + else { + console.error(this.error, target); + } + } + } + CountUp.prototype.handleScroll = function (self) { + if (!self || !window || self.once) + return; + var bottomOfScroll = window.innerHeight + window.scrollY; + var rect = self.el.getBoundingClientRect(); + var topOfEl = rect.top + window.pageYOffset; + var bottomOfEl = rect.top + rect.height + window.pageYOffset; + if (bottomOfEl < bottomOfScroll && bottomOfEl > window.scrollY && self.paused) { + // in view + self.paused = false; + setTimeout(function () { return self.start(); }, self.options.scrollSpyDelay); + if (self.options.scrollSpyOnce) + self.once = true; + } + else if ((window.scrollY > bottomOfEl || topOfEl > bottomOfScroll) && + !self.paused) { + // out of view + self.reset(); + } + }; + /** + * Smart easing works by breaking the animation into 2 parts, the second part being the + * smartEasingAmount and first part being the total amount minus the smartEasingAmount. It works + * by disabling easing for the first part and enabling it on the second part. It is used if + * usingEasing is true and the total animation amount exceeds the smartEasingThreshold. + */ + CountUp.prototype.determineDirectionAndSmartEasing = function () { + var end = (this.finalEndVal) ? this.finalEndVal : this.endVal; + this.countDown = (this.startVal > end); + var animateAmount = end - this.startVal; + if (Math.abs(animateAmount) > this.options.smartEasingThreshold && this.options.useEasing) { + this.finalEndVal = end; + var up = (this.countDown) ? 1 : -1; + this.endVal = end + (up * this.options.smartEasingAmount); + this.duration = this.duration / 2; + } + else { + this.endVal = end; + this.finalEndVal = null; + } + if (this.finalEndVal !== null) { + // setting finalEndVal indicates smart easing + this.useEasing = false; + } + else { + this.useEasing = this.options.useEasing; + } + }; + // start animation + CountUp.prototype.start = function (callback) { + if (this.error) { + return; + } + if (callback) { + this.options.onCompleteCallback = callback; + } + if (this.duration > 0) { + this.determineDirectionAndSmartEasing(); + this.paused = false; + this.rAF = requestAnimationFrame(this.count); + } + else { + this.printValue(this.endVal); + } + }; + // pause/resume animation + CountUp.prototype.pauseResume = function () { + if (!this.paused) { + cancelAnimationFrame(this.rAF); + } + else { + this.startTime = null; + this.duration = this.remaining; + this.startVal = this.frameVal; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + } + this.paused = !this.paused; + }; + // reset to startVal so animation can be run again + CountUp.prototype.reset = function () { + cancelAnimationFrame(this.rAF); + this.paused = true; + this.resetDuration(); + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.printValue(this.startVal); + }; + // pass a new endVal and start animation + CountUp.prototype.update = function (newEndVal) { + cancelAnimationFrame(this.rAF); + this.startTime = null; + this.endVal = this.validateValue(newEndVal); + if (this.endVal === this.frameVal) { + return; + } + this.startVal = this.frameVal; + if (this.finalEndVal == null) { + this.resetDuration(); + } + this.finalEndVal = null; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + }; + CountUp.prototype.printValue = function (val) { + var _a; + if (!this.el) + return; + var result = this.formattingFn(val); + if ((_a = this.options.plugin) === null || _a === void 0 ? void 0 : _a.render) { + this.options.plugin.render(this.el, result); + return; + } + if (this.el.tagName === 'INPUT') { + var input = this.el; + input.value = result; + } + else if (this.el.tagName === 'text' || this.el.tagName === 'tspan') { + this.el.textContent = result; + } + else { + this.el.innerHTML = result; + } + }; + CountUp.prototype.ensureNumber = function (n) { + return (typeof n === 'number' && !isNaN(n)); + }; + CountUp.prototype.validateValue = function (value) { + var newValue = Number(value); + if (!this.ensureNumber(newValue)) { + this.error = "[CountUp] invalid start or end value: ".concat(value); + return null; + } + else { + return newValue; + } + }; + CountUp.prototype.resetDuration = function () { + this.startTime = null; + this.duration = Number(this.options.duration) * 1000; + this.remaining = this.duration; + }; + return CountUp; +}()); +export { CountUp }; diff --git a/node_modules/countup.js/dist/countUp.min.js b/node_modules/countup.js/dist/countUp.min.js new file mode 100644 index 0000000..5a013b4 --- /dev/null +++ b/node_modules/countup.js/dist/countUp.min.js @@ -0,0 +1 @@ +var __assign=this&&this.__assign||function(){return(__assign=Object.assign||function(t){for(var i,n=1,s=arguments.length;ns.endVal;s.frameVal=n?s.endVal:s.frameVal,s.frameVal=Number(s.frameVal.toFixed(s.options.decimalPlaces)),s.printValue(s.frameVal),i1?s.options.decimal+r[1]:"",s.options.useGrouping){e="";for(var l=3,h=0,u=0,p=n.length;uwindow.scrollY&&t.paused?(t.paused=!1,setTimeout(function(){return t.start()},t.options.scrollSpyDelay),t.options.scrollSpyOnce&&(t.once=!0)):(window.scrollY>a||s>i)&&!t.paused&&t.reset()}},t.prototype.determineDirectionAndSmartEasing=function(){var t=this.finalEndVal?this.finalEndVal:this.endVal;this.countDown=this.startVal>t;var i=t-this.startVal;if(Math.abs(i)>this.options.smartEasingThreshold&&this.options.useEasing){this.finalEndVal=t;var n=this.countDown?1:-1;this.endVal=t+n*this.options.smartEasingAmount,this.duration=this.duration/2}else this.endVal=t,this.finalEndVal=null;null!==this.finalEndVal?this.useEasing=!1:this.useEasing=this.options.useEasing},t.prototype.start=function(t){this.error||(t&&(this.options.onCompleteCallback=t),this.duration>0?(this.determineDirectionAndSmartEasing(),this.paused=!1,this.rAF=requestAnimationFrame(this.count)):this.printValue(this.endVal))},t.prototype.pauseResume=function(){this.paused?(this.startTime=null,this.duration=this.remaining,this.startVal=this.frameVal,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count)):cancelAnimationFrame(this.rAF),this.paused=!this.paused},t.prototype.reset=function(){cancelAnimationFrame(this.rAF),this.paused=!0,this.resetDuration(),this.startVal=this.validateValue(this.options.startVal),this.frameVal=this.startVal,this.printValue(this.startVal)},t.prototype.update=function(t){cancelAnimationFrame(this.rAF),this.startTime=null,this.endVal=this.validateValue(t),this.endVal!==this.frameVal&&(this.startVal=this.frameVal,null==this.finalEndVal&&this.resetDuration(),this.finalEndVal=null,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count))},t.prototype.printValue=function(t){var i;if(this.el){var n=this.formattingFn(t);if(null!==(i=this.options.plugin)&&void 0!==i&&i.render)this.options.plugin.render(this.el,n);else if("INPUT"===this.el.tagName)this.el.value=n;else"text"===this.el.tagName||"tspan"===this.el.tagName?this.el.textContent=n:this.el.innerHTML=n}},t.prototype.ensureNumber=function(t){return"number"==typeof t&&!isNaN(t)},t.prototype.validateValue=function(t){var i=Number(t);return this.ensureNumber(i)?i:(this.error="[CountUp] invalid start or end value: ".concat(t),null)},t.prototype.resetDuration=function(){this.startTime=null,this.duration=1e3*Number(this.options.duration),this.remaining=this.duration},t}();export{CountUp}; \ No newline at end of file diff --git a/node_modules/countup.js/dist/countUp.umd.js b/node_modules/countup.js/dist/countUp.umd.js new file mode 100644 index 0000000..9fdde5a --- /dev/null +++ b/node_modules/countup.js/dist/countUp.umd.js @@ -0,0 +1,311 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.countUp = {})); +})(this, (function (exports) { 'use strict'; + + var __assign = (undefined && undefined.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + // playground: stackblitz.com/edit/countup-typescript + var CountUp = /** @class */ (function () { + function CountUp(target, endVal, options) { + var _this = this; + this.endVal = endVal; + this.options = options; + this.version = '2.6.0'; + this.defaults = { + startVal: 0, + decimalPlaces: 0, + duration: 2, + useEasing: true, + useGrouping: true, + useIndianSeparators: false, + smartEasingThreshold: 999, + smartEasingAmount: 333, + separator: ',', + decimal: '.', + prefix: '', + suffix: '', + enableScrollSpy: false, + scrollSpyDelay: 200, + scrollSpyOnce: false, + }; + this.finalEndVal = null; // for smart easing + this.useEasing = true; + this.countDown = false; + this.error = ''; + this.startVal = 0; + this.paused = true; + this.once = false; + this.count = function (timestamp) { + if (!_this.startTime) { + _this.startTime = timestamp; + } + var progress = timestamp - _this.startTime; + _this.remaining = _this.duration - progress; + // to ease or not to ease + if (_this.useEasing) { + if (_this.countDown) { + _this.frameVal = _this.startVal - _this.easingFn(progress, 0, _this.startVal - _this.endVal, _this.duration); + } + else { + _this.frameVal = _this.easingFn(progress, _this.startVal, _this.endVal - _this.startVal, _this.duration); + } + } + else { + _this.frameVal = _this.startVal + (_this.endVal - _this.startVal) * (progress / _this.duration); + } + // don't go past endVal since progress can exceed duration in the last frame + var wentPast = _this.countDown ? _this.frameVal < _this.endVal : _this.frameVal > _this.endVal; + _this.frameVal = wentPast ? _this.endVal : _this.frameVal; + // decimal + _this.frameVal = Number(_this.frameVal.toFixed(_this.options.decimalPlaces)); + // format and print value + _this.printValue(_this.frameVal); + // whether to continue + if (progress < _this.duration) { + _this.rAF = requestAnimationFrame(_this.count); + } + else if (_this.finalEndVal !== null) { + // smart easing + _this.update(_this.finalEndVal); + } + else { + if (_this.options.onCompleteCallback) { + _this.options.onCompleteCallback(); + } + } + }; + // default format and easing functions + this.formatNumber = function (num) { + var neg = (num < 0) ? '-' : ''; + var result, x1, x2, x3; + result = Math.abs(num).toFixed(_this.options.decimalPlaces); + result += ''; + var x = result.split('.'); + x1 = x[0]; + x2 = x.length > 1 ? _this.options.decimal + x[1] : ''; + if (_this.options.useGrouping) { + x3 = ''; + var factor = 3, j = 0; + for (var i = 0, len = x1.length; i < len; ++i) { + if (_this.options.useIndianSeparators && i === 4) { + factor = 2; + j = 1; + } + if (i !== 0 && (j % factor) === 0) { + x3 = _this.options.separator + x3; + } + j++; + x3 = x1[len - i - 1] + x3; + } + x1 = x3; + } + // optional numeral substitution + if (_this.options.numerals && _this.options.numerals.length) { + x1 = x1.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; }); + x2 = x2.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; }); + } + return neg + _this.options.prefix + x1 + x2 + _this.options.suffix; + }; + // t: current time, b: beginning value, c: change in value, d: duration + this.easeOutExpo = function (t, b, c, d) { + return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b; + }; + this.options = __assign(__assign({}, this.defaults), options); + this.formattingFn = (this.options.formattingFn) ? + this.options.formattingFn : this.formatNumber; + this.easingFn = (this.options.easingFn) ? + this.options.easingFn : this.easeOutExpo; + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.endVal = this.validateValue(endVal); + this.options.decimalPlaces = Math.max(this.options.decimalPlaces); + this.resetDuration(); + this.options.separator = String(this.options.separator); + this.useEasing = this.options.useEasing; + if (this.options.separator === '') { + this.options.useGrouping = false; + } + this.el = (typeof target === 'string') ? document.getElementById(target) : target; + if (this.el) { + this.printValue(this.startVal); + } + else { + this.error = '[CountUp] target is null or undefined'; + } + // scroll spy + if (typeof window !== 'undefined' && this.options.enableScrollSpy) { + if (!this.error) { + // set up global array of onscroll functions to handle multiple instances + window['onScrollFns'] = window['onScrollFns'] || []; + window['onScrollFns'].push(function () { return _this.handleScroll(_this); }); + window.onscroll = function () { + window['onScrollFns'].forEach(function (fn) { return fn(); }); + }; + this.handleScroll(this); + } + else { + console.error(this.error, target); + } + } + } + CountUp.prototype.handleScroll = function (self) { + if (!self || !window || self.once) + return; + var bottomOfScroll = window.innerHeight + window.scrollY; + var rect = self.el.getBoundingClientRect(); + var topOfEl = rect.top + window.pageYOffset; + var bottomOfEl = rect.top + rect.height + window.pageYOffset; + if (bottomOfEl < bottomOfScroll && bottomOfEl > window.scrollY && self.paused) { + // in view + self.paused = false; + setTimeout(function () { return self.start(); }, self.options.scrollSpyDelay); + if (self.options.scrollSpyOnce) + self.once = true; + } + else if ((window.scrollY > bottomOfEl || topOfEl > bottomOfScroll) && + !self.paused) { + // out of view + self.reset(); + } + }; + /** + * Smart easing works by breaking the animation into 2 parts, the second part being the + * smartEasingAmount and first part being the total amount minus the smartEasingAmount. It works + * by disabling easing for the first part and enabling it on the second part. It is used if + * usingEasing is true and the total animation amount exceeds the smartEasingThreshold. + */ + CountUp.prototype.determineDirectionAndSmartEasing = function () { + var end = (this.finalEndVal) ? this.finalEndVal : this.endVal; + this.countDown = (this.startVal > end); + var animateAmount = end - this.startVal; + if (Math.abs(animateAmount) > this.options.smartEasingThreshold && this.options.useEasing) { + this.finalEndVal = end; + var up = (this.countDown) ? 1 : -1; + this.endVal = end + (up * this.options.smartEasingAmount); + this.duration = this.duration / 2; + } + else { + this.endVal = end; + this.finalEndVal = null; + } + if (this.finalEndVal !== null) { + // setting finalEndVal indicates smart easing + this.useEasing = false; + } + else { + this.useEasing = this.options.useEasing; + } + }; + // start animation + CountUp.prototype.start = function (callback) { + if (this.error) { + return; + } + if (callback) { + this.options.onCompleteCallback = callback; + } + if (this.duration > 0) { + this.determineDirectionAndSmartEasing(); + this.paused = false; + this.rAF = requestAnimationFrame(this.count); + } + else { + this.printValue(this.endVal); + } + }; + // pause/resume animation + CountUp.prototype.pauseResume = function () { + if (!this.paused) { + cancelAnimationFrame(this.rAF); + } + else { + this.startTime = null; + this.duration = this.remaining; + this.startVal = this.frameVal; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + } + this.paused = !this.paused; + }; + // reset to startVal so animation can be run again + CountUp.prototype.reset = function () { + cancelAnimationFrame(this.rAF); + this.paused = true; + this.resetDuration(); + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.printValue(this.startVal); + }; + // pass a new endVal and start animation + CountUp.prototype.update = function (newEndVal) { + cancelAnimationFrame(this.rAF); + this.startTime = null; + this.endVal = this.validateValue(newEndVal); + if (this.endVal === this.frameVal) { + return; + } + this.startVal = this.frameVal; + if (this.finalEndVal == null) { + this.resetDuration(); + } + this.finalEndVal = null; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + }; + CountUp.prototype.printValue = function (val) { + var _a; + if (!this.el) + return; + var result = this.formattingFn(val); + if ((_a = this.options.plugin) === null || _a === void 0 ? void 0 : _a.render) { + this.options.plugin.render(this.el, result); + return; + } + if (this.el.tagName === 'INPUT') { + var input = this.el; + input.value = result; + } + else if (this.el.tagName === 'text' || this.el.tagName === 'tspan') { + this.el.textContent = result; + } + else { + this.el.innerHTML = result; + } + }; + CountUp.prototype.ensureNumber = function (n) { + return (typeof n === 'number' && !isNaN(n)); + }; + CountUp.prototype.validateValue = function (value) { + var newValue = Number(value); + if (!this.ensureNumber(newValue)) { + this.error = "[CountUp] invalid start or end value: ".concat(value); + return null; + } + else { + return newValue; + } + }; + CountUp.prototype.resetDuration = function () { + this.startTime = null; + this.duration = Number(this.options.duration) * 1000; + this.remaining = this.duration; + }; + return CountUp; + }()); + + exports.CountUp = CountUp; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/node_modules/countup.js/dist/countUp.withPolyfill.min.js b/node_modules/countup.js/dist/countUp.withPolyfill.min.js new file mode 100644 index 0000000..d9a7d72 --- /dev/null +++ b/node_modules/countup.js/dist/countUp.withPolyfill.min.js @@ -0,0 +1 @@ +!function(){for(var t=0,i=["webkit","moz","ms","o"],n=0;na.endVal;a.frameVal=n?a.endVal:a.frameVal,a.frameVal=Number(a.frameVal.toFixed(a.options.decimalPlaces)),a.printValue(a.frameVal),i1?a.options.decimal+r[1]:"",a.options.useGrouping){e="";for(var l=3,u=0,h=0,p=n.length;hwindow.scrollY&&t.paused?(t.paused=!1,setTimeout(function(){return t.start()},t.options.scrollSpyDelay),t.options.scrollSpyOnce&&(t.once=!0)):(window.scrollY>s||a>i)&&!t.paused&&t.reset()}},t.prototype.determineDirectionAndSmartEasing=function(){var t=this.finalEndVal?this.finalEndVal:this.endVal;this.countDown=this.startVal>t;var i=t-this.startVal;if(Math.abs(i)>this.options.smartEasingThreshold&&this.options.useEasing){this.finalEndVal=t;var n=this.countDown?1:-1;this.endVal=t+n*this.options.smartEasingAmount,this.duration=this.duration/2}else this.endVal=t,this.finalEndVal=null;null!==this.finalEndVal?this.useEasing=!1:this.useEasing=this.options.useEasing},t.prototype.start=function(t){this.error||(t&&(this.options.onCompleteCallback=t),this.duration>0?(this.determineDirectionAndSmartEasing(),this.paused=!1,this.rAF=requestAnimationFrame(this.count)):this.printValue(this.endVal))},t.prototype.pauseResume=function(){this.paused?(this.startTime=null,this.duration=this.remaining,this.startVal=this.frameVal,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count)):cancelAnimationFrame(this.rAF),this.paused=!this.paused},t.prototype.reset=function(){cancelAnimationFrame(this.rAF),this.paused=!0,this.resetDuration(),this.startVal=this.validateValue(this.options.startVal),this.frameVal=this.startVal,this.printValue(this.startVal)},t.prototype.update=function(t){cancelAnimationFrame(this.rAF),this.startTime=null,this.endVal=this.validateValue(t),this.endVal!==this.frameVal&&(this.startVal=this.frameVal,null==this.finalEndVal&&this.resetDuration(),this.finalEndVal=null,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count))},t.prototype.printValue=function(t){var i;if(this.el){var n=this.formattingFn(t);if(null!==(i=this.options.plugin)&&void 0!==i&&i.render)this.options.plugin.render(this.el,n);else if("INPUT"===this.el.tagName)this.el.value=n;else"text"===this.el.tagName||"tspan"===this.el.tagName?this.el.textContent=n:this.el.innerHTML=n}},t.prototype.ensureNumber=function(t){return"number"==typeof t&&!isNaN(t)},t.prototype.validateValue=function(t){var i=Number(t);return this.ensureNumber(i)?i:(this.error="[CountUp] invalid start or end value: ".concat(t),null)},t.prototype.resetDuration=function(){this.startTime=null,this.duration=1e3*Number(this.options.duration),this.remaining=this.duration},t}();export{CountUp}; \ No newline at end of file diff --git a/node_modules/countup.js/gulpfile.js b/node_modules/countup.js/gulpfile.js new file mode 100644 index 0000000..8829b33 --- /dev/null +++ b/node_modules/countup.js/gulpfile.js @@ -0,0 +1,32 @@ +const gulp = require('gulp'); +const uglifyES = require('uglify-es'); +const composer = require('gulp-uglify/composer'); +const concat = require('gulp-concat'); +const del = require('del'); +const uglify = composer(uglifyES, console); + +const clean = () => del(['dist/*']); + +const buildNormal = () => { + return gulp.src('./dist/countUp.js') + .pipe(concat('countUp.min.js')) + .pipe(uglify()) + .pipe(gulp.dest('dist')); +} + +const buildLegacy = () => { + return gulp.src([ + './requestAnimationFrame.polyfill.js', + './dist/countUp.js' + ]) + .pipe(concat('countUp.withPolyfill.min.js')) + .pipe(uglify()) + .pipe(gulp.dest('dist')); +} + +gulp.task('clean', clean); +const build = gulp.series(buildNormal, buildLegacy); +gulp.task('build', build); + +exports.clean = clean; +exports.default = build; diff --git a/node_modules/countup.js/index.html b/node_modules/countup.js/index.html new file mode 100644 index 0000000..24df4b8 --- /dev/null +++ b/node_modules/countup.js/index.html @@ -0,0 +1,155 @@ + + + + + + + + + + + CountUp.js + + + + + + + + + + + + + + Fork me on GitHub +
+
+
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ +
+
+
+

CountUp.js  

+
+
+

CountUp.js is a dependency-free, lightweight JavaScript class that can be used to quickly create animations that display numerical data in a more interesting way.

+

Install via npm/yarn using the package name  countup.js.

+

View on Github

+
+
+
+

Current stars:

+

0

+
+ +
+
+

Params:

+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+
+
+
+

Options:

+
+ +
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+

Methods:

+ + + + +
+ +
+
+ +
+
+
+
+
+

Custom:

+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + diff --git a/node_modules/countup.js/jest.config.js b/node_modules/countup.js/jest.config.js new file mode 100644 index 0000000..824478e --- /dev/null +++ b/node_modules/countup.js/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + roots: [ + '/src' + ], + transform: { + '^.+\\.ts?$': 'ts-jest' + }, + testRegex: '(\\.|/)(test|spec)\\.ts?$', + testEnvironment: 'jsdom', + moduleFileExtensions: [ + 'ts', + 'js', + 'jsx', + 'json', + 'node' + ] +} diff --git a/node_modules/countup.js/package.json b/node_modules/countup.js/package.json new file mode 100644 index 0000000..91136e4 --- /dev/null +++ b/node_modules/countup.js/package.json @@ -0,0 +1,44 @@ +{ + "name": "countup.js", + "description": "Animates a numerical value by counting to it", + "version": "2.6.0", + "license": "MIT", + "main": "./dist/countUp.umd.js", + "module": "./dist/countUp.min.js", + "author": "@inorganik", + "repository": { + "type": "git", + "url": "git+https://github.com/inorganik/countUp.js.git" + }, + "scripts": { + "build": "npm run clean && tsc && gulp && npm run build:umd", + "build:umd": "rollup dist/countUp.js --format umd --file dist/countUp.umd.js --name countUp", + "clean": "gulp clean", + "lint": "eslint -c .eslintrc.js --ext .ts ./src", + "serve": "http-server -o -c-1 ./", + "start": "npm run build && npm run serve", + "test": "jest", + "test:watch": "jest --watch" + }, + "devDependencies": { + "@types/jest": "^28.1.8", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/eslint-plugin-tslint": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", + "browserify": "^17.0.0", + "del": "^6.1.1", + "eslint": "^8.26.0", + "eslint-plugin-import": "^2.26.0", + "gulp": "^4.0.2", + "gulp-concat": "^2.6.1", + "gulp-uglify": "^3.0.2", + "http-server": "^14.1.1", + "jest": "^28.1.3", + "jest-environment-jsdom": "^28.1.3", + "rollup": "^2.79.1", + "ts-jest": "^28.0.8", + "typescript": "^4.8.4", + "uglify-es": "^3.3.9" + }, + "types": "./dist/countUp.d.ts" +} diff --git a/node_modules/countup.js/requestAnimationFrame.polyfill.js b/node_modules/countup.js/requestAnimationFrame.polyfill.js new file mode 100644 index 0000000..465abb2 --- /dev/null +++ b/node_modules/countup.js/requestAnimationFrame.polyfill.js @@ -0,0 +1,26 @@ +// make sure requestAnimationFrame and cancelAnimationFrame are defined +// polyfill for browsers without native support +// by Opera engineer Erik Möller +(function () { + var lastTime = 0; + var vendors = ['webkit', 'moz', 'ms', 'o']; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || + window[vendors[x] + 'CancelRequestAnimationFrame']; + } + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function (callback) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { return callback(currTime + timeToCall); }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + } +})(); diff --git a/node_modules/countup.js/src/countUp.spec.ts b/node_modules/countup.js/src/countUp.spec.ts new file mode 100644 index 0000000..01bb99d --- /dev/null +++ b/node_modules/countup.js/src/countUp.spec.ts @@ -0,0 +1,281 @@ +import { CountUp, CountUpPlugin } from './countUp'; + +describe('CountUp', () => { + + let countUp; + let time; + + const getTargetHtml = () => document.getElementById('target')?.innerHTML; + const resetRAF = () => { + time = 0; + jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => { + time += 100; + if (time < 2500) { + return cb(time) as any; + } + }); + }; + + beforeEach(() => { + document.body.innerHTML = + '
' + + '

' + + '
'; + + countUp = new CountUp('target', 100); + resetRAF(); + }); + + describe('constructor', () => { + + it('should create for a valid target, and print startVal', () => { + expect(countUp).toBeTruthy(); + expect(countUp.error.length).toBe(0); + expect(getTargetHtml()).toEqual('0'); + }); + + it('should set an error for a bad target', () => { + countUp = new CountUp('notThere', 100); + + expect(countUp.error.length).toBeGreaterThan(0); + }); + + it('should set an error for a bad endVal', () => { + const endVal = '%' as any; + countUp = new CountUp('target', endVal); + + expect(countUp.error.length).toBeGreaterThan(0); + }); + + it('should set an error for a bad startVal', () => { + const startVal = 'oops' as any; + countUp = new CountUp('target', 100, { startVal }); + + expect(countUp.error.length).toBeGreaterThan(0); + }); + + it('should return a value for version', () => { + expect(countUp.version).toBeTruthy(); + }); + }); + + describe('class methods', () => { + it('should count when start method is called', () => { + countUp.start(); + + expect(getTargetHtml()).toEqual('100'); + }); + + it('should use a callback provided to start', () => { + const cb = jest.fn(); + countUp.start(cb); + + expect(getTargetHtml()).toEqual('100'); + expect(cb).toHaveBeenCalled(); + }); + + it('should pause when pauseResume is called', () => { + countUp.start(); + // resetRAF(); + countUp.pauseResume(); + + expect(countUp.paused).toBeTruthy(); + }); + + it('should reset when reset is called', () => { + countUp.start(); + countUp.reset(); + + expect(getTargetHtml()).toEqual('0'); + expect(countUp.paused).toBeTruthy(); + }); + + it('should update when update is called', () => { + countUp.start(); + expect(getTargetHtml()).toEqual('100'); + + resetRAF(); + countUp.update(200); + expect(getTargetHtml()).toEqual('200'); + }); + + }); + + describe('various use-cases', () => { + it('should handle large numbers', () => { + countUp = new CountUp('target', 6000); + const spy = jest.spyOn(countUp, 'determineDirectionAndSmartEasing'); + countUp.start(); + + expect(getTargetHtml()).toEqual('6,000'); + expect(spy).toHaveBeenCalled(); + }); + + it('should not use easing when specified with a large number (auto-smooth)', () => { + countUp = new CountUp('target', 6000, { useEasing: false }); + const spy = jest.spyOn(countUp, 'easingFn'); + countUp.start(); + + expect(getTargetHtml()).toEqual('6,000'); + expect(spy).toHaveBeenCalledTimes(0); + }); + + it('should count down when endVal is less than startVal', () => { + countUp = new CountUp('target', 10, { startVal: 500 }); + expect(getTargetHtml()).toEqual('500'); + countUp.start(); + + expect(getTargetHtml()).toEqual('10'); + }); + + it('should handle negative numbers', () => { + countUp = new CountUp('target', -500); + countUp.start(); + + expect(getTargetHtml()).toEqual('-500'); + }); + + it('should properly handle a zero duration', () => { + countUp = new CountUp('target', 2000, { duration: 0 }); + countUp.start(); + + expect(getTargetHtml()).toEqual('2,000'); + }); + + it('should call the callback when finished if there is one', () => { + const cb = jest.fn(); + countUp.start(cb); + + expect(getTargetHtml()).toEqual('100'); + expect(cb).toHaveBeenCalled(); + }); + }); + + describe('options', () => { + it('should respect the decimalPlaces option', () => { + countUp = new CountUp('target', 100, { decimalPlaces: 2 }); + countUp.start(); + + expect(getTargetHtml()).toEqual('100.00'); + }); + + it('should respect the duration option', () => { + countUp = new CountUp('target', 100, { duration: 1 }); + countUp.start(); + + expect(getTargetHtml()).toEqual('100'); + }); + + it('should respect the useEasing option', () => { + countUp = new CountUp('target', 100, { useEasing: false }); + countUp.start(); + + expect(getTargetHtml()).toEqual('100'); + }); + + it('should respect the useGrouping option', () => { + countUp = new CountUp('target', 100000, { useGrouping: false }); + countUp.start(); + + expect(getTargetHtml()).toEqual('100000'); + resetRAF(); + + countUp = new CountUp('target', 1000000, { useGrouping: true }); + countUp.start(); + + expect(getTargetHtml()).toEqual('1,000,000'); + }); + + it('should respect the useIndianSeparators option', () => { + countUp = new CountUp('target', 100000, { useIndianSeparators: true }); + countUp.start(); + + expect(getTargetHtml()).toEqual('1,00,000'); + resetRAF(); + + countUp = new CountUp('target', 10000000, { useIndianSeparators: true }); + countUp.start(); + + expect(getTargetHtml()).toEqual('1,00,00,000'); + }); + + it('should respect the separator option', () => { + countUp = new CountUp('target', 10000, { separator: ':' }); + countUp.start(); + + expect(getTargetHtml()).toEqual('10:000'); + }); + + it('should respect the decimal option', () => { + countUp = new CountUp('target', 100, { decimal: ',', decimalPlaces: 1 }); + countUp.start(); + + expect(getTargetHtml()).toEqual('100,0'); + }); + + it('should respect the easingFn option', () => { + const easeOutQuintic = jest.fn().mockReturnValue(100); + countUp = new CountUp('target', 100, { easingFn: easeOutQuintic }); + countUp.start(); + + expect(easeOutQuintic).toHaveBeenCalled(); + expect(getTargetHtml()).toEqual('100'); + }); + + it('should respect the formattingFn option', () => { + const formatter = jest.fn().mockReturnValue('~100~'); + countUp = new CountUp('target', 100, { formattingFn: formatter }); + countUp.start(); + + expect(formatter).toHaveBeenCalled(); + expect(getTargetHtml()).toEqual('~100~'); + }); + + it('should respect the prefix option', () => { + countUp = new CountUp('target', 100, { prefix: '$' }); + countUp.start(); + + expect(getTargetHtml()).toEqual('$100'); + }); + + it('should respect the suffix option', () => { + countUp = new CountUp('target', 100, { suffix: '!' }); + countUp.start(); + + expect(getTargetHtml()).toEqual('100!'); + }); + + it('should respect the numerals option', () => { + const numerals = [')', '!', '@', '#', '$', '%', '^', '&', '*', '(']; + countUp = new CountUp('target', 100, { numerals }); + countUp.start(); + + expect(getTargetHtml()).toEqual('!))'); + }); + + it('should respect the onCompleteCallback option', () => { + const options = { onCompleteCallback: jest.fn() }; + const callbackSpy = jest.spyOn(options, 'onCompleteCallback'); + countUp = new CountUp('target', 100, options); + countUp.start(); + + expect(getTargetHtml()).toEqual('100'); + expect(callbackSpy).toHaveBeenCalled(); + }); + + it('should respect the plugin option', () => { + const plugin: CountUpPlugin = { + render: (el, result) => { + el.innerHTML = result; + } + }; + countUp = new CountUp('target', 1000, { + plugin, + useGrouping: true + }); + countUp.start(); + + expect(getTargetHtml()).toEqual('1,000'); + }); + }); +}); diff --git a/node_modules/countup.js/src/countUp.ts b/node_modules/countup.js/src/countUp.ts new file mode 100644 index 0000000..45372f3 --- /dev/null +++ b/node_modules/countup.js/src/countUp.ts @@ -0,0 +1,334 @@ +export interface CountUpOptions { // (default) + startVal?: number; // number to start at (0) + decimalPlaces?: number; // number of decimal places (0) + duration?: number; // animation duration in seconds (2) + useGrouping?: boolean; // example: 1,000 vs 1000 (true) + useIndianSeparators?: boolean; // example: 1,00,000 vs 100,000 (false) + useEasing?: boolean; // ease animation (true) + smartEasingThreshold?: number; // smooth easing for large numbers above this if useEasing (999) + smartEasingAmount?: number; // amount to be eased for numbers above threshold (333) + separator?: string; // grouping separator (,) + decimal?: string; // decimal (.) + // easingFn: easing function for animation (easeOutExpo) + easingFn?: (t: number, b: number, c: number, d: number) => number; + formattingFn?: (n: number) => string; // this function formats result + prefix?: string; // text prepended to result + suffix?: string; // text appended to result + numerals?: string[]; // numeral glyph substitution + enableScrollSpy?: boolean; // start animation when target is in view + scrollSpyDelay?: number; // delay (ms) after target comes into view + scrollSpyOnce?: boolean; // run only once + onCompleteCallback?: () => any; // gets called when animation completes + plugin?: CountUpPlugin; // for alternate animations +} + +export declare interface CountUpPlugin { + render(elem: HTMLElement, formatted: string): void; +} + +// playground: stackblitz.com/edit/countup-typescript +export class CountUp { + + version = '2.6.0'; + private defaults: CountUpOptions = { + startVal: 0, + decimalPlaces: 0, + duration: 2, + useEasing: true, + useGrouping: true, + useIndianSeparators: false, + smartEasingThreshold: 999, + smartEasingAmount: 333, + separator: ',', + decimal: '.', + prefix: '', + suffix: '', + enableScrollSpy: false, + scrollSpyDelay: 200, + scrollSpyOnce: false, + }; + private rAF: any; + private startTime: number; + private remaining: number; + private finalEndVal: number = null; // for smart easing + private useEasing = true; + private countDown = false; + el: HTMLElement | HTMLInputElement; + formattingFn: (num: number) => string; + easingFn?: (t: number, b: number, c: number, d: number) => number; + error = ''; + startVal = 0; + duration: number; + paused = true; + frameVal: number; + once = false; + + constructor( + target: string | HTMLElement | HTMLInputElement, + private endVal: number, + public options?: CountUpOptions + ) { + this.options = { + ...this.defaults, + ...options + }; + this.formattingFn = (this.options.formattingFn) ? + this.options.formattingFn : this.formatNumber; + this.easingFn = (this.options.easingFn) ? + this.options.easingFn : this.easeOutExpo; + + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.endVal = this.validateValue(endVal); + this.options.decimalPlaces = Math.max(0 || this.options.decimalPlaces); + this.resetDuration(); + this.options.separator = String(this.options.separator); + this.useEasing = this.options.useEasing; + if (this.options.separator === '') { + this.options.useGrouping = false; + } + this.el = (typeof target === 'string') ? document.getElementById(target) : target; + if (this.el) { + this.printValue(this.startVal); + } else { + this.error = '[CountUp] target is null or undefined'; + } + + // scroll spy + if (typeof window !== 'undefined' && this.options.enableScrollSpy) { + if (!this.error) { + // set up global array of onscroll functions to handle multiple instances + window['onScrollFns'] = window['onScrollFns'] || []; + window['onScrollFns'].push(() => this.handleScroll(this)); + window.onscroll = () => { + window['onScrollFns'].forEach((fn) => fn()); + }; + this.handleScroll(this); + } else { + console.error(this.error, target); + } + } + } + + handleScroll(self: CountUp): void { + if (!self || !window || self.once) return; + const bottomOfScroll = window.innerHeight + window.scrollY; + const rect = self.el.getBoundingClientRect(); + const topOfEl = rect.top + window.pageYOffset + const bottomOfEl = rect.top + rect.height + window.pageYOffset; + if (bottomOfEl < bottomOfScroll && bottomOfEl > window.scrollY && self.paused) { + // in view + self.paused = false; + setTimeout(() => self.start(), self.options.scrollSpyDelay); + if (self.options.scrollSpyOnce) + self.once = true; + } else if ( + (window.scrollY > bottomOfEl || topOfEl > bottomOfScroll) && + !self.paused + ) { + // out of view + self.reset(); + } + } + + /** + * Smart easing works by breaking the animation into 2 parts, the second part being the + * smartEasingAmount and first part being the total amount minus the smartEasingAmount. It works + * by disabling easing for the first part and enabling it on the second part. It is used if + * usingEasing is true and the total animation amount exceeds the smartEasingThreshold. + */ + private determineDirectionAndSmartEasing(): void { + const end = (this.finalEndVal) ? this.finalEndVal : this.endVal; + this.countDown = (this.startVal > end); + const animateAmount = end - this.startVal; + if (Math.abs(animateAmount) > this.options.smartEasingThreshold && this.options.useEasing) { + this.finalEndVal = end; + const up = (this.countDown) ? 1 : -1; + this.endVal = end + (up * this.options.smartEasingAmount); + this.duration = this.duration / 2; + } else { + this.endVal = end; + this.finalEndVal = null; + } + if (this.finalEndVal !== null) { + // setting finalEndVal indicates smart easing + this.useEasing = false; + } else { + this.useEasing = this.options.useEasing; + } + } + + // start animation + start(callback?: (args?: any) => any): void { + if (this.error) { + return; + } + if (callback) { + this.options.onCompleteCallback = callback; + } + if (this.duration > 0) { + this.determineDirectionAndSmartEasing(); + this.paused = false; + this.rAF = requestAnimationFrame(this.count); + } else { + this.printValue(this.endVal); + } + } + + // pause/resume animation + pauseResume(): void { + if (!this.paused) { + cancelAnimationFrame(this.rAF); + } else { + this.startTime = null; + this.duration = this.remaining; + this.startVal = this.frameVal; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + } + this.paused = !this.paused; + } + + // reset to startVal so animation can be run again + reset(): void { + cancelAnimationFrame(this.rAF); + this.paused = true; + this.resetDuration(); + this.startVal = this.validateValue(this.options.startVal); + this.frameVal = this.startVal; + this.printValue(this.startVal); + } + + // pass a new endVal and start animation + update(newEndVal: string | number): void { + cancelAnimationFrame(this.rAF); + this.startTime = null; + this.endVal = this.validateValue(newEndVal); + if (this.endVal === this.frameVal) { + return; + } + this.startVal = this.frameVal; + if (this.finalEndVal == null) { + this.resetDuration(); + } + this.finalEndVal = null; + this.determineDirectionAndSmartEasing(); + this.rAF = requestAnimationFrame(this.count); + } + + count = (timestamp: number): void => { + if (!this.startTime) { this.startTime = timestamp; } + + const progress = timestamp - this.startTime; + this.remaining = this.duration - progress; + + // to ease or not to ease + if (this.useEasing) { + if (this.countDown) { + this.frameVal = this.startVal - this.easingFn(progress, 0, this.startVal - this.endVal, this.duration); + } else { + this.frameVal = this.easingFn(progress, this.startVal, this.endVal - this.startVal, this.duration); + } + } else { + this.frameVal = this.startVal + (this.endVal - this.startVal) * (progress / this.duration); + } + + // don't go past endVal since progress can exceed duration in the last frame + const wentPast = this.countDown ? this.frameVal < this.endVal : this.frameVal > this.endVal; + this.frameVal = wentPast ? this.endVal : this.frameVal; + + // decimal + this.frameVal = Number(this.frameVal.toFixed(this.options.decimalPlaces)); + + // format and print value + this.printValue(this.frameVal); + + // whether to continue + if (progress < this.duration) { + this.rAF = requestAnimationFrame(this.count); + } else if (this.finalEndVal !== null) { + // smart easing + this.update(this.finalEndVal); + } else { + if (this.options.onCompleteCallback) { + this.options.onCompleteCallback(); + } + } + } + + printValue(val: number): void { + if (!this.el) return; + const result = this.formattingFn(val); + if (this.options.plugin?.render) { + this.options.plugin.render(this.el, result); + return; + } + if (this.el.tagName === 'INPUT') { + const input = this.el as HTMLInputElement; + input.value = result; + } else if (this.el.tagName === 'text' || this.el.tagName === 'tspan') { + this.el.textContent = result; + } else { + this.el.innerHTML = result; + } + } + + ensureNumber(n: any): boolean { + return (typeof n === 'number' && !isNaN(n)); + } + + validateValue(value: string | number): number { + const newValue = Number(value); + if (!this.ensureNumber(newValue)) { + this.error = `[CountUp] invalid start or end value: ${value}`; + return null; + } else { + return newValue; + } + } + + private resetDuration(): void { + this.startTime = null; + this.duration = Number(this.options.duration) * 1000; + this.remaining = this.duration; + } + + // default format and easing functions + + formatNumber = (num: number): string => { + const neg = (num < 0) ? '-' : ''; + let result: string, x1: string, x2: string, x3: string; + result = Math.abs(num).toFixed(this.options.decimalPlaces); + result += ''; + const x = result.split('.'); + x1 = x[0]; + x2 = x.length > 1 ? this.options.decimal + x[1] : ''; + if (this.options.useGrouping) { + x3 = ''; + let factor = 3, j = 0; + for (let i = 0, len = x1.length; i < len; ++i) { + if (this.options.useIndianSeparators && i === 4) { + factor = 2; + j = 1; + } + if (i !== 0 && (j % factor) === 0) { + x3 = this.options.separator + x3; + } + j++; + x3 = x1[len - i - 1] + x3; + } + x1 = x3; + } + // optional numeral substitution + if (this.options.numerals && this.options.numerals.length) { + x1 = x1.replace(/[0-9]/g, (w) => this.options.numerals[+w]); + x2 = x2.replace(/[0-9]/g, (w) => this.options.numerals[+w]); + } + return neg + this.options.prefix + x1 + x2 + this.options.suffix; + } + + // t: current time, b: beginning value, c: change in value, d: duration + easeOutExpo = (t: number, b: number, c: number, d: number): number => + c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b; + +} diff --git a/node_modules/countup.js/tsconfig.json b/node_modules/countup.js/tsconfig.json new file mode 100644 index 0000000..47f932c --- /dev/null +++ b/node_modules/countup.js/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "lib": ["es2017", "dom"], + "module": "esnext", + "moduleResolution": "node", + "declaration": true, + "outDir": "dist", + "target": "es5", + "pretty": true, + "esModuleInterop": true, + "skipLibCheck": true, + }, + "compileOnSave": true, + "include": ["src"], + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..25a00fd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "CpuHigherLowerGame", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "countup.js": "^2.6.0" + } + }, + "node_modules/countup.js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.6.0.tgz", + "integrity": "sha512-GeORCrCcaFUHP3RNf0/dWK+XQX+fsdtrMO31mNvsbKXNNG+DMTcgZ4dWpIG9BnOS8t5+iJbaRXgaaG9oLs0N4g==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..efe0d15 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "countup.js": "^2.6.0" + } +}