You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
10 KiB
314 lines
10 KiB
<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.dashboard ? 135 : -90)
|
|
const isShowCap = computed(() => {
|
|
const { dashboard } = props
|
|
return current.value > 0 && (dashboard ? 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, dashboard } = 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 (dashboard) {
|
|
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, dashboard, max } = props
|
|
const circle = getCircle(size, strokeWidth)
|
|
const percent = dashboard ? 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(${dashboard ? 'from 225deg,' : ''} transparent 0%,`;
|
|
let gradientEnd = `transparent var(--l-circle-percent), transparent ${dashboard ? '75%' : '100%'})`
|
|
|
|
if (isString(strokeColor)) {
|
|
gradient += ` ${strokeColor} 0%, ${strokeColor} var(--l-circle-percent), ${gradientEnd}`
|
|
startColor = endColor = strokeColor
|
|
} else if (Array.isArray(strokeColor)){
|
|
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, dashboard, max } = props
|
|
const circle = getCircle(size, strokeWidth)
|
|
const arc = dashboard ? 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;
|
|
webview.value.evalJs(`init(${useCanvas.value})`)
|
|
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 })
|
|
stopFinnished = watch(current, ()=>{
|
|
webview.value.evalJs(`setStyle(0,${JSON.stringify(trailStyles.value)})`)
|
|
webview.value.evalJs(`setStyle(1,${JSON.stringify(strokeStyles.value)})`)
|
|
}, { 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> |