123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- <template>
- <view class="l-circle" :class="[{clockwise: !clockwise && !useCanvas}, ['is-' + lineCap]]" :style="[styles]">
- <!-- #ifndef APP-NVUE -->
- <view class="check"></view>
- <view v-if="!useCanvas" class="l-circle__trail" :style="[trailStyles]">
- <text class="cap start"></text>
- <text class="cap end"></text>
- </view>
- <view v-if="!useCanvas" class="l-circle__stroke" :style="[strokeStyles]">
- <view class="l-circle__stroke-line"></view>
- <text class="cap start" v-if="current"></text>
- <text class="cap end" v-if="current"></text>
- </view>
- <canvas v-if="useCanvas" type="2d" :canvas-id="canvasId" :id="canvasId" class="l-circle__canvas" ></canvas>
- <!-- #endif -->
- <!-- #ifdef APP-NVUE -->
- <web-view
- @pagefinish="finished = true"
- @error="onerror"
- @onPostMessage="onMessage"
- class="l-circle__view"
- ref="webview"
- src="/uni_modules/lime-circle/hybrid/html/index.html"></web-view>
- <!-- #endif -->
- <view class="l-circle__inner">
- <slot></slot>
- </view>
- </view>
- </template>
- <script lang="ts">
- // @ts-nocheck
- import { ref, computed, watch, reactive, defineComponent, onMounted ,onUnmounted, getCurrentInstance, nextTick } from './vue';
- import { useTransition } from './useTransition';
- import CircleProps from './props';
- import { getCanvas , isCanvas2d} from './getCanvas';
- import {Circle} from './circle'
- import { addUnit } from '@/uni_modules/lime-shared/addUnit';
- import { unitConvert } from '@/uni_modules/lime-shared/unitConvert';
- import { isString } from '@/uni_modules/lime-shared/isString';
- import { getRect } from '@/uni_modules/lime-shared/getRect';
- // import { useTransition } from '@/uni_modules/lime-use';
-
- export default defineComponent({
- name: 'l-circle',
- props: CircleProps,
- emits: ['update:current'],
- setup(props, {emit}) {
- const context = getCurrentInstance()
- const useCanvas = ref(props.canvas)
- const canvasId = `l-circle-${context.uid}`;
- let circleCanvas = null
-
- const RADIAN = Math.PI / 180
- const ratio = computed(() => 100 / props.max)
- const percent = ref<number>(0)
- const angle = computed(() => props.dashborad ? 135 : -90)
- const isShowCap = computed(() => {
- const { dashborad } = props
- return current.value > 0 && (dashborad ? true : current.value < props.max)
- })
- const offsetTop = ref<number | string>(0)
- const strokeEndCap = reactive({
- x: '0',
- y: '0'
- })
- const styles = computed(() => ({
- width: addUnit(props.size),
- height:addUnit(props.size),
- // #ifdef APP-NVUE
- transform: !useCanvas.value && `translateY(${offsetTop.value})`,
- // #endif
- // #ifndef APP-NVUE
- '--l-circle-offset-top': !useCanvas.value && offsetTop.value,
- // #endif
- }))
- const classes = computed(() => {
- const { clockwise, lineCap } = props
- // {
- // clockwise: !clockwise && !useCanvas.value,
- // [`is-${lineCap}`]: lineCap
- // }
- return lineCap ? `is-${lineCap} ` : ' ' + !clockwise && !useCanvas.value && `clockwise`
- })
-
- // css render
- const trailStyles = computed(() => {
- const { size, trailWidth, trailColor, dashborad } = props
- const circle = getCircle(size, trailWidth)
- const mask = `radial-gradient(transparent ${circle.r - 0.5}px, #000 ${circle.r}px)`
- let background = ''
- let capStart = { x: '', y: '' }
- let capEnd = capStart
- if (dashborad) {
- background = `conic-gradient(from 225deg, ${trailColor} 0%, ${trailColor} 75%, transparent 75%, transparent 100%)`
- capStart = calcPosition(circle.c, 135)
- capEnd = calcPosition(circle.c, 45)
- offsetTop.value = (unitConvert(size) - (unitConvert(capStart.y) + unitConvert(trailWidth) / 2)) / 4 + 'px'
- } else {
- background = `${trailColor}`
- }
- return {
- color: trailColor,
- mask,
- '-webkit-mask': mask,
- background,
- '--l-circle-trail-cap-start-x': capStart.x,
- '--l-circle-trail-cap-start-y': capStart.y,
- '--l-circle-trail-cap-end-x': capEnd.x,
- '--l-circle-trail-cap-end-y': capEnd.y,
- // '--l-circle-trail-cap-color': trailColor,
- '--l-circle-trail-cap-size': addUnit(unitConvert(trailWidth))
- }
- })
- const strokeStyles = computed(() => {
- const { size, strokeWidth, strokeColor, dashborad, max } = props
- const circle = getCircle(size, strokeWidth)
- const percent = dashborad ? current.value * 0.75 * ratio.value : current.value * ratio.value;
- const mask = `radial-gradient(transparent ${circle.r - 0.5}px, #000 ${circle.r}px)`
- const cap = calcPosition(circle.c, angle.value)
- let startColor = '';
- let endColor = '';
- let gradient = `conic-gradient(${dashborad ? 'from 225deg,' : ''} transparent 0%,`;
- let gradientEnd = `transparent var(--l-circle-percent), transparent ${dashborad ? '75%' : '100%'})`
- if (isString(strokeColor)) {
- gradient += ` ${strokeColor} 0%, ${strokeColor} var(--l-circle-percent), ${gradientEnd}`
- startColor = endColor = strokeColor
- } else {
- const len = strokeColor.length
- for (let i = 0; i < len; i++) {
- const color = strokeColor[i] as string
- if (i === 0) {
- gradient += `${color} 0%,`
- startColor = color
- } else {
- gradient += `${color} calc(var(--l-circle-percent) * ${(i + 1) / len}),`
- }
- if (i == len - 1) {
- endColor = color
- }
- }
- gradient += gradientEnd
- }
- return {
- mask,
- '-webkit-mask': mask,
- '--l-background': gradient,
- // background: isString(strokeColor) ? strokeColor : strokeColor[0],
- // background: gradient,
- // transition: `--l-circle-percent ${duration}ms`,
- '--l-circle-percent': `${percent / ratio.value == max ? percent + 0.1 : percent}%`,
- '--l-circle-stroke-cap-start-color': startColor,
- '--l-circle-stroke-cap-end-color': endColor,
- '--l-circle-stroke-cap-start-x': cap.x,
- '--l-circle-stroke-cap-start-y': cap.y,
- '--l-circle-stroke-cap-end-x': strokeEndCap.x,
- '--l-circle-stroke-cap-end-y': strokeEndCap.y,
- '--l-circle-stroke-cap-size': addUnit(unitConvert(strokeWidth)),
- '--l-circle-stroke-cap-opacity': isShowCap.value ? 1 : 0
- }
- })
- const calcStrokeCap = () => {
- const { size, strokeWidth, dashborad, max } = props
- const circle = getCircle(size, strokeWidth)
- const arc = dashborad ? 180 / 2 * 3 : 180 * 2
- const step = arc / max * current.value + angle.value
- const cap = calcPosition(circle.c, step)
- strokeEndCap.x = cap.x
- strokeEndCap.y = cap.y
- }
- const calcPosition = (r : number, angle : number) => {
- return {
- x: r + r * Math.cos(angle * RADIAN) + 'px',
- y: r + r * Math.sin(angle * RADIAN) + 'px'
- }
- }
- const getCircle = (size : number | string, lineWidth : number | string) => {
- const s = unitConvert(size)
- const w = unitConvert(lineWidth)
- const c = (s - w) / 2
- const r = s / 2 - w
- return {
- s, w, c, r
- }
- }
- // css render end
- const [current, stopTransition] = useTransition(percent, {
- duration: props.duration,
- })
- const stopPercent = watch(() => props.percent, (v) => {
- percent.value = v
- circleCanvas && circleCanvas.play(v)
- })
-
- const stopCurrent = watch(current, (v) => {
- if(!useCanvas.value) {
- calcStrokeCap()
- }
- emit('update:current', v.toFixed(2))
- })
- const getProps = () => {
- const {strokeWidth, trailWidth} = props
- return Object.assign({}, props, {trailWidth: unitConvert(trailWidth), strokeWidth: unitConvert(strokeWidth)})
- }
- // #ifdef APP-NVUE
- const finished = ref(false)
- const init = ref(false)
- const webview = ref(null)
- const onerror = () => {
-
- }
- const onMessage = (e: any) => {
- const {detail:{data: [res]}} = e;
- if(res.event == 'init') {
- useCanvas.value = res.data.useCanvas // && props.canvas
- init.value = true;
- circleCanvas = {
- setOption(props: any) {
- webview.value.evalJs(`setOption(${JSON.stringify(props)})`)
- },
- play(v: number) {
- webview.value.evalJs(`play(${v})`)
- }
- }
- }
- if(res.event == 'progress') {
- current.value = res.data
- }
- }
- let stopFinnished = watch(init, () => {
- stopFinnished()
- if(useCanvas.value) {
- circleCanvas.setOption(getProps())
- circleCanvas.play(props.percent)
- stopTransition()
- } else {
- webview.value.evalJs(`setClass('.l-circle', 'is-round', ${props.lineCap == 'round'})`)
- webview.value.evalJs(`setClass('.l-circle', 'clockwise', ${props.clockwise})`)
- stopFinnished = watch([trailStyles, strokeStyles], (v) => {
- webview.value.evalJs(`setStyle(0,${JSON.stringify(v[0])})`)
- webview.value.evalJs(`setStyle(1,${JSON.stringify(v[1])})`)
- }, { immediate: true })
- }
- percent.value = props.percent
- })
-
- // #endif
- // #ifndef APP-NVUE
- onMounted(() => {
- getRect('.check', {context}).then(res => {
- // alert(CSS.supports('background', 'conic-gradient(#000, #fff)'))
- useCanvas.value = !(res.height > 0 && !props.canvas)
- if(useCanvas.value) {
- stopTransition()
- setTimeout(() => {
- getCanvas(canvasId, {context}).then(res => {
- circleCanvas = new Circle(res, {
- size: unitConvert(props.size),
- run: (v: number) => current.value = v,
- pixelRatio: isCanvas2d ? uni.getSystemInfoSync().pixelRatio : 1,
- })
- circleCanvas.setOption(getProps())
- circleCanvas.play(props.percent)
- })
- },50)
- }
- percent.value = props.percent
- })
- })
- // #endif
- onUnmounted(() => {
- stopPercent()
- stopCurrent()
- stopTransition()
- // #ifdef APP-NVUE
- stopFinnished && stopFinnished()
- // #endif
- })
- return {
- useCanvas,
- canvasId,
- classes,
- styles,
- trailStyles,
- strokeStyles,
- current,
- // #ifdef APP-NVUE
- webview,
- onerror,
- onMessage,
- finished
- // #endif
- }
- }
- })
- </script>
- <style lang="scss">
- @import './index';
- </style>
|