UNPKG

vue-confetti-explosion

Version:
423 lines (347 loc) • 15.7 kB
'use strict';var vue=require('vue');function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }var ROTATION_SPEED_MIN = 200; // minimum possible duration of single particle full rotation var ROTATION_SPEED_MAX = 800; // maximum possible duration of single particle full rotation var CRAZY_PARTICLES_FREQUENCY = 0.1; // 0-1 frequency of crazy curvy unpredictable particles var CRAZY_PARTICLE_CRAZINESS = 0.3; // 0-1 how crazy these crazy particles are var BEZIER_MEDIAN = 0.5; // utility for mid-point bezier curves, to ensure smooth motion paths var FORCE = 0.5; // 0-1 roughly the vertical force at which particles initially explode var SIZE = 12; // max height for particle rectangles, diameter for particle circles var FLOOR_HEIGHT = 800; // pixels the particles will fall from initial explosion point var FLOOR_WIDTH = 1600; // horizontal spread of particles in pixels var PARTICLE_COUNT = 150; var DURATION = 3500; var COLORS = ["#FFC700", "#FF0000", "#2E3191", "#41BBC7"]; var script = { props: { particleCount: { type: Number, default: PARTICLE_COUNT }, particleSize: { type: Number, default: SIZE }, duration: { type: Number, default: DURATION }, colors: { type: Array, default: COLORS }, force: { type: Number, default: FORCE }, stageHeight: { type: Number, default: FLOOR_HEIGHT }, stageWidth: { type: Number, default: FLOOR_WIDTH }, shouldDestroyAfterDone: { type: Boolean, default: true } }, setup: function setup(props) { var isVisible = vue.ref(true); var setItemRef = function setItemRef(el, degree) { confettiStyles(el, { degree: degree }); }; var particles = vue.computed(function () { return createParticles(props.particleCount, props.colors); }); vue.watchEffect(function () { props.particleCount > 300 && console.log("[VUE-CONFETTI-EXPLOSION] That's a lot of confetti, you sure about that? A lesser number" + " like 200 will still give off the party vibes while still not bricking the device 😉"); }); var isValid = vue.computed(function () { return validate(props.particleCount, props.duration, props.colors, props.particleSize, props.force, props.stageHeight, props.stageWidth); }); vue.onMounted( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() { return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return waitFor(props.duration); case 2: if (props.shouldDestroyAfterDone) { isVisible.value = false; } case 3: case "end": return _context.stop(); } } }, _callee); }))); var createParticles = function createParticles(count, colors) { var increment = 360 / count; return Array.from({ length: count }, function (_, i) { return { color: colors[i % colors.length], degree: i * increment }; }); }; var waitFor = function waitFor(ms) { return new Promise(function (resolve) { return setTimeout(resolve, ms); }); }; // From here: https://stackoverflow.com/a/11832950 function round(num) { var precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2; return Math.round((num + Number.EPSILON) * Math.pow(10, precision)) / Math.pow(10, precision); } function arraysEqual(a, b) { if (a === b) return true; if (a == null || b == null) return false; if (a.length !== b.length) return false; for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } var mapRange = function mapRange(value, x1, y1, x2, y2) { return (value - x1) * (y2 - x2) / (y1 - x1) + x2; }; var rotate = function rotate(degree, amount) { var result = degree + amount; return result > 360 ? result - 360 : result; }; var coinFlip = function coinFlip() { return Math.random() > 0.5; }; // avoid this for circles, as it will have no visual effect var zAxisRotation = [0, 0, 1]; var rotationTransforms = [// dual axis rotations (a bit more realistic) [1, 1, 0], [1, 0, 1], [0, 1, 1], // single axis rotations (a bit dumber) [1, 0, 0], [0, 1, 0], zAxisRotation]; var shouldBeCircle = function shouldBeCircle(rotationIndex) { return !arraysEqual(rotationTransforms[rotationIndex], zAxisRotation) && coinFlip(); }; var isUndefined = function isUndefined(value) { return typeof value === "undefined"; }; var error = function error(message) { console.error(message); }; function validate(particleCount, duration, colors, particleSize, force, floorHeight, floorWidth) { var isSafeInteger = Number.isSafeInteger; if (!isUndefined(particleCount) && isSafeInteger(particleCount) && particleCount < 0) { error("particleCount must be a positive integer"); return false; } if (!isUndefined(duration) && isSafeInteger(duration) && duration < 0) { error("duration must be a positive integer"); return false; } if (!isUndefined(colors) && !Array.isArray(colors)) { error("colors must be an array of strings"); return false; } if (!isUndefined(particleSize) && isSafeInteger(particleSize) && particleSize < 0) { error("particleSize must be a positive integer"); return false; } if (!isUndefined(force) && isSafeInteger(force) && (force < 0 || force > 1)) { error("force must be a positive integer and should be within 0 and 1"); return false; } if (!isUndefined(floorHeight) && typeof floorHeight === "number" && isSafeInteger(floorHeight) && floorHeight < 0) { error("floorHeight must be a positive integer"); return false; } if (!isUndefined(floorWidth) && typeof floorWidth === "number" && isSafeInteger(floorWidth) && floorWidth < 0) { error("floorWidth must be a positive integer"); return false; } return true; } function confettiStyles(node, _ref2) { var degree = _ref2.degree; // Get x landing point for it var landingPoint = mapRange(Math.abs(rotate(degree, 90) - 180), 0, 180, -props.stageWidth / 2, props.stageWidth / 2); // Crazy calculations for generating styles var rotation = Math.random() * (ROTATION_SPEED_MAX - ROTATION_SPEED_MIN) + ROTATION_SPEED_MIN; var rotationIndex = Math.round(Math.random() * (rotationTransforms.length - 1)); var durationChaos = props.duration - Math.round(Math.random() * 1000); var shouldBeCrazy = Math.random() < CRAZY_PARTICLES_FREQUENCY; var isCircle = shouldBeCircle(rotationIndex); // x-axis disturbance, roughly the distance the particle will initially deviate from its target var x1 = shouldBeCrazy ? round(Math.random() * CRAZY_PARTICLE_CRAZINESS, 2) : 0; var x2 = x1 * -1; var x3 = x1; // x-axis arc of explosion, so 90deg and 270deg particles have curve of 1, 0deg and 180deg have 0 var x4 = round(Math.abs(mapRange(Math.abs(rotate(degree, 90) - 180), 0, 180, -1, 1)), 4); // roughly how fast particle reaches end of its explosion curve var y1 = round(Math.random() * BEZIER_MEDIAN, 4); // roughly maps to the distance particle goes before reaching free-fall var y2 = round(Math.random() * props.force * (coinFlip() ? 1 : -1), 4); // roughly how soon the particle transitions from explosion to free-fall var y3 = BEZIER_MEDIAN; // roughly the ease of free-fall var y4 = round(Math.max(mapRange(Math.abs(degree - 180), 0, 180, props.force, -props.force), 0), 4); var setCSSVar = function setCSSVar(key, val) { return node === null || node === void 0 ? void 0 : node.style.setProperty(key, val + ""); }; setCSSVar("--x-landing-point", "".concat(landingPoint, "px")); setCSSVar("--duration-chaos", "".concat(durationChaos, "ms")); setCSSVar("--x1", "".concat(x1)); setCSSVar("--x2", "".concat(x2)); setCSSVar("--x3", "".concat(x3)); setCSSVar("--x4", "".concat(x4)); setCSSVar("--y1", "".concat(y1)); setCSSVar("--y2", "".concat(y2)); setCSSVar("--y3", "".concat(y3)); setCSSVar("--y4", "".concat(y4)); // set --width and --height here setCSSVar("--width", "".concat(isCircle ? props.particleSize : Math.round(Math.random() * 4) + props.particleSize / 2, "px")); setCSSVar("--height", (isCircle ? props.particleSize : Math.round(Math.random() * 2) + props.particleSize) + "px"); setCSSVar("--rotation", "".concat(rotationTransforms[rotationIndex].join())); setCSSVar("--rotation-duration", "".concat(rotation, "ms")); setCSSVar("--border-radius", "".concat(isCircle ? "50%" : "0")); } return { isVisible: isVisible, isValid: isValid, stageHeight: props.stageHeight, particles: particles, setItemRef: setItemRef }; } };function render(_ctx, _cache, $props, $setup, $data, $options) { return $setup.isVisible && $setup.isValid ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, class: "confetti-container", style: vue.normalizeStyle("--floor-height: ".concat($setup.stageHeight, "px;")) }, [(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($setup.particles, function (_ref) { var color = _ref.color, degree = _ref.degree; return vue.openBlock(), vue.createElementBlock("div", { key: degree, class: "particle", ref: function ref(el) { return $setup.setItemRef(el, degree); } }, [vue.createElementVNode("div", { style: vue.normalizeStyle("--bgcolor: ".concat(color, ";")) }, null, 4)], 512); }), 128))], 4)) : vue.createCommentVNode("", true); }function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } }var css_248z = "\n@keyframes y-axis-4ff796ae {\nto {\n transform: translate3d(0, var(--floor-height), 0);\n}\n}\n@keyframes x-axis-4ff796ae {\nto {\n transform: translate3d(var(--x-landing-point), 0, 0);\n}\n}\n@keyframes rotation-4ff796ae {\nto {\n transform: rotate3d(var(--rotation), 360deg);\n}\n}\n.confetti-container[data-v-4ff796ae] {\n width: 0;\n height: 0;\n overflow: visible;\n position: relative;\n transform: translate3d(var(--x, 0), var(--y, 0), 0);\n z-index: 1200;\n}\n.confetti-container > .particle[data-v-4ff796ae] {\n animation: x-axis-4ff796ae var(--duration-chaos) forwards cubic-bezier(var(--x1), var(--x2), var(--x3), var(--x4));\n}\n.confetti-container > .particle div[data-v-4ff796ae] {\n position: absolute;\n top: 0;\n left: 0;\n animation: y-axis-4ff796ae var(--duration-chaos) forwards cubic-bezier(var(--y1), var(--y2), var(--y3), var(--y4));\n width: var(--width);\n height: var(--height);\n}\n.confetti-container > .particle div[data-v-4ff796ae]:before {\n display: block;\n height: 100%;\n width: 100%;\n content: \"\";\n background-color: var(--bgcolor);\n animation: rotation-4ff796ae var(--rotation-duration) infinite linear;\n border-radius: var(--border-radius);\n}\n"; styleInject(css_248z);script.render = render; script.__scopeId = "data-v-4ff796ae";// Import vue component // Default export is installable instance of component. // IIFE injects install function into component, allowing component // to be registered via Vue.use() as well as Vue.component(), var component = /*#__PURE__*/(function () { // Assign InstallableComponent type var installable = script; // Attach install function executed by Vue.use() installable.install = function (app) { app.component("ConfettiExplosion", installable); }; return installable; })(); // It's possible to expose named exports when writing components that can // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; // export const RollupDemoDirective = directive; var namedExports=/*#__PURE__*/Object.freeze({__proto__:null,'default':component});// only expose one global var, with named exports exposed as properties of // that global var (eg. plugin.namedExport) Object.entries(namedExports).forEach(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), exportName = _ref2[0], exported = _ref2[1]; if (exportName !== 'default') component[exportName] = exported; });module.exports=component;