CSS 之 帧动画(Keyframe Animation)

您所在的位置:网站首页 动画Ganz CSS 之 帧动画(Keyframe Animation)

CSS 之 帧动画(Keyframe Animation)

2024-07-09 09:19| 来源: 网络整理| 查看: 265

一、简介

​ CSS 制作 Web 动画有两种方式: 帧动画(Keyframe Animation)和过渡动画(Transition Animation)。在不同的业务场景中,我们应该选择不同的动画方式,通常来说:对于交互元素,会使用过渡动画,而对于连续的装饰性元素,则应该使用帧动画。

​ 本文章将会仔细讲解帧动画的相关知识,让你对其有一个较为全面的了解。

二、帧动画 1、基本概念

​ 帧动画允许你通过指定 CSS 属性在不同时间点上的行为来创建动画效果,这些时间点被称为关键帧,所以又被称为关键帧动画。帧动画需要通过@keyframes来创建。

​ 关键帧动画的主要思想是在多个不同时间点的CSS代码块之间进行插值计算,这个插值计算由浏览器自动完成的,它会根据@keyframes中定义的多个代码块中的样式变化,自动创建样式变化的中间状态,从而样式能够平滑的变化。

​ 一个帧动画主要由两个关键部分组成:创建帧动画和使用帧动画。

2、创建帧动画 动画名称:

​ 在使用@keyframes创建关键帧动画时,会在其后面跟随一个自定义标识符,表示当前动画的名称,该名称可在其所在作用域内通过animation-name使用。但要注意的是:不要使用CSS的关键词来命名动画,例如auto、width等等,这可能会导致动画失效。

@keyframes 动画名称 {...} /* 正确命名 */ @keyframes fadeOut {...} /* 错误命名 */ @keyframes auto {...}

​ 动画名称标识符是严格区分大小写的,fadeOut与fadeout是两个不同的名称。而且如果多个动画使用相同的名称,则通过动画名称调用动画时,将以定义顺序中的最后一个动画为准。

/* 严格区分大小写 */ @keyframes fadeOut {...} /* 两者并不相同 */ @keyframes fadeout {...} /* 当命名重复时 以最后定义的为准 */ @keyframes fadeOut {...} 动画关键帧:

​ @keyframes内部没有时间的概念,它只有一个百分比完成的概念,n%表示的是动画完成的百分比,可以在0%~100%之间的任何一个位置定义,一个百分比表示一个关键帧,可以定义任意数量的关键帧。浏览器会在关键帧之间进行插值计算,使帧与帧之间的样式平滑的进行变化。

@keyframes 动画名称 { /* 动画关键帧 */ 0% { /* 样式 */ } /* 。。。 */ 100% { /* 样式 */ } }

​ 除了使用n%之外,还有两个关键字from、to可以使用,两者分别等同于0%和100%。

@keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } /* 等同于 */ @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }

​ 在 @keyframes 规则中的关键帧顺序并不重要,因为浏览器将按照百分比值的顺序播放,而不是按照它们在代码中出现的先后顺序播放。而且如果定义的关键帧百分比出现了重复的情况,则在执行时,会将重复的关键帧百分比进行合并,如果存在重复的属性,则属性值以最后定义的为准。

@keyframes move { 0% { background: red; transform: translateX(0); } 50% { background: yellow; transform: translateX(200px); } 100% { background: red; transform: translateX(0); } 50% { transform: translateX(50px); opacity: 0.5; } } /* 等同于 */ @keyframes move { 0% { background: red; transform: translateX(0); } 50% { background: yellow; transform: translateX(50px); opacity: 0.5; } 100% { background: red; transform: translateX(0); } }

​ 如果没有指定 0% / from 关键帧,以及100% / to 关键帧,则浏览器会以动画绑定元素的初始样式为内容,构造 0% / from 关键帧和100% / to 关键帧。

.d { width: 100px; height: 100px; background: red; animation: move 2s infinite; } @keyframes move { /* 未定义 0%和100% 关键帧 */ 50% { background: yellow; transform: translateX(200px); width: 200px; } } /* 等同于 */ @keyframes move { 0% { width: 100px; height: 100px; background: red; } 50% { background: yellow; transform: translateX(200px); width: 200px; } 100% { width: 100px; height: 100px; background: red; } } 关键帧中的!important:

​ 关键帧中的使用!important的样式属性将会被忽略,并不会起作用。因此!important此时不会抬高样式属性值的权重。因为在元素动画过程中,关键帧中声明的样式不在级联的上下文中,但本身的权重已经超过所有的普通权重样式,所以再无法通过!important来提高权重。

​ 但是动画所绑定的元素上的样式如果使用了!important,则该样式不会被动画中定义的样式属性值所影响,因为该权重超过了动画样式的权重。

@keyframes move { 0% { background: red; transform: translateX(0); } 50% { background: yellow; transform: translateX(50px); } 100% { background: red; transform: translateX(0); } } .d { width: 100px; height: 100px; /* 该样式使用了important提高权重 不会被动画关键帧中的样式所影响 */ background: red !important; animation: move 2s infinite; } 关键帧中的CSS自定义属性:

​ CSS的自定义属性是指以--开头的声明的变量,其可以通过var(--属性名)的形式,将自定义属性的属性值在作用域范围内的任何一个属性上使用:

.d { /* 定义自定义属性 */ --w: 20px; /* 使用自定义属性 等同于 width: 20px; */ width: var(--w); height: 20px; background: red; /* 使用自定义属性 等同于 border: 20px solid #ccc; */ border: var(--w) solid #ccc; }

​ 自定义属性可以在关键帧中使用,可以正常渲染,但是如果在关键帧中修改自定义属性的值,且元素样式中使用了该自定义属性,则浏览器无法对其进行插值计算,也就无法实现平滑的动画效果。主要原因为CSS 自定义属性的值对于浏览器来说相当于一个字符串,字符串的变化属于是不连贯的属性,无法进行插值计算。

@keyframes changeW { 50% { --w: 40px; } } .d { --w: 20px; width: var(--w); height: 20px; background: red; border: var(--w) solid #ccc; animation: changeW 2s infinite; }

​ 该问题可以通过CSS的@property特性来解决,但该特性属于实验性技术,有浏览器兼容性问题。因此此处就不展开叙述了,更多具体可查看: @property。

关键帧合并:

​ 如果@keyframes 中定义的多个关键帧存在样式属性重复,则可以将多个关键帧进行合并定义,减少代码重复。再将各个关键帧中不重复的样式属性进行单独定义,浏览器会将定义两次的关键帧内的样书进行合并,如果存在重复的属性,则属性值以最后定义的为准。

@keyframes move { /* 将多个关键帧中的重复样式属性合并定义 */ 0%, 100% { background: red; transform: translateX(0); width: 100px; } 50% { background: yellow; transform: translateX(200px); width: 200px; } /* 不重复的样式属性再进行单独定义 */ 100% { /* 该关键帧的定义位于后面 所以属性值优先级高 */ width: 150px; } } /* 等同于 */ @keyframes move { 0%{ background: red; transform: translateX(0); width: 100px; } 50% { background: yellow; transform: translateX(200px); width: 200px; } 100% { background: red; transform: translateX(0); width: 150px; } } 3、使用帧动画

​ @keyframes 仅仅是定义了在所在作用域内全局都可以使用的关键帧动画,定义好了动画的过程,但是其并不能自动应用。还需要通过CSS的animation属性来显式的指定关键帧动画应用的元素,以及一系列相关的动画属性。

.test-div { /* 使用动画 */ animation: fadeOut 500ms linear; } animation-name:

​ 该属性用于指定引用@keyframs动画的名称,必须严格遵守大小写,fadeOut与fadeout是两个不同的名称。

animation-name:fadeOut;

​ 该属性可以设置多个动画名称,属性值之间通过,进行分隔,表示同时引用多个动画。

animation-name:fadeOut, moveTo; animation-duration:

​ 该属性用于指定动画完成一个周期所需要的时间,值必须为非负数,单位为s或ms,默认为0。该时间定义了**@keyframes** 中的的时间轴,关键帧百分比值(n%)对应的时间节点是相对于该时间轴来计算的。

animation-duration: 2s; animation-duration: 200ms;

​ 如果该属性值为0,则动画效果是否可见取决于animation-fill-mode属性的值,而animation-fill-mode的表现效果又受到animaiton-direction属性的影响。具体情况分为下面四种:

如果animation-fill-mode: none;,无论animaiton-direction设置什么值,则动画无任何视觉效果。如果animation-fill-mode: forwards/both;,且animaiton-direction: normal/alternate;,则动画会直接显示最后一帧的样式效果。如果animation-fill-mode: forwards/both;,且animaiton-direction: reverse/alternate-reverse;,则动画会直接显示第一帧的样式效果。如果animation-fill-mode: backwards;,无论animaiton-direction设置什么值,则动画都会直接显示第一帧的样式效果。

​ 该属性可以设置多个运动时间,属性值之间通过,进行分隔,设置的多个属性值会按照顺序映射到对应的animation-name中的动画上。如果该属性值的数量等于animation-name属性值的数量,则两者一一对应;如果该属性值的数量小于animation-name属性值的数量,则多余的动画以重复该属性的属性值,使属性值与动画一一对应;如果该属性值的数量大于animation-name属性值的数量,则多余的属性值会被忽略。

.animated { animation-name: translate, rotate, scale; animation-duration: 1s; /* 属性值数量小于动画的数量 最终等同于 */ animation-duration: 1s, 1s, 1s; } .animated { animation-name: translate, rotate, scale; animation-duration: 1s, 2s; /* 属性值数量小于动画的数量 最终等同于 */ animation-duration: 1s, 2s, 1s; } .animated { animation-name: translate, rotate, scale; /* 属性值数量等于动画的数量 一一对应 */ animation-duration: 1s, 2s, 3s; } .animated { animation-name: translate, rotate, scale; animation-duration: 1s, 2s, 3s, 4s; /* 属性值数量大于动画的数量 最终等同于 */ animation-duration: 1s, 2s, 3s; /* 动画时间 4s 没有相应的关键帧动画,将会被忽略 */ } animation-timing-function:

​ 该属性用于指定动画执行时的速度曲线,属性值有:linear 、ease(默认值)、ease-in 、ease-out 和 ease-in-out ,以及steps()或cubic-bezier()(贝塞尔曲线函数)。

animation-timing-function:linear;

linear表示匀速运动,速度曲线是一条直线。

ease(默认值)表示慢-快-慢,初期速度很慢,然后先逐渐加速,再快速减速,最终缓慢结束。加速过程较缓,减速过程较快。

ease-out表示减速运动,先快后慢,初期速度很快,然后逐渐减速,最终缓慢结束。

ease-in表示加速运动,先慢后快,初期速度很慢,然后逐渐加速,最终快速结束。

ease-in-out表示慢-快-慢,初期速度慢,然后逐渐加速,中期速度最快,然后逐渐减速,最终缓慢结束。加速过程和减速过程相当。

linear() 函数定义了一个分段线性函数,可以在其各个点之间线性插值,从而允许你模拟出更复杂的动画效果,比如弹跳和弹簧等效果。JS或SVG可以通过linear曲线生成器 生成相应的曲线参数。

steps()函数定义了分步运动,其效果相当于逐步执行过渡动画,类似于帧动画,接受两个参数,第一个参数指定的是步数,第二个参数指定的是方向。第二个参数值可以是 jump-start 、jump-end 、 jump-none 、 jump-both 、start 和 end(默认值) ,其中 start 和 jump-start 表现行为一样,同样的 end 和 jump-end 表现行为一样。函数还有两个预定义的关键字:step-start 和 step-end。前者等同于 steps(1, start),而后者等同于 steps(1, end)。

​ jump-start表示第一步动画在开始时发生;jump-end 表示最后一步动画在结束时发生;jump-both 表示在 0% 和 100% 处均出现跳跃,相当于在动画过程中加上一步;表示两端均无跳跃,而是在 0% 处和 100% 处将值各保持 1/n 的时长。更多内容可以查看:easing-function。

cubic-bezier()用于定义贝塞尔曲线,贝塞尔曲线由 P0、P1、P2 和 P3 四个点进行定义。P0 和 P3 是曲线的起点和终点,在 CSS 中,这两个点是固定的,因为坐标是成比例。P0 为 (0, 0),代表初始时间和初始状态,P3 为 (1, 1),代表最终时间和最终状态。其余的中间点 P1(x1,y1)、P2(x2,y2) 是可以动态改变的两个点,对应 cubic-bezier(x1,y1,x2,y2) 中的四个参数,通过改变 P1、P2 两点的坐标值来动态生成的贝塞尔曲线表示动画中的速度变化。

​ 具体的贝塞尔曲线很难直接书写出来,我们可以通过贝塞尔曲线生成器,来获取对应的曲线值。或者可以通过浏览器调试工具对速度曲线进行调整。

​ 在固定y的情况下,x越大运动越慢,在固定x情况下,y越大运动越快。y如果是负数,则元素会变小,y’如果大于1,则会变大,但最终还会恢复为1。

在这里插入图片描述

animation-delay:

​ 该属性用于指定动画初始开始执行前需要延迟等待的时间,单位为s或ms,默认为0。如果动画会执行多次,也只会在初次执行时进行延迟等待,在后续执行时,不会再进行延迟等待。

animation-delay: 2s;

​ 该属性设置的属性值通常为正值,但如果属性值为负数,则浏览器会按照该负数的绝对值时间,跳转到该时间对应的动画节点处开始执行动画,类似于动画从中间节点开始执行。其实负值中的负号相当于一个信号,告诉浏览器将这个值视为偏移而不是延迟,偏移到该时间对应的动画节点处开始执行动画。

​ 如果指定的偏移时间(animtion-delay)大于动画单次迭代的持续时间,则需要进一步考虑是否有足够的迭代次数,使动画总的执行时间大于偏移时间。如果大于等于,则初始会偏移跳转到对应的时间节点处开始执行动画;如果小于,则动画不会有执行过程,而且直接跳转到动画的最终结束状态。

@keyframes move { 0% { background: red; transform: translateX(20px); } 50% { background: yellow; transform: translateX(200px); } 100% { background: red; transform: translateX(0); } } .d { width: 180px; height: 100px; margin-bottom: 10px; background: red; /* 执行时间5秒 匀速 保留结束和开始样式 执行1次 */ animation: move 5s linear both 1; /* 延迟时间为0 */ animation-delay: 0; } .d1 { /* 延迟时间为正数 正常延迟 */ animation-delay: 3s; } .d2 { /* 延迟时间为负数 且绝对值小于动画的总执行周期 延迟变为偏移 从中间1.5s节点开始执行 */ animation-delay: -1.5s; } .d3 { /* 延迟时间为负数 且绝对值大于动画的总执行周期 直接展示结束状态 */ animation-delay: -6s; }

页面效果:

在这里插入图片描述

​ 该属性还可以设置多个动画延迟时间,属性值之间通过,进行分隔,设置的多个属性值会按照顺序映射到对应的animation-name中的动画上。如果该属性值的数量等于animation-name属性值的数量,则两者一一对应;如果该属性值的数量小于animation-name属性值的数量,则多余的动画以重复该属性的属性值,使属性值与动画一一对应;如果该属性值的数量大于animation-name属性值的数量,则多余的属性值会被忽略。

.element { animation-name: color, translate, scale, rotate, opacity; animation-delay: .5s; /* 属性值数量相等 最终等同于 */ animation-delay: .5s, .5s, .5s, .5s, .5s; } .element { animation-name: color, translate, scale, rotate, opacity; animation-delay: .5s, 1s; /* 属性值数量小于动画的数量 最终等同于 */ animation-delay: .5s, 1s, .5s, 1s, .5s; } .element { animation-name: color, translate, scale, rotate, opacity; animation-delay: .5s, 1s, 1.5s, 2s, 2.5s; /* 属性值数量等于动画的数量 一一对应 */ animation-delay: .5s, 1s, 1.5s, 2s, 2.5s; } .element { animation-name: color, translate, scale, rotate, opacity; animation-delay: .5s, 1s, 1.5s, 2s, 2.5s, 3s; /* 属性值数量大于动画的数量 最终等同于 */ animation-delay: .5s, 1s, 1.5s, 2s, 2.5s; /* 多余的 3s 将会被忽略 */ } animation-iteration-count:

​ 该属性用于指定动画播放的次数,属性值可以为正整数或infinite(循环无限播放),默认为1,表示动画只执行一次。

animation-iteration-count: infinite; animation-direction:

​ 该属性用于指定动画的执行方向,其属性值有四种:

normal(默认值):表示动画正向执行,在每次执行中,都是从0%/from执行到100%/to。如果动画执行多次(animation-iteration-count),则一次执行结束后,下一次执行时会立即回到起点(0%/from),并重新从起点执行。reverse:表示动画反向执行,在每次执行中,都是从100%/to执行到0%/from。如果动画执行多次(animation-iteration-count),则一次执行结束后,下一次执行时会立即回到动画起点(100%/to),并重新从起点执行。alternate:表示动画正反交替执行。如果动画执行多次(animation-iteration-count),则奇数次执行按正向执行,而偶数次迭代按反向执行。alternate-reverse:表示动画反向交替执行。如果动画执行多次(animation-iteration-count),则奇数次执行按反向执行,而偶数次迭代按正向执行。

​ 总结一下:normal 和 reverse 两者表现行为恰好相反;alternate 和 alternate-reverse 两者表现行为恰好相反;alternate 同时具备 normal 和 reverse 两者表现特征。

animation-direction: altermate;

​ 当该属性值为reverse时,动画的时间函数效果也会被反转,因为设置的时间函数是相对于正向执行的。例如:ease-in 时间函数将变为 ease-out。

​ 该属性还会影响到animation-fill-mode属性的最终效果,具体可以看下面的animation-fill-mode章节。

animation-play-state:

​ 该属性用于指定动画是否执行,默认值为running,表示正在执行。可以设置属性值为paused,表示暂停动画,动画会暂停执行,并保留当前时间节点的样式状态。如果再次设置属性值为running,则会从当前状态继续执行。

// 暂停动画 animation-play-state: paused; // 运行动画 animation-play-state: running;

​ 该属性通常与:hover、:focus以及通过Js变更类名等操作结合使用,实现动画的暂停与播放。

@keyframes spin { to { rotate: 360deg; } } .animated { /* 默认正常执行动画 */ animation: spin 2s linear infinite; } .animated:hover { /* hover时暂停动画 离开hover状态时继续执行动画 */ animation-play-state: paused; }

​ 还有一种常见的应用场景是:在页面中存在大量的动画时,这些动画可能会消耗大量的 CPU 和 GPU 资源。即使用户当前并未查看页面上的某些动画,它们仍然在后台运行,可能导致性能下降。可以通过暂停那些不在视窗或容器可视区中的动画,只播放位于可视区域内的动画的方式,从而减轻系统负担,提高页面整体性能。

animation-fill-mode:

​ 该属性用于指定动画的填充模式,即在动画执行开始前/结束后,@keyframes 中声明的关键帧样式是否对目标元素有效。

​ 该属性有四个属性值:

none(默认值),表示在动画执行结束后@keyframes 中所有样式都无效,展示元素本身的样式。forwards(向前取样式),表示在动画结束后,将会应用动画结束时最后一帧的样式,也就是正常情况下@keyframes中100%/to关键帧的样式。该关键帧位于动画结束后的前面,所以是向前取样式。backwards(向后取样式),表示在动画开始前(包括animation-delay时间),将会应用动画开始时第一帧的样式,也就是正常情况下@keyframes中0%/from关键帧的样式。该关键帧位于动画开始前的后面,所以是向后取样式。both,表示同时遵循forwards和backwards的规则,在开始前应用动画开始时的样式,在结束后应用动画结束时的样式。 animation-fill-mode: both;

​ 该属性会被animation-direction属性和animation-iteration-count属性影响到,在使用时要注意。

​ 如果animation-direction属性值为reverse或alternate-reverse,动画反向播放,则此时动画第一帧的样式对应100%/to,动画的最后一帧对应0%/from。此时该属性设置属性值为forwards/backwards/both,具体的样式效果就会与正常情况相反。

​ animation-iteration-count属性也是类似的道理,其效果会改变动画的最后一帧对应的样式,从而会影响到animation-fill-mode属性的具体效果。例如下面的例子:动画的最后一个关键帧是 0% ,因为动画运行了两次,第一次迭代是从 0% 到 100% ,第二次迭代从 100% 到 0% 。

@keyframes ani { 0% { translate: -300px; } 100% { translate: 300px; } } .animated { animation: ani5s linear forwards; animation-fill-mode: forwards; animation-direction: alternate; animation-iteration-count: 2; }

​ animation-direction属性和animation-iteration-count属性与最后一帧的关系,如下图所示:

在这里插入图片描述

animation-composition:

​ 该属性用于指定动画合成的逻辑,即动画关键帧中的样式属性(效果值)与元素本身的同一样式属性(基础值)或者多个动画元素之前的相同样式属性的复合操作。

replace(替换,默认值):表示将效果值覆盖基础值,页面样式为效果值。或者是在多个动画之间,最后一个动画将完全替换之前的动画效果,也就是只有最后一个动画效果会起作用。add(叠加):表示将效果值添加到基础值之后,其效果相当于在基础值的的样式基础上继续叠加效果值的样式。或者是在多个动画之间,将多个动画的效果会叠加在一起,一起影响属性的最终效果。例如,如果一个动画元素向右移动 30px ,而另一个动画使元素向左移动 20px ,使用 add 复合操作之后,元素将向右移动 10px。accumulate(累计):表示将效果值与基础值累计相加。如果属性值为数值类型,则最终效果为两者相加后的样式。或者是在多个动画之间,将属性值累计相加。例如元素默认有一个 filter 属性的值为 blur(5px) , 动画1的 0% 位置有一个 blur(10px) ,动画2的 0% 位置有一个 blur(20px) ,设置该属性后,0% 关键帧的 filter 属性的复合值是 blur(35px)。 @keyframes move { 0% { transform: translateX(0px); width: 200px; } 50% { transform: translateX(200px); width: 400px; } 100% { transform: translateX(0); width: 200px; } } .d { width: 180px; height: 100px; background: red; animation: move 5s infinite; /* 设置效果为相加 则初始0% 和 结束100%时 的width的实际效果为380px 50%时的实际效果为580px */ animation-composition: accumulate; }

​ 该属性通常不在CSS中使用,都是在JavaScript中使用,结合Web动画API使用:

element.animate([ { transform: 'translateX(0px)' }, { transform: 'translateX(100px)' } ], { duration: 1000, composite: 'add' });

​ 更多相关内容可查看:animation-composition。

其他属性:

​ 除了上面那些标准动画属性之外,还有一些实验性动画属性,此处就不展开讲述了,感兴趣的可以自行查看:

animation-range:该属性用于设置动画附件范围沿其时间线的开始和结束位置,即动画沿时间线的开始和结束位置。animation-range-start:该属性用于设置动画的附件范围沿其时间线的开始位置,即动画沿时间线的开始位置。animation-range-end:该属性用于设置动画附件范围沿其时间线的结束位置,即动画沿时间线的结束位置。animation-timeline:该属性用于指定控制 CSS 动画进度的时间线。 animation属性简写:

​ 除animation-composition之外,其他animation的相关属性都可以简写到一起,简写属性如下:

animation: 动画名称 持续时间 运动曲线 延迟等待时间 执行次数 执行方向 填充模式 执行状态 animation: animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-direction animation-fill-mode animation-play-state;

​ 例如:

animation: move 2s linear 1s infinite alternate forwards running;

​ 简写属性中类型的属性值的顺序很重要,其中第一个可以被解析时间的值会被分配给animation-duration,再次出现的第二个值,才会分配给animation-delay。而其余属性值的顺序就不是那么重要了。

animation同时应用多个动画:

​ 如果要给同一个元素指定多个动画,则只需要在animation中,通过,分隔设置多个动画的相关动画属性即可。

animation: move 2s linear 1s infinite alternate forwards, bear 2s linear·;

​ 不同的动画之间的属性互不干扰,但具体应用到动画元素上的样式可能会有冲突覆盖。例如,一个动画试图将元素向左移动,而另一个动画同时试图将元素向右移动。此时的具体动画效果就取决于animation-composition属性的值。

​ 为了避免这些问题,要尽量减少多个动画同时作用于同一个元素的情况,可以通过分层动画的形式,拆分动画。例如:

@keyframes xAxis { 50% { animation-timing-function: ease-in; translate:450px 0; } } @keyframes yAxis { 50% { animation-timing-function: ease-out; translate: 0 -450px; } } .xAxis { animation: xAxis 2.5s infinite linear; } .yAxis { animation: yAxis 2.5s infinite linear; }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3