vue-confetti-explosion
Version:
Confetti explosion in Vue 3 🎉🎊
423 lines (347 loc) • 15.7 kB
JavaScript
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;
;