合同小程序前端代码仓库
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.
 
 
 
 
 

330 lines
7.6 KiB

// 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
}