UNPKG

vue3-radial-progress

Version:

A smart and light radial progress bar component for Vue 3.

307 lines (280 loc) 10.2 kB
import { defineComponent, reactive, ref, computed, watch, openBlock, createElementBlock, normalizeStyle, createElementVNode, renderSlot } from 'vue'; function randomString() { return Math.random().toString(16).substring(2); } var script = 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(props) { const gradient = reactive({ fx: 0.99, fy: 0.5, cx: 0.5, cy: 0.5, r: 0.65 }); const radialGradientId = `rg-${randomString()}`; const strokeDashoffset = ref(0); const currentAngle = ref(0); const gradientAnimation = ref(null); const radius = computed(() => props.diameter / 2); const innerCircleDiameter = computed(() => props.diameter - props.innerStrokeWidth * 2); const circumference = computed(() => Math.PI * innerCircleDiameter.value); const stepSize = computed(() => props.totalSteps === 0 ? 0 : 100 / props.totalSteps); const finishedPercentage = computed(() => stepSize.value * props.completedSteps); const circleSlice = computed(() => 2 * Math.PI / props.totalSteps); const animationIncrements = computed(() => 100 / props.fps); const totalPoints = computed(() => props.animateSpeed / animationIncrements.value); const animateSlice = computed(() => circleSlice.value / totalPoints.value); const innerCircleRadius = computed(() => innerCircleDiameter.value / 2); const containerStyle = computed(() => ({ height: `${props.diameter}px`, width: `${props.diameter}px` })); const progressStyle = computed(() => ({ height: `${props.diameter}px`, width: `${props.diameter}px`, strokeWidth: `${props.strokeWidth}px`, strokeDashoffset: strokeDashoffset.value, transition: `stroke-dashoffset ${props.animateSpeed}ms ${props.timingFunc}` })); const strokeStyle = computed(() => ({ height: `${props.diameter}px`, width: `${props.diameter}px`, strokeWidth: `${props.innerStrokeWidth}px` })); const innerCircleStyle = computed(() => ({ width: `${innerCircleDiameter.value}px` })); watch(() => [props.diameter, props.totalSteps, props.completedSteps, props.strokeWidth], changeProgress, { immediate: true }); function getPointOfCircle(angle) { const radius = 0.5; const x = radius + radius * Math.cos(angle); const y = radius + radius * Math.sin(angle); return { x, y }; } function gotoPoint() { const 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); } const angleOffset = (props.completedSteps - 1) * circleSlice.value; let i = (currentAngle.value - angleOffset) / animateSlice.value; const incrementer = Math.abs(i - totalPoints.value) / totalPoints.value; const isMoveForward = i < totalPoints.value; gradientAnimation.value = setInterval(() => { 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, innerCircleRadius, radialGradientId, strokeDashoffset, innerCircleStyle, containerStyle, circumference, progressStyle, currentAngle, strokeStyle, gradient, radius }; } }); const _hoisted_1 = ["width", "height"]; const _hoisted_2 = ["id", "fx", "fy", "cx", "cy", "r"]; const _hoisted_3 = ["stop-color"]; const _hoisted_4 = ["stop-color"]; const _hoisted_5 = ["r", "cx", "cy", "stroke", "stroke-dasharray", "stroke-linecap"]; const _hoisted_6 = ["transform", "r", "cx", "cy", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap"]; function render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("div", { class: "vrp__wrapper", style: normalizeStyle(_ctx.containerStyle) }, [createElementVNode("div", { class: "vrp__inner", style: normalizeStyle(_ctx.innerCircleStyle) }, [renderSlot(_ctx.$slots, "default")], 4), (openBlock(), createElementBlock("svg", { width: _ctx.diameter, height: _ctx.diameter, version: "1.1", xmlns: "http://www.w3.org/2000/svg" }, [createElementVNode("defs", null, [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 }, [createElementVNode("stop", { offset: "30%", "stop-color": _ctx.startColor }, null, 8, _hoisted_3), createElementVNode("stop", { offset: "100%", "stop-color": _ctx.stopColor }, null, 8, _hoisted_4)], 8, _hoisted_2)]), 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: normalizeStyle(_ctx.strokeStyle) }, null, 12, _hoisted_5), createElementVNode("circle", { transform: 'rotate(270, ' + _ctx.radius + ',' + _ctx.radius + ')', r: _ctx.innerCircleRadius, cx: _ctx.radius, cy: _ctx.radius, fill: "transparent", stroke: `url('#${_ctx.radialGradientId}')`, "stroke-dasharray": _ctx.circumference, "stroke-dashoffset": _ctx.circumference, "stroke-linecap": _ctx.strokeLinecap, style: 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"; // IIFE injects install function into component, allowing component // to be registered via Vue.use() as well as Vue.component(), var entry_esm = /*#__PURE__*/(() => { // Assign InstallableComponent type const installable = script; // Attach install function executed by Vue.use() installable.install = 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; export { entry_esm as default };