vue3-radial-progress
Version:
A smart and light radial progress bar component for Vue 3.
382 lines (354 loc) • 12.9 kB
JavaScript
;var vue=require('vue');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.");
}function randomString() {
return Math.random().toString(16).substring(2);
}var script = vue.defineComponent({
props: {
// Sets width/diameter of the inner stroke.
diameter: {
type: Number,
required: false,
default: 200
},
// Sets the total steps/progress to the end.
totalSteps: {
type: Number,
required: true,
default: 10
},
// Sets the current progress of the inner stroke.
completedSteps: {
type: Number,
required: true,
default: 0
},
// Sets the start color of the inner stroke (gradient).
startColor: {
type: String,
required: false,
default: "#00C58E"
},
// Sets the end color of the inner stroke (gradient).
stopColor: {
type: String,
required: false,
default: "#00E0A1"
},
// Sets the color of the inner stroke to be applied to the shape.
innerStrokeColor: {
type: String,
required: false,
default: "#2F495E"
},
// Sets the width of the stroke to be applied to the shape.
// Read more: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width
strokeWidth: {
type: Number,
required: false,
default: 10
},
// Sets the width of the inner stroke to be applied to the shape.
innerStrokeWidth: {
type: Number,
required: false,
default: 10
},
// Sets the shape to be used at the end of stroked.
// Read more: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap
strokeLinecap: {
type: String,
required: false,
default: "round"
},
// Sets how long the animation should take to complete one cycle.
// Read more: https://www.w3schools.com/cssref/css3_pr_animation-duration.asp
animateSpeed: {
type: Number,
required: false,
default: 1000
},
// Sets the frames per seconds to update inner stroke animation.
fps: {
type: Number,
required: false,
default: 60
},
// Sets how the animation progresses through the duration of each cycle.
// Read more: https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function
timingFunc: {
type: String,
required: false,
default: "linear"
},
// Sets the inner stroke direction.
isClockwise: {
type: Boolean,
required: false,
default: true
}
},
setup: function setup(props) {
var gradient = vue.reactive({
fx: 0.99,
fy: 0.5,
cx: 0.5,
cy: 0.5,
r: 0.65
});
var radialGradientId = "rg-".concat(randomString());
var strokeDashoffset = vue.ref(0);
var currentAngle = vue.ref(0);
var gradientAnimation = vue.ref(null);
var radius = vue.computed(function () {
return props.diameter / 2;
});
var innerCircleDiameter = vue.computed(function () {
return props.diameter - props.innerStrokeWidth * 2;
});
var circumference = vue.computed(function () {
return Math.PI * innerCircleDiameter.value;
});
var stepSize = vue.computed(function () {
return props.totalSteps === 0 ? 0 : 100 / props.totalSteps;
});
var finishedPercentage = vue.computed(function () {
return stepSize.value * props.completedSteps;
});
var circleSlice = vue.computed(function () {
return 2 * Math.PI / props.totalSteps;
});
var animationIncrements = vue.computed(function () {
return 100 / props.fps;
});
var totalPoints = vue.computed(function () {
return props.animateSpeed / animationIncrements.value;
});
var animateSlice = vue.computed(function () {
return circleSlice.value / totalPoints.value;
});
var innerCircleRadius = vue.computed(function () {
return innerCircleDiameter.value / 2;
});
var containerStyle = vue.computed(function () {
return {
height: "".concat(props.diameter, "px"),
width: "".concat(props.diameter, "px")
};
});
var progressStyle = vue.computed(function () {
return {
height: "".concat(props.diameter, "px"),
width: "".concat(props.diameter, "px"),
strokeWidth: "".concat(props.strokeWidth, "px"),
strokeDashoffset: strokeDashoffset.value,
transition: "stroke-dashoffset ".concat(props.animateSpeed, "ms ").concat(props.timingFunc)
};
});
var strokeStyle = vue.computed(function () {
return {
height: "".concat(props.diameter, "px"),
width: "".concat(props.diameter, "px"),
strokeWidth: "".concat(props.innerStrokeWidth, "px")
};
});
var innerCircleStyle = vue.computed(function () {
return {
width: "".concat(innerCircleDiameter.value, "px")
};
});
vue.watch(function () {
return [props.diameter, props.totalSteps, props.completedSteps, props.strokeWidth];
}, changeProgress, {
immediate: true
});
function getPointOfCircle(angle) {
var radius = 0.5;
var x = radius + radius * Math.cos(angle);
var y = radius + radius * Math.sin(angle);
return {
x: x,
y: y
};
}
function gotoPoint() {
var point = getPointOfCircle(currentAngle.value);
if (point.x && point.y) {
gradient.fx = point.x;
gradient.fy = point.y;
}
}
function direction() {
return props.isClockwise ? 1 : -1;
}
function changeProgress() {
strokeDashoffset.value = (100 - finishedPercentage.value) / 100 * circumference.value * direction();
if (gradientAnimation.value) {
clearInterval(gradientAnimation.value);
}
var angleOffset = (props.completedSteps - 1) * circleSlice.value;
var i = (currentAngle.value - angleOffset) / animateSlice.value;
var incrementer = Math.abs(i - totalPoints.value) / totalPoints.value;
var isMoveForward = i < totalPoints.value;
gradientAnimation.value = setInterval(function () {
if (isMoveForward && i >= totalPoints.value || !isMoveForward && i < totalPoints.value) {
gradientAnimation.value && clearInterval(gradientAnimation.value);
return;
}
currentAngle.value = angleOffset + animateSlice.value * i;
gotoPoint();
i += isMoveForward ? incrementer : -incrementer;
}, animationIncrements.value);
}
return {
gradientAnimation: gradientAnimation,
innerCircleRadius: innerCircleRadius,
radialGradientId: radialGradientId,
strokeDashoffset: strokeDashoffset,
innerCircleStyle: innerCircleStyle,
containerStyle: containerStyle,
circumference: circumference,
progressStyle: progressStyle,
currentAngle: currentAngle,
strokeStyle: strokeStyle,
gradient: gradient,
radius: radius
};
}
});var _hoisted_1 = ["width", "height"];
var _hoisted_2 = ["id", "fx", "fy", "cx", "cy", "r"];
var _hoisted_3 = ["stop-color"];
var _hoisted_4 = ["stop-color"];
var _hoisted_5 = ["r", "cx", "cy", "stroke", "stroke-dasharray", "stroke-linecap"];
var _hoisted_6 = ["transform", "r", "cx", "cy", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap"];
function render(_ctx, _cache, $props, $setup, $data, $options) {
return vue.openBlock(), vue.createElementBlock("div", {
class: "vrp__wrapper",
style: vue.normalizeStyle(_ctx.containerStyle)
}, [vue.createElementVNode("div", {
class: "vrp__inner",
style: vue.normalizeStyle(_ctx.innerCircleStyle)
}, [vue.renderSlot(_ctx.$slots, "default")], 4), (vue.openBlock(), vue.createElementBlock("svg", {
width: _ctx.diameter,
height: _ctx.diameter,
version: "1.1",
xmlns: "http://www.w3.org/2000/svg"
}, [vue.createElementVNode("defs", null, [vue.createElementVNode("radialGradient", {
id: _ctx.radialGradientId,
fx: _ctx.gradient.fx,
fy: _ctx.gradient.fy,
cx: _ctx.gradient.cx,
cy: _ctx.gradient.cy,
r: _ctx.gradient.r
}, [vue.createElementVNode("stop", {
offset: "30%",
"stop-color": _ctx.startColor
}, null, 8, _hoisted_3), vue.createElementVNode("stop", {
offset: "100%",
"stop-color": _ctx.stopColor
}, null, 8, _hoisted_4)], 8, _hoisted_2)]), vue.createElementVNode("circle", {
r: _ctx.innerCircleRadius,
cx: _ctx.radius,
cy: _ctx.radius,
fill: "transparent",
stroke: _ctx.innerStrokeColor,
"stroke-dasharray": _ctx.circumference,
"stroke-dashoffset": "0",
"stroke-linecap": _ctx.strokeLinecap,
style: vue.normalizeStyle(_ctx.strokeStyle)
}, null, 12, _hoisted_5), vue.createElementVNode("circle", {
transform: 'rotate(270, ' + _ctx.radius + ',' + _ctx.radius + ')',
r: _ctx.innerCircleRadius,
cx: _ctx.radius,
cy: _ctx.radius,
fill: "transparent",
stroke: "url('#".concat(_ctx.radialGradientId, "')"),
"stroke-dasharray": _ctx.circumference,
"stroke-dashoffset": _ctx.circumference,
"stroke-linecap": _ctx.strokeLinecap,
style: vue.normalizeStyle(_ctx.progressStyle)
}, null, 12, _hoisted_6)], 8, _hoisted_1))], 4);
}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.vrp__wrapper[data-v-6a0cf1f6] {\r\n position: relative;\n}\n.vrp__inner[data-v-6a0cf1f6] {\r\n position: absolute;\r\n top: 0;\r\n right: 0;\r\n bottom: 0;\r\n left: 0;\r\n border-radius: 50%;\r\n margin: 0 auto;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\n}\r\n";
styleInject(css_248z);script.render = render;
script.__scopeId = "data-v-6a0cf1f6";// 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("RadialProgressBar", 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});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;