UNPKG

attractive-alert

Version:

一个美观易用的弹框库,提供丰富的图标和动画效果

901 lines (780 loc) 30.7 kB
/** * Copyright 2025 王小玗 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Attractive Alert库 * 一个美观易用的弹框库 * 可拓展性高 */ class Atal { constructor(executor) { this.state = 'pending'; this.value = null; this.okCallbacks = []; this.cancelCallbacks = []; this.customCallbacks = []; this.createdCallback = () => {}; this.closeCallback = () => {}; const executeCallbacks = (callbacks, value = null) => { const copies = callbacks.slice(); callbacks.length = 0; copies.forEach(cb => { try { value !== null ? cb(value) : cb(); } catch (error) { console.error('Atal callback error:', error); } }); }; const resolve = (type, value = null) => { if (this.state !== 'pending') return; this.state = type; this.value = value; if (type === 'ok') { executeCallbacks(this.okCallbacks); } else if (type === 'cancel') { executeCallbacks(this.cancelCallbacks); } else if (type === 'custom') { executeCallbacks(this.customCallbacks, value); } try { this.closeCallback(); } catch (error) { console.error('Atal close callback error:', error); } }; const ok = () => resolve('ok'); const cancel = () => resolve('cancel'); const custom = (value) => resolve('custom', value); const close = (fn) => { this.closeCallback = fn; }; const created = (value) => { this.createdCallback(value); }; try { executor(ok, cancel, custom, created, close); } catch (error) { cancel(); } this._executeCallbacks = executeCallbacks; } ok(callback) { if (this.state === 'ok') { callback(); } else if (this.state === 'pending') { this.okCallbacks.push(callback); } return this; } cancel(callback) { if (this.state === 'cancel') { callback(); } else if (this.state === 'pending') { this.cancelCallbacks.push(callback); } return this; } custom(callback) { if (this.state === 'custom') { callback(this.value); } else if (this.state === 'pending') { this.customCallbacks.push(callback); } return this; } created(callback) { this.createdCallback = callback; return this; } close(type = 'cancel', value = null) { if (this.state !== 'pending') return this; try { this.closeCallback(); } catch (error) { console.error('Atal close callback error:', error); } if (type === 'ok') { this.state = 'ok'; this._executeCallbacks(this.okCallbacks); } else if (type === 'cancel') { this.state = 'cancel'; this._executeCallbacks(this.cancelCallbacks); } else if (type === 'custom') { this.state = 'custom'; this.value = value; this._executeCallbacks(this.customCallbacks, value); } return this; } static alert() { return new Atal((ok, cancel, custom, created, close) => { let options; if (arguments.length === 1 && typeof arguments[0] === 'object') { options = { ...Atal.defaults, ...arguments[0] }; } else { options = { ...Atal.defaults }; if (arguments.length >= 1) options.title = arguments[0]; if (arguments.length >= 2) options.content = arguments[1]; if (arguments.length >= 3) options.icon = arguments[2]; } options.buttons = Atal.processButtons(options.buttons); Atal.addStyles(); const alertElement = Atal.createAlertHTML(options); document.body.appendChild(alertElement); const overlayElement = alertElement.querySelector('.atal-overlay'); // 如果不启用动画,移除所有动画样式 if (!options.animateIcon) { const icon = overlayElement.querySelector('.atal-icon'); if (icon) { const paths = icon.querySelectorAll('*'); paths.forEach(path => { path.style.animation = 'none'; path.style.strokeDasharray = 'none'; path.style.strokeDashoffset = '0'; if (path.classList.contains('dot')) { path.style.opacity = '1'; } }); } } // 设置定时器 if (options.timer) { setTimeout(() => { Atal.closeAlert(alertElement); ok(); }, options.timer); } // 绑定按钮事件 const confirmBtn = overlayElement.querySelector('.atal-confirm'); if (confirmBtn) { confirmBtn.addEventListener('click', () => { Atal.closeAlert(alertElement); ok(); }); } const cancelBtn = overlayElement.querySelector('.atal-cancel'); if (cancelBtn) { cancelBtn.addEventListener('click', () => { Atal.closeAlert(alertElement); cancel(); }); } // 绑定自定义按钮事件 const customBtns = overlayElement.querySelectorAll('.atal-custom'); customBtns.forEach((btn, index) => { btn.addEventListener('click', () => { Atal.closeAlert(alertElement); // 传递自定义按钮的索引和配置信息 custom({ index: index, button: options.buttons.custom[index] }); }); }); // 点击遮罩层关闭 overlayElement.addEventListener('click', (e) => { if (e.target === overlayElement) { Atal.closeAlert(alertElement); cancel(); } }); close(() => Atal.closeAlert(alertElement)); Promise.resolve().then(() => { created(alertElement); }); }); } // 静态方法 - 处理按钮配置 static processButtons(buttonsConfig) { const result = { confirm: null, cancel: null, custom: [] }; // 处理确认按钮 if (buttonsConfig.confirm !== undefined && buttonsConfig.confirm !== null) { if (buttonsConfig.confirm === true) { result.confirm = Atal.defaultButtons.confirm; } else if (typeof buttonsConfig.confirm === 'string' || typeof buttonsConfig.confirm === 'object') { result.confirm = buttonsConfig.confirm; } } // 处理取消按钮 if (buttonsConfig.cancel !== undefined && buttonsConfig.cancel !== null) { if (buttonsConfig.cancel === true) { result.cancel = Atal.defaultButtons.cancel; } else if (typeof buttonsConfig.cancel === 'string' || typeof buttonsConfig.cancel === 'object') { result.cancel = buttonsConfig.cancel; } } // 处理自定义按钮 if (Array.isArray(buttonsConfig.custom)) { result.custom = buttonsConfig.custom.filter(btn => btn !== null && btn !== undefined && btn !== false ); } return result; } // 静态方法 - 设置原生alert static native() { window.alert = Atal.alert; } // 静态方法 - 关闭弹窗 static closeAlert(element) { if (element && element.parentNode) { element.parentNode.removeChild(element); } } // 静态方法 - 添加样式 static addStyles() { if (document.getElementById('atal-styles')) return; const style = document.createElement('style'); style.id = 'atal-styles'; style.textContent = Atal.styles; document.head.appendChild(style); } // 静态方法 - 创建弹窗HTML static createAlertHTML(options) { let iconHTML; let iconColor; if (typeof options.icon === 'object') { iconHTML = options.icon ? Atal.icons[options.icon.type](options.icon.color) || '' : ''; iconColor = options.icon.color; } else if (typeof options.icon === 'string') { iconHTML = options.icon ? Atal.icons[options.icon](Atal.getIconColor(options.icon)) || '' : ''; iconColor = Atal.getIconColor(options.icon); } else { iconHTML = ''; iconColor = Atal.getIconColor(''); } let content = ''; if (options.content instanceof HTMLElement) { content = '<div id="atal-custom-content"></div>'; } else if (typeof options.content === 'string') { content = options.html ? options.content : `<p>${Atal.escapeHTML(options.content)}</p>`; } // 生成自定义按钮HTML let customButtonsHTML = ''; if (options.buttons.custom && options.buttons.custom.length > 0) { customButtonsHTML = options.buttons.custom.map((button, index) => { return `<button class="atal-button atal-custom" data-index="${index}" style="--icon-color: ${button.color || iconColor}">${button.text}</button>`; }).join(''); } let confirmButtonHTML = ''; if (options.buttons.confirm) { if (typeof options.buttons.confirm === "object") { confirmButtonHTML = `<button class="atal-button atal-confirm" style="--icon-color: ${options.buttons.confirm.color || iconColor}">${options.buttons.confirm.text}</button>`; } else if (typeof options.buttons.confirm === "string") { confirmButtonHTML = `<button class="atal-button atal-confirm" style="--icon-color: ${iconColor}">${options.buttons.confirm}</button>`; } } let cancelButtonHTML = ''; if (options.buttons.cancel) { if (typeof options.buttons.cancel === "object") { cancelButtonHTML = `<button class="atal-button atal-cancel" style="--icon-color: ${options.buttons.cancel.color || iconColor}">${options.buttons.cancel.text}</button>`; } else if (typeof options.buttons.cancel === "string") { cancelButtonHTML = `<button class="atal-button atal-cancel" style="--icon-color: ${iconColor}">${options.buttons.cancel}</button>`; } } const alertElement = document.createElement('div'); alertElement.innerHTML = ` <div class="atal-overlay"> <div class="atal-container"> <div class="atal-header"> ${iconHTML} <h3 class="atal-title">${options.title}</h3> </div> <div class="atal-content" id="atal-content"> ${content} </div> <div class="atal-footer"> ${cancelButtonHTML} ${customButtonsHTML} ${confirmButtonHTML} </div> </div> </div> `; // 处理HTML元素内容 if (options.content instanceof HTMLElement) { const contentContainer = alertElement.querySelector('#atal-custom-content'); if (contentContainer) { contentContainer.appendChild(options.content); } } return alertElement; } // 静态方法 - 获取图标颜色 static getIconColor(icon) { const colors = { success: '#4CAF50', // 绿色 error: '#F44336', // 红色 warning: '#FF9800', // 橙色 info: '#2196F3', // 蓝色 question: '#9C27B0', // 紫色 heart: '#E91E63', // 粉色 star: '#FFC107', // 黄色 download: '#2196F3', // 蓝色 upload: '#2196F3', // 蓝色 key: '#FF9800' // 橙色 }; return colors[icon] || '#2196F3'; // 默认蓝色 } // 静态方法 - HTML转义 static escapeHTML(str) { return str.replace(/[&<>"']/g, function (match) { switch (match) { case '&': return '&amp;'; case '<': return '&lt;'; case '>': return '&gt;'; case '"': return '&quot;'; case "'": return '&#x27;'; default: return match; } }); } static at(defaultOptions) { // 创建并返回一个函数,该函数可以使用预设配置 const atAtal = function() { let options; // 处理不同参数形式 if (arguments.length === 1 && typeof arguments[0] === 'object') { // 单个对象参数 options = { ...defaultOptions, ...arguments[0] }; } else { // 多个参数形式 options = { ...defaultOptions }; if (arguments.length >= 1) options.title = arguments[0]; if (arguments.length >= 2) options.content = arguments[1]; if (arguments.length >= 3) options.icon = arguments[2]; } // 调用原始的Atal.alert方法 return Atal.alert(options); }; // 复制静态方法和属性 Object.keys(Atal).forEach(key => { if (typeof Atal[key] === 'function' && key !== 'at') { atAtal[key] = Atal[key]; } }); // 添加一个方法来更新默认配置 atAtal.updateDefaults = function(newDefaults) { Object.assign(defaultOptions, newDefaults); }; // 添加一个方法来获取当前配置 atAtal.getDefaults = function() { return { ...defaultOptions }; }; // 添加at方法,允许链式创建更深层次at atAtal.at = function(newDefaults) { return Atal.at({ ...defaultOptions, ...newDefaults }); }; return atAtal; } static setDefaults(options) { if (options) { Atal.defaults = {...Atal.defaults, ...options}; } else { return Atal.defaults; } } static setDefaultButtons(confirm, cancel) { if (confirm === undefined && cancel === undefined) return Atal.defaultButtons; if (confirm !== undefined) Atal.defaultButtons.confirm = confirm; if (cancel !== undefined) Atal.defaultButtons.cancel = cancel; } } // 静态属性 - 默认配置 Atal.defaults = { title: '', content: '', icon: '', buttons: { confirm: true, // 使用布尔值表示使用默认按钮 cancel: false, // 使用布尔值表示使用默认按钮 custom: [] // 自定义按钮数组 }, timer: null, animateIcon: true, html: false }; // 静态属性 - 默认按钮配置 Atal.defaultButtons = { confirm: 'OK', cancel: 'Cancel' }; // 静态属性 - 图标 Atal.icons = { success: (color) => ` <svg class="atal-icon success-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/> <path class="check" d="M20,32l10,10,14-20" style="stroke-dasharray: 44; stroke-dashoffset: 44;"/> </svg> `, error: (color) => ` <svg class="atal-icon error-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/> <path class="line line1" d="M24,24L40,40" style="stroke-dasharray: 22.6; stroke-dashoffset: 22.6;"/> <path class="line line2" d="M40,24L24,40" style="stroke-dasharray: 22.6; stroke-dashoffset: 22.6;"/> </svg> `, warning: (color) => ` <svg class="atal-icon warning-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="triangle" d="M32,10L4,54H60L32,10Z" style="stroke-dasharray: 160; stroke-dashoffset: 160;"/> <path class="exclamation" d="M32,26V40M32,44V44" style="stroke-dasharray: 22; stroke-dashoffset: 22;"/> <path class="dot" d="M32,46a1,1,0,1,1-1,1A1,1,0,0,1,32,46Z" fill="${color}" style="opacity: 0;"/> </svg> `, info: (color) => ` <svg class="atal-icon info-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/> <path class="dot" d="M32,16a2,2,0,1,1-2,2A2,2,0,0,1,32,16Z" style="opacity: 0;"/> <path class="line" d="M32,28V44" style="stroke-dasharray: 20; stroke-dashoffset: 20; stroke-width: 4;"/> </svg> `, question: (color) => ` <svg class="atal-icon question-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/> <path class="question-mark" d="M24,24c0-4,4-8,8-8,6,0,8,4,8,8,0,4-4,6-6,8-2,2-2,6-2,6" style="stroke-dasharray: 60; stroke-dashoffset: 60;"/> <path class="dot" d="M32,48a2,2,0,1,1-2,2A2,2,0,0,1,32,48Z" fill="${color}" style="opacity: 0;"/> </svg> `, heart: (color) => ` <svg class="atal-icon heart-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" fill="none"> <path class="heart" d="M32,14c5.5-11,22-8.5,22,5.5,0,16.5-22,27.5-22,27.5S10,36,10,19.5C10,6.5,26.5,3,32,14Z" style="stroke-dasharray: 142; stroke-dashoffset: 142;"/> </svg> `, star: (color) => ` <svg class="atal-icon star-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="star" d="M32,8l8,16,18,2-12,14,2,18-16-10-16,10,2-18L6,26l18-2L32,8Z" style="stroke-dasharray: 181; stroke-dashoffset: 181;"/> </svg> `, download: (color) => ` <svg class="atal-icon download-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/> <path class="arrow" d="M32,20V44M32,44L24,36M32,44L40,36" style="stroke-dasharray: 48; stroke-dashoffset: 48;"/> <path class="line" d="M20,52H44" style="stroke-dasharray: 24; stroke-dashoffset: 24;"/> </svg> `, upload: (color) => ` <svg class="atal-icon upload-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/> <path class="arrow" d="M32,44V20M32,20L24,28M32,20L40,28" style="stroke-dasharray: 48; stroke-dashoffset: 48;"/> <path class="line" d="M20,52H44" style="stroke-dasharray: 24; stroke-dashoffset: 24;"/> </svg> `, key: (color) => ` <svg class="atal-icon key-icon" viewBox="0 0 64 64" stroke="${color}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"> <path class="circle" d="M32,4A28,28,0,1,1,4,32,28,28,0,0,1,32,4Z" style="stroke-dasharray: 180; stroke-dashoffset: 180;"/> <path class="key-handle" d="M44,32a6,6,0,0,1-6,6,6,6,0,0,1-6-6,6,6,0,0,1,6-6A6,6,0,0,1,44,32Z" style="stroke-dasharray: 38; stroke-dashoffset: 38;"/> <path class="key-teeth" d="M44,32H52" style="stroke-dasharray: 8; stroke-dashoffset: 8;"/> <path class="key-shaft" d="M32,32H40" style="stroke-dasharray: 8; stroke-dashoffset: 8;"/> </svg> ` }; // 静态属性 - 样式 Atal.styles = ` .atal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } .atal-container { background-color: white; border-radius: 16px; width: 90%; max-width: 420px; box-shadow: 0 12px 36px rgba(0, 0, 0, 0.15); overflow: hidden; animation: atalFadeIn 0.3s ease-out; } .atal-header { padding: 30px 25px 15px; text-align: center; } .atal-icon { width: 80px; height: 80px; margin-bottom: 20px; transform-origin: center; } .atal-title { margin: 0; font-size: 1.75rem; color: #333; font-weight: 600; letter-spacing: 0.5px; } .atal-content { padding: 15px 30px; text-align: center; color: #555; font-size: 1.1rem; line-height: 1.6; } .atal-footer { display: flex; justify-content: center; padding: 25px; gap: 15px; flex-wrap: wrap; } .atal-button { padding: 12px 28px; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); font-weight: 500; letter-spacing: 0.5px; min-width: 80px; } .atal-confirm { background-color: var(--icon-color); color: white; } .atal-confirm:hover { background-color: var(--icon-color); filter: brightness(0.9); transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12); } .atal-cancel { background-color: #f5f5f5; color: #666; border: 1px solid #ddd; } .atal-cancel:hover { background-color: #e5e5e5; transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); } .atal-custom { background-color: var(--icon-color, #6c757d); color: white; } .atal-custom:hover { background-color: var(--icon-color, #5a6268); filter: brightness(0.9); transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12); } @keyframes atalFadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } /* 图标动画 */ /* 成功图标动画 */ .success-icon .circle { animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .success-icon .check { animation: check-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards; } /* 错误图标动画 */ .error-icon .circle { animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .error-icon .line { animation: error-line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards; } .error-icon .line2 { animation-delay: 0.8s; } /* 警告图标动画 */ .warning-icon .triangle { animation: triangle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .warning-icon .exclamation { animation: exclamation-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards; } .warning-icon .dot { animation: dot-anim 0.3s ease-out 0.8s forwards; } /* 信息图标动画 */ .info-icon .circle { animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .info-icon .line { animation: line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards; } .info-icon .dot { animation: dot-anim 0.3s ease-out 0.8s forwards; } /* 问题图标动画 */ .question-icon .circle { animation: circle-anim 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .question-icon .question-mark { animation: question-anim 0.6s cubic-bezier(0.65, 0, 0.45, 1) 0.6s forwards; } .question-icon .dot { animation: dot-anim 0.3s ease-out 1.2s forwards; } /* 心形图标动画 */ .heart-icon .heart { animation: heart-anim 0.5s cubic-bezier(0.65, 0, 0.45, 1) forwards; transform-origin: center; } /* 星星图标动画 */ .star-icon .star { animation: star-anim 0.8s cubic-bezier(0.65, 0, 0.45, 1) forwards; transform-origin: center; } /* 动画关键帧 */ @keyframes circle-anim { 0% { stroke-dashoffset: 180; } 100% { stroke-dashoffset: 0; } } @keyframes check-anim { 0% { stroke-dashoffset: 44; } 100% { stroke-dashoffset: 0; } } @keyframes error-line-anim { 0% { stroke-dashoffset: 22.6; } 100% { stroke-dashoffset: 0; } } @keyframes triangle-anim { 0% { stroke-dashoffset: 150; } 100% { stroke-dashoffset: 0; } } @keyframes exclamation-anim { 0% { stroke-dashoffset: 22; } 100% { stroke-dashoffset: 0; } } @keyframes line-anim { 0% { stroke-dashoffset: 22; } 100% { stroke-dashoffset: 0; } } @keyframes dot-anim { 0% { opacity: 0; transform: scale(0.3); } 50% { opacity: 1; transform: scale(1.2); } 100% { opacity: 1; transform: scale(1); } } @keyframes question-anim { 0% { stroke-dashoffset: 60; } 100% { stroke-dashoffset: 0; } } @keyframes heart-anim { 0% { stroke-dashoffset: 120; transform: scale(0.8); } 50% { stroke-dashoffset: 60; transform: scale(1.1); } 100% { stroke-dashoffset: 0; transform: scale(1); } } @keyframes star-anim { 0% { stroke-dashoffset: 150; transform: scale(0.8) rotate(-30deg); opacity: 0; } 50% { stroke-dashoffset: 75; transform: scale(1.1) rotate(10deg); opacity: 1; } 100% { stroke-dashoffset: 0; transform: scale(1) rotate(0); opacity: 1; } } /* 下载图标动画 */ .download-icon .circle { animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .download-icon .arrow { animation: download-arrow-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) 0.4s forwards; } .download-icon .line { animation: download-line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards; } /* 上传图标动画 */ .upload-icon .circle { animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .upload-icon .arrow { animation: upload-arrow-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) 0.4s forwards; } .upload-icon .line { animation: upload-line-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards; } /* 钥匙图标动画 */ .key-icon .circle { animation: circle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .key-icon .key-handle { animation: key-handle-anim 0.4s cubic-bezier(0.65, 0, 0.45, 1) 0.4s forwards; } .key-icon .key-teeth { animation: key-teeth-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards; } .key-icon .key-shaft { animation: key-shaft-anim 0.3s cubic-bezier(0.65, 0, 0.45, 1) 1.1s forwards; } /* 新增动画关键帧 */ @keyframes download-arrow-anim { 0% { stroke-dashoffset: 48; } 100% { stroke-dashoffset: 0; } } @keyframes download-line-anim { 0% { stroke-dashoffset: 24; } 100% { stroke-dashoffset: 0; } } @keyframes upload-arrow-anim { 0% { stroke-dashoffset: 48; } 100% { stroke-dashoffset: 0; } } @keyframes upload-line-anim { 0% { stroke-dashoffset: 24; } 100% { stroke-dashoffset: 0; } } @keyframes key-handle-anim { 0% { stroke-dashoffset: 38; transform: scale(0.8); } 100% { stroke-dashoffset: 0; transform: scale(1); } } @keyframes key-teeth-anim { 0% { stroke-dashoffset: 8; } 100% { stroke-dashoffset: 0; } } @keyframes key-shaft-anim { 0% { stroke-dashoffset: 8; } 100% { stroke-dashoffset: 0; } } `; window.Atal = Atal;