前端雪碧图
东北小麦客 2024-03-15 JavaScriptsprite精灵图雪碧图
# playAnimation.ts
/**
* 菜单子icon的精灵图为 7440px 一共 31 帧 从第0帧开始, 直接取31 动画会越界, 动画不连贯, 所以菜单子icon的帧数 = 31 -1
* v2(动态调整百分比) 由v1版的 动态调整像素 改为 动态调整百分比
* defaultWalkCnt 帧数
* @params targetDom; 播放动画的img元素
* @params defaultWalkCnt; 帧数
*/
// const defaultWalkCnt = 31 - 1; // 一共 31 帧, 从第0帧开始, 直接取31 动画会越界, 动画不连贯
export function registerAnimation(targetDom, defaultWalkCnt = 30) {
const walkCnt = defaultWalkCnt;
let cur = 0;
let requestAnimationFrameId;
let stepSize = 1;
const dom = targetDom as HTMLImageElement;
if (!dom) {
// console.log('无效的dom元素');
return {};
}
function render() {
cur += stepSize;
if (cur >= walkCnt || cur < 0) {
cur = cur < 0 ? 0 : walkCnt;
cancelRun();
return;
}
dom.style.objectPosition = `0% ${(cur / walkCnt) * 100}%`;
requestAnimationFrameId = requestAnimationFrame(render);
}
/**
* 进入动画
*/
function enter() {
dom.style.objectPosition = '0% 0%';
stepSize = 1;
requestAnimationFrameId = requestAnimationFrame(render);
}
/**
* 退出动画
*/
function leave() {
stepSize = -1;
requestAnimationFrameId = requestAnimationFrame(render);
}
function cancelRun() {
cancelAnimationFrame(requestAnimationFrameId);
dom.style.objectPosition = `0% ${(cur / walkCnt) * 100}%`;
}
function addListen() {
dom.addEventListener('mouseenter', enter);
dom.addEventListener('mouseleave', leave);
}
function removeListen() {
dom.removeEventListener('mouseenter', enter);
dom.removeEventListener('mouseleave', leave);
}
return {
addListen,
removeListen,
};
}
# DynaicLoading.vue
<template>
<div
v-show="isLoading"
class="common-min"
:style="{ backgroundPositionY: `${(placeIconBiasIdx / walkCnt) * 100}%` }"
></div>
<img v-show="!isLoading" ref="imgDom" draggable="false" class="common-icon" />
</template>
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, toRefs, watch } from 'vue';
import { registerAnimation } from './playAnimation';
type MenuDataType = {
/**
* 中文标题
*/
title: string;
/**
* icon路径支持网络url、"/"前端项目public文件夹两种类型值
*/
icon: any;
/**
* 路由导航
*/
routerNavigator?: string; // 路由导航
/**
* 标识是否有该子菜单的权限
*/
hasPerm?: boolean;
/**
* 排序
*/
no?: number;
/**
* 英文标题
*/
eTitle?: string;
/**
* 决定 placeIcon 雪碧图上的所取的位置
*/
placeIconIdx: number;
/**
* 目的页面路由
*/
routerName?: string;
};
const walkCnt = 12 - 1; // 一共12个子菜单icon, placeIconBiasIdx 下标从0开始(placeIconBiasIdx +1 的会落掉第一个子菜单icon), 直接取12 最后一个图标会越界
const getDefaultPngUrl = () => {
return new URL(`../img/default.png`, import.meta.url).href;
};
const props = withDefaults(
defineProps<{
iconSrc: string;
menu?: MenuDataType;
placeIconBiasIdx?: number; // 决定 placeIcon 雪碧图上的所取的位置
}>(),
{ iconSrc: '', placeIconBiasIdx: 0 },
);
const emits = defineEmits(['imgIsloaded']);
const { menu, placeIconBiasIdx } = toRefs(props);
const imgDom = ref(null);
const isLoading = ref(true);
const cbs = ref(null);
function setbg() {
if (!imgDom.value) {
return;
}
// 监听图片的加载
imgDom.value.onload = () => {
isLoading.value = false;
cbs.value = registerAnimation(imgDom.value);
emits('imgIsloaded', true);
};
imgDom.value.onerror = () => {
isLoading.value = false;
emits('imgIsloaded', true);
imgDom.value.src = getDefaultPngUrl();
};
imgDom.value.setAttribute('fetchpriority', 'high'); // 提升图标加载优先级 存在兼容性问题(苹果系列浏览器未完全支持)
imgDom.value.src = props.iconSrc;
}
// 根据权限动态注册icon动画
watch(
() => [cbs.value, menu.value],
([cdFn, menu]) => {
if (imgDom.value && cdFn) {
if (menu.hasPerm) {
cdFn.addListen();
} else {
cdFn.removeListen();
}
}
},
{ deep: true },
);
onMounted(() => {
// TODO
setTimeout(() => {
setbg();
}, 0);
});
onBeforeUnmount(() => {
imgDom.value = null;
});
</script>
<style lang="less" scoped>
.common-icon {
cursor: pointer;
display: block;
// margin: 0 max(18px, 0.9375vw);
width: max(240px, 12.5vw);
height: max(240px, 12.5vw);
object-fit: cover;
object-position: 0vw 0vw;
transform: translateZ(0);
}
.common-min {
cursor: pointer;
display: block;
// margin: 0 max(18px, 0.9375vw);
width: max(240px, 12.5vw);
height: max(240px, 12.5vw);
background-size: cover;
background-image: url('../img/place-icons.png'); // 动态改变 background-position-y 即可得到对应标题的图标
background-position-x: 0;
}
</style>