|
|
- // type UseLoadingOtions = {
- // type: string,
- // color: string,
- // el: UniElement
- // }
- import {tinyColor} from '@/uni_modules/lime-color'
- function easeInOutCubic(t : number) : number {
- return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
- }
-
- type useLoadingReturnType = {
- state : Ref<boolean>
- color : Ref<string>
- play: () => void
- failed: () => void
- clear : () => void
- destroy : () => void
- }
- type Point = {
- x1: number
- y1: number
- x2: number
- y2: number
- }
- export function useLoading(
- element : Ref<UniElement | null>,
- type : 'circular' | 'spinner',
- strokeColor : string,
- ratio : number,
- immediate: boolean = false,
- ) : useLoadingReturnType {
- const state = ref(false)
- const color = ref(strokeColor)
- let tick = 0 // 0 不绘制 | 1 旋转 | 2 错误
- let init = false
- let isDestroy = ref(false)
- let width = 0
- let height = 0
- let size = 0
- let x = 0
- let y = 0
- let ctx : DrawableContext | null = null
- let timer = -1
- let isClear = false;
- let drawing = false;
- const updateSize = () => {
- if (element.value == null) return
-
- const rect = element.value!.getBoundingClientRect();
- ctx = element.value!.getDrawableContext()! as DrawableContext
- width = rect.width
- height = rect.height
- size = ratio > 1 ? ratio : Math.floor(Math.min(width, height) * ratio)
- x = width / 2
- y = height / 2
- }
- const circular = () => {
- if (ctx == null) return
- let _ctx = ctx!
- let startAngle = 0;
- let endAngle = 0;
- let startSpeed = 0;
- let endSpeed = 0;
- let rotate = 0;
-
- // 不使用360的原因是加上rotate后,会导致闪烁
- const ARC_LENGTH = 359.5
- const PI = Math.PI / 180
- const SPEED = 0.018
- const ROTATE_INTERVAL = 0.09
- const center = size / 2
- const lineWidth = size / 10;
-
- function draw() {
- if(isClear) return
- _ctx.reset();
- _ctx.beginPath();
- _ctx.arc(
- x,
- y,
- center - lineWidth,
- startAngle * PI + rotate,
- endAngle * PI + rotate);
- _ctx.lineWidth = lineWidth;
- _ctx.strokeStyle = color.value;
- _ctx.stroke();
-
- if (endAngle < ARC_LENGTH && startAngle == 0) {
- endSpeed += SPEED
- endAngle = Math.min(ARC_LENGTH, easeInOutCubic(endSpeed) * ARC_LENGTH)
- } else if (endAngle == ARC_LENGTH && startAngle < ARC_LENGTH) {
- startSpeed += SPEED
- startAngle = Math.min(ARC_LENGTH, easeInOutCubic(startSpeed) * ARC_LENGTH);
- } else if (endAngle >= ARC_LENGTH && startAngle >= ARC_LENGTH) {
- endSpeed = 0
- startSpeed = 0
- startAngle = 0;
- endAngle = 0;
- }
- rotate += ROTATE_INTERVAL;
- _ctx.update()
- // clearTimeout(timer)
-
- timer = setTimeout(() => draw(), 24)
- }
- draw()
- }
- const spinner = () => {
- if (ctx == null) return
- let _ctx = ctx!
- const steps = 12;
- let step = 0;
- const lineWidth = size / 10;
- // 线长度和距离圆心距离
- const length = size / 4 - lineWidth;
- const offset = size / 4;
-
-
- function generateColorGradient(hex: string, steps: number):string[]{
- const colors:string[] = []
- const _color = tinyColor(hex)
- for (let i = 1; i <= steps; i++) {
- _color.setAlpha(i/steps);
- colors.push(_color.toRgbString());
- }
- return colors
- }
- let colors = computed(():string[]=> generateColorGradient(color.value, steps))
-
- function draw() {
- if(tick == 0) return
- _ctx.reset();
- for (let i = 0; i < steps; i++) {
- const stepAngle = 360 / steps
- const angle = stepAngle * i;
- const index =(steps + i - (step % steps)) % steps
- // 正余弦
- const sin = Math.sin(angle / 180 * Math.PI);
- const cos = Math.cos(angle / 180 * Math.PI);
- // 开始绘制
- _ctx.lineWidth = lineWidth;
- _ctx.lineCap = 'round';
- _ctx.beginPath();
- _ctx.moveTo(size / 2 + offset * cos, size / 2 + offset * sin);
- _ctx.lineTo(size / 2 + (offset + length) * cos, size / 2 + (offset + length) * sin);
- _ctx.strokeStyle = colors.value[index]
- _ctx.stroke();
- }
- step += 1
- _ctx.update()
- timer = setTimeout(() => draw(), 1000/10)
- }
- draw()
- }
- const clear = () => {
- clearTimeout(timer)
- drawing = false
- tick = 0
- if(ctx == null) return
- // ctx?.reset()
- // ctx?.update()
- setTimeout(()=>{
- ctx!.reset()
- ctx!.update()
- },1000)
-
- }
- const failed = () => {
- if(tick == 1) {
- drawing = false
- }
- clearTimeout(timer)
- tick = 2
- if (ctx == null || drawing) return
- let _ctx = ctx!
- const _size = size * 0.61
- const _sizeX = _size * 0.65
- const lineWidth = _size / 6;
- const lineLength = Math.ceil(Math.sqrt(Math.pow(_sizeX, 2) * 2))
-
- const startX1 = (width - _sizeX) * 0.5
- const startY = (height - _sizeX) * 0.5
- const startX2 = startX1 + _sizeX
-
- // 添加圆的参数
- const centerX = width / 2;
- const centerY = height / 2;
- const radius = (_size * Math.sqrt(2)) / 2 + lineWidth / 2;
- const totalSteps = 36;
-
- function generateSteps(stepsCount: number):Point[][] {
-
- const halfStepsCount = stepsCount / 2;
- const step = lineLength / halfStepsCount //Math.floor(lineLength / 18);
- const steps:Point[][] = []
- for (let i = 0; i < stepsCount; i++) {
- const sub:Point[] = []
- const index = i % 18 + 1
- if(i < halfStepsCount) {
-
- const x2 = Math.sin(45 * Math.PI / 180) * step * index + startX1
- const y2 = Math.cos(45 * Math.PI / 180) * step * index + startY
-
- const start1 = {
- x1: startX1,
- y1: startY,
- x2,
- y2,
- } as Point
-
- sub.push(start1)
- } else {
- sub.push(steps[halfStepsCount-1][0])
- const x2 = Math.sin((45 - 90) * Math.PI / 180) * step * index + startX2
- const y2 = Math.cos((45 - 90) * Math.PI / 180) * step * index + startY
-
- const start2 = {
- x1: startX2,
- y1: startY,
- x2,
- y2,
- } as Point
- sub.push(start2)
- }
- steps.push(sub)
- }
-
- return steps
- }
- const steps = generateSteps(36);
- function draw(){
- if(steps.length == 0 || tick == 0) {
- clearTimeout(timer)
- return
- }
- const drawStep = steps.shift()!
- _ctx.reset()
- _ctx.lineWidth = lineWidth;
- _ctx.strokeStyle = color.value;
-
- // 绘制逐渐显示的圆
- _ctx.beginPath();
- _ctx.arc(centerX, centerY, radius, 0, (2 * Math.PI) * (totalSteps - steps.length) / totalSteps);
- _ctx.lineWidth = lineWidth;
- _ctx.strokeStyle = color.value;
- _ctx.stroke();
-
- // 绘制X
- _ctx.beginPath();
- drawStep.forEach(item => {
- _ctx.beginPath();
- _ctx.moveTo(item.x1, item.y1)
- _ctx.lineTo(item.x2, item.y2)
- _ctx.stroke();
- })
- _ctx.update()
- timer = setTimeout(() => draw(), 1000/30)
- }
- draw()
- }
- const destroy = () => {
- isDestroy.value = true;
- clear()
- }
- const play = () => {
- if(tick == 2) {
- drawing = false
- }
- if(drawing) return
- tick = 1
- if(width == 0 || height == 0) return
- if (type == 'circular') {
- circular()
- } else if (type == 'spinner') {
- spinner()
- }
- drawing = true
- }
-
- const _watch = (v:boolean) => {
- if(isDestroy.value) return
- if (v) {
- play()
- } else {
- failed()
- }
- }
- const stopWatchState = watch(state, _watch)
-
- const ob = new UniResizeObserver((entries: UniResizeObserverEntry[])=>{
- if(isDestroy.value) return
- entries.forEach(entry => {
- if(isDestroy.value) return
- const rect = entry.target.getBoundingClientRect();
- if(rect.width > 0 && rect.height > 0) {
- updateSize();
- if(tick == 1) {
- play()
- state.value = true
- } else if(tick == 2) {
- failed()
- state.value = false
- } else if(immediate && !init) {
- _watch(state.value)
- init = true
- }
- }
- })
- })
-
- const stopWatchElement = watch(element, (el:UniElement|null) => {
- if(el == null || isDestroy.value) return
- ob.observe(el)
- })
-
- onUnmounted(()=>{
- stopWatchState()
- stopWatchElement()
- clear()
- ob.disconnect()
- })
- return {
- state,
- play,
- failed,
- clear,
- color,
- destroy
- } as useLoadingReturnType
- }
|