最近,群里面的同学发了这么一个非常有意思是动画效果:

原效果地址 -- CodePen Demo -- Letter Hop

当然,原效果,主要使用了 GSAP 动画库以及一个 3D 文字 JavaScript 库:

import { Those3DTexts } from "https://cdn.skypack.dev/that-3d-text-library";
import { gsap } from "https://cdn.skypack.dev/gsap";
import { MotionPathPlugin } from "https://cdn.skypack.dev/gsap/MotionPathPlugin"; gsap.registerPlugin(MotionPathPlugin); const letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; // ....
// 剩余代码

但是,这个效果,其实本身并不复杂。

本文,我们将不借助任何动画库,尝试用最简单的 CSS 和 JavaScript 代码还原一个类似的 Emoji 3D 表情动画。

我们的目标是实现这么一个效果:

实现 3D Emoji 表情

首先,一个比较大的难点就是,我们如何使用 CSS 实现一个 3D 的 Emoji 表情?像是这样:

这里,其实使用的是个障眼法。核心就在于,使用多层 Emoji 表情的叠加,当叠加的间距合适的情况下,当观看角度处于一定的合理范围内时,视觉上就能得到一种 3D 的效果。

什么意思呢?之前在这篇文章中 -- 哇哦,巧用视觉障眼法,还原 3D 文字特效,有介绍过这个技巧。我们快速回顾一下:

合理的利用距离、角度及光影构建出不一样的 3D 效果。看看下面这个例子,只是简单是设置了三层字符,让它们在 Z 轴上相距一定的距离。

简单的伪代码如下:

<div>
<span class='C'>C</span>
<span class='S'>S</span>
<span class='S'>S</span>
<span></span>
<span class='3'>3</span>
<span class='D'>D</span>
</div>
$bright : #AFA695;
$gold : #867862;
$dark : #746853;
$duration : 10s;
div {
perspective: 2000px;
transform-style: preserve-3d;
animation: fade $duration infinite;
}
span {
transform-style: preserve-3d;
transform: rotateY(25deg);
animation: rotate $duration infinite ease-in; &:after, &:before {
content: attr(class);
color: $gold;
z-index: -1;
animation: shadow $duration infinite;
}
&:after{
transform: translateZ(-16px);
}
&:before {
transform: translateZ(-8px);
}
}
@keyframes fade {
// 透明度变化
}
@keyframes rotate {
// 字体旋转
}
@keyframes shadow {
// 字体颜色变化
}

简单捋一下,上述代码的核心就是:

  1. 父元素、子元素设置 transform-style: preserve-3d
  2. span 元素的两个伪元素复制两个相同的字,利用 translateZ() 让它们在 Z 轴间隔一定距离
  3. 添加简单的旋转、透明度、字体颜色变化

可以得到这样一种类似电影开片的标题 3D 动画,其实只有 3 层元素,但是由于角度恰当,视觉上的衔接比较完美,看上去就非常的 3D。

为什么上面说需要合理的利用距离、角度及光影呢?

还是同一个动画效果,如果动画的初始旋转角度设置的稍微大一点,整个效果就会穿帮:

可以看到,在前几帧,能看出来简单的分层结构。又或者,简单调整一下 perspective,设置父容器的 perspective2000px 改为 500px,穿帮效果更为明显:

也就是说,在恰当的距离,合适的角度,我们仅仅通过很少的元素,就能在视觉上形成比较不错的 3D 效果。

上述的完整代码,你可以猛击这里:CSS 灵感 -- 3D 文字出场动画

我们把上述的效果,套用到一个 Emoji 表情上:

<div class="g-emoji">
<div class="g-foo"></div>
<div class="g-bar"></div>
<div class="g-baz"></div>
</div>
.g-emoji {
position: relative;
width: 200px;
height: 200px;
perspective: 2000px;
transform-style: preserve-3d;
font-size: 200px;
animation: rotate 2s alternate infinite ease-in-out; &::before,
&::after {
content: "\1F600"
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
}
&::after {
transform: translate(-50%, -50%) translateZ(-4px);
} .g-foo,
.g-bar,
.g-baz{
position: absolute;
inset: 0;
transform-style: preserve-3d;
} .g-foo::before,
.g-foo::after,
.g-bar::before,
.g-bar::after,
.g-baz::before,
.g-baz::after{
content: "\1F600";
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
}
.g-foo::before {
transform: translate(-50%, -50%) translateZ(-8px);
opacity: .95;
}
.g-foo::after {
transform: translate(-50%, -50%) translateZ(-12px);
opacity: .9;
}
.g-bar::before {
transform: translate(-50%, -50%) translateZ(-16px);
opacity: .85;
}
.g-bar::after {
transform: translate(-50%, -50%) translateZ(-20px);
opacity: .8;
}
.g-baz::before {
transform: translate(-50%, -50%) translateZ(-24px);
opacity: .75;
}
.g-baz::after {
transform: translate(-50%, -50%) translateZ(-28px);
opacity: .7;
}
} @keyframes rotate {
0% {
transform: rotateY(-45deg);
}
100% {
transform: rotateY(45deg);
}
}

这里做了什么事情呢:

  1. 利用元素的伪元素,生成了多个同样的 Emoji 表情,也就是这一句 content: "\1F600",其中 \1f600 表示是的笑脸的 Emoji 表情
  2. 多个相同的 Emoji 表情,叠加在一起,但是设置了不同的 translateZ以及不同的透明度

这里需要提一句,Emoji 表情是有特定的编码范围的,Emoji 表情的编码范围通常是指 Unicode 字符集中专门用于表示 Emoji 图形的范围,一个常见的范围是从 U+1F600 到 U+1F64F。

这样,我们让整个容器在一定角度下绕 Y 轴旋转起来,就可以得到 3D 效果:

这里设定了旋转角度为 -45deg ~ 45deg

如果我们把角度调大,就能清晰的看到效果效果穿帮(下面效果的旋转角度为 -85deg ~ 85deg):

完整的代码,你可以戳这里了:CodePen Demo - 3D Emoji Demo

弹跳动画

好,有了 3D Emoji,接下来,就是实现一个自由落体的弹跳动画。

这个相对而言比较简单,当然,为了效果逼着,在下落的过程中需要让元素受到挤压变形。

关于一个动画原则及技巧,建议同学们可以看看我的这篇文章 -- Web 动画原则及技巧浅析

核心借助缓动函数,以及 transform,我们在上述 DEMO 的基础上,实现弹跳动画效果:

<div class="g-emoji">
<div class="g-foo"></div>
<div class="g-bar"></div>
<div class="g-baz"></div>
</div>
body, html {
width: 100%;
height: 100%;
display: flex;
background: conic-gradient(#fff, #fff 90deg, #ddd 90deg, #ddd 180deg, #fff 180deg, #fff 270deg, #ddd 270deg);
background-size: 50px 50px;
} .g-emoji {
position: relative;
width: 200px;
height: 200px;
margin: auto;
perspective: 2000px;
transform-style: preserve-3d;
font-size: 200px;
animation:
rotate 2s alternate infinite ease-in-out,
fall .6s alternate infinite cubic-bezier(.22,.16,.04,.99) forwards; //...
}
@keyframes fall {
0% {
scale: 1.25 0.75;
translate: 0 150px;
}
25% {
scale: 1 1;
}
100% {
scale: 1 1;
translate: 0 0;
}
}

与上面代码不一样的是,这里新增了 fall 动画效果,此效果完成了两件事:

  1. 利用 translate 实现了下落动画
  2. 利用 scale 实现了形变变化

当然,由于是自由落地,选取了一个与自由落体速率相近的 cubic-bezier(.22,.16,.04,.99) 缓动函数,并且,利用了 alternate infinite 让整个动画效果,反向无限运行。

这样,我们就能得到这么一个效果:

嘿,是不是有那么点效果了。

解决弹起瞬间切换 Emoji 表情

OK,接下来,我们要解决另外一个难点。

如何在 Emoji 表情弹起的瞬间,替换一个新的 Emoji 表情呢?

此处的麻烦之处在于,上面列出的两个动画效果,都是 infinite 无限动画。熟悉 CSS 动画的同学应该都知道,在 JavaScript 中,我们可以利用 animationstartanimationend 两个事件,监听 CSS 动画的开始与结束。

然而,上面也说了,由于本例中的 CSS 动画都是无限动画,我们无法通过这两个事件去获取譬如动画弹起和下落的一些关键事件节点。

因此,这里我使用了 requestAnimationFrame 去完成这个事情。步骤大致如下:

  1. 改造一下代码,DEMO 中 CSS 代码中使用的 Emoji 表情,通过 JavaScript 写入元素的 style 标签内,通过 CSS 变量获取
  2. 通过 requestAnimationFrame 监听页面渲染的每一帧,计算每一帧元素当前的位置与上一帧的位置的一些关系
  3. 如果发现上一帧中,元素在下降(计算相对位置变化得到),而最新的一帧中,元素开始上升,则找到了元素从下落到上升转换的这一帧
  4. 而(3)这一帧的 requestAnimationFrame(),可以理解为是一个 HOOK,我们在这一帧中,实现 Emoji 表情的随机生成与写入元素的 Style 属性中

上面一段步骤代码,需要好好理解,代码大致如下:

<div class="g-emoji">
<div class="g-foo"></div>
<div class="g-bar"></div>
<div class="g-baz"></div>
</div>

.g-emoji {
position: relative;
animation: rotate 2.3s alternate infinite ease-in-out,
fall .6s alternate infinite cubic-bezier(.22,.16,.04,.99) forwards; &::before,
&::after {
content: var(--emoji, "\1F600");
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
} .g-foo::before,
.g-foo::after,
.g-bar::before,
.g-bar::after,
.g-baz::before,
.g-baz::after{
content: var(--emoji, "\1F600");
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
}
// ...
}

最为核心的 JavaScript 代码:

const emoji = document.querySelectorAll('.g-emoji')[0];

let curTranslate = 0;
let lastTranslate = 0;
let diff = 0; function aniFun() {
curTranslate = window.getComputedStyle(emoji, null).getPropertyValue("translate").split(' ')[1].slice(0, -2) - 0; // 翻转
if (diff > 0 && (curTranslate - lastTranslate < 0)) {
emoji.style = `--emoji: "${generateRandomEmoji()}"`;
} window.requestAnimationFrame(aniFun);
diff = curTranslate - lastTranslate;
lastTranslate = curTranslate;
} function generateRandomEmoji() {
// 开始的 Emoji 编码
var emojiStart = 0x1F600;
var emojiStart2 = 0x1F900;
// 结束的 Emoji 编码
var emojiEnd = 0x1F64F;
var emojiEnd2 = 0x1F9FF; var randomCode = Math.random() > 0.5
? Math.floor(Math.random() * (emojiEnd - emojiStart + 1)) + emojiStart
: Math.floor(Math.random() * (emojiEnd2 - emojiStart2 + 1)) + emojiStart2;
var emoji = String.fromCodePoint(randomCode); return emoji;
} window.requestAnimationFrame(aniFun);

这样,我们就成功的拿到了动画从下落转向上升的那一帧。

经读者提醒,这里还可以使用 animationiteration 去监听无限动画的每一次轮回,当然,使用 animationiteration 会有两个节点,需要判断是从下落转向上升的那一个节点,其余代码和上述的代码类似。

并且,在这一帧中,利用 generateRandomEmoji(),随机生成了一个 Emoji 表情的 Unicode 编码,插入元素中。这样,我们能得到这样一个效果:

效果还是很不错的。主体动画已经实现了,接下来,我们进行下一个环节。

增加随机背景

好,到这里,基本上最为核心的部分我们已经实现了。

接下来,就是让整个动画更加的丰满有特色的一些辅助工作。

下一个非常有意思点,如何添加随机背景动画?可以看到最上面的 DEMO 图,在 Emoji 表情变化的瞬间,背景图也在变化。

这个也好做,上面我们既然已经拿到了从下落转向上升的那一帧。那么我们就可以在这一帧中,做更多事情。

随机背景的做法就是:

  1. 事先基于 <body>,使用 CSS 实现多个不同的背景效果,每个效果,都赋予一个单独的 className
  2. 在表情切换的瞬间,也随机切换一个背景效果,其本质就是给 body 再添加一个事先定义好的范围内的随机的 className,表现为不同的背景效果

body.a {
background-image: conic-gradient(#fff, #fff 90deg, #ddd 90deg, #ddd 180deg, #fff 180deg, #fff 270deg, #ddd 270deg);
background-size: 50px 50px;
}
body.b {
background-image:
linear-gradient(0deg, transparent 9%,
rgba(255, 255, 255, .2) 10%, rgba(255, 255, 255, .2) 12%, transparent 13%, transparent 29%,
rgba(255, 255, 255, .1) 30%, rgba(255, 255, 255, .1) 31%, transparent 32%, transparent 49%,
rgba(255, 255, 255, .1) 50%, rgba(255, 255, 255, .1) 51%, transparent 52%, transparent 69%,
rgba(255, 255, 255, .1) 70%, rgba(255, 255, 255, .1) 71%, transparent 72%, transparent 89%,
rgba(255, 255, 255, .1) 90%, rgba(255, 255, 255, .1) 91%, transparent 92%, transparent),
linear-gradient(90deg, transparent 9%,
rgba(255, 255, 255, .2) 10%, rgba(255, 255, 255, .2) 12%, transparent 13%, transparent 29%,
rgba(255, 255, 255, .1) 30%, rgba(255, 255, 255, .1) 31%, transparent 32%, transparent 49%,
rgba(255, 255, 255, .1) 50%, rgba(255, 255, 255, .1) 51%, transparent 52%, transparent 69%,
rgba(255, 255, 255, .1) 70%, rgba(255, 255, 255, .1) 71%, transparent 72%, transparent 89%,
rgba(255, 255, 255, .1) 90%, rgba(255, 255, 255, .1) 91%, transparent 92%, transparent);
background-size:50px 50px;
}
body.c {
background-image: linear-gradient(rgba(0, 255, 0, .7) .1em, transparent .1em), linear-gradient(90deg, rgba(0, 255, 0, .7) .1em, transparent .1em);
background-size: 3em 3em;
}
body.d {
background: repeating-linear-gradient(45deg, #444 0 20px, #c0466f 0 40px);
}
body.e {
background: repeating-radial-gradient(circle at 50% 50%, #fff, #9C27B0 20px, #FF5722 21px, #9C27B0 40px, #000000 41px, #256b8f 60px, #fff 61px);
}
body.f {
background: conic-gradient(#333 0 45deg, #fff 0 360deg);
background-position: -50% -50%;
background-size: 30px 30px;
}
body.g {
&::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient 3s ease infinite;
}
}
body.h {
background: linear-gradient(30deg, #000 0, #000 49.9%, #fff 50%);
}
body.i {
background: #000;
&::before,
&::after {
content: '';
position: absolute;
inset: 0 50% 0 0;
background: linear-gradient(
45deg,
#00f376 10%,
transparent 10%,
transparent 50%,
#00f376 50%,
#00f376 60%,
transparent 60%,
transparent 100%
);
background-size: 40px 40px;
animation: move 0.3s linear infinite;
}
&::after {
inset: 0 0 0 50%;
transform: rotateY(180deg);
}
}
body.j {
&::before {
content: "";
position: absolute;
inset: 0;
background: conic-gradient(#fff 0, transparent 30%, #fff); }
}
body.k {
&::before {
content: "";
position: absolute;
inset: -100vmax;
background: conic-gradient(#fff 0, transparent 45%, #fff);
animation: bgrotate 2s infinite linear;
}
}
const body = document.querySelectorAll('body')[0];
const container = document.querySelectorAll('.g-container')[0];
const emoji = document.querySelectorAll('.g-emoji')[0];
const bgArr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']; let curTranslate = 0;
let lastTranslate = 0;
let diff = 0; function aniFun() {
curTranslate = window.getComputedStyle(emoji, null).getPropertyValue("translate").split(' ')[1].slice(0, -2) - 0; // 翻转
if (diff > 0 && (curTranslate - lastTranslate < 0)) {
emoji.style = `--emoji: "${generateRandomEmoji()}"`;
body.style = `--bg: ${generateRandomColor()}`;
body.setAttribute('class', bgArr[Math.floor(Math.random() * bgArr.length)]);
} window.requestAnimationFrame(aniFun);
diff = curTranslate - lastTranslate;
lastTranslate = curTranslate;
} function generateRandomColor() {
var red = Math.floor(Math.random() * 256);
var green = Math.floor(Math.random() * 256);
var blue = Math.floor(Math.random() * 256); var color = "rgb(" + red + ", " + green + ", " + blue + ")"; return color;
} function generateRandomEmoji() {
// 开始的 Emoji 编码
var emojiStart = 0x1F600;
var emojiStart2 = 0x1F900;
// 结束的 Emoji 编码
var emojiEnd = 0x1F64F;
var emojiEnd2 = 0x1F9FF; var randomCode = Math.random() > 0.5
? Math.floor(Math.random() * (emojiEnd - emojiStart + 1)) + emojiStart
: Math.floor(Math.random() * (emojiEnd2 - emojiStart2 + 1)) + emojiStart2;
var emoji = String.fromCodePoint(randomCode); return emoji;
} window.requestAnimationFrame(aniFun);

上面,我们定义了 body.a ~body.k 的多个随机背景效果。

在表情切换的瞬间,也随机切换一个背景效果,其本质就是给 body 再添加一个事先定义好的范围内的随机的 className

效果如下:

这样,整个动画就基本完成了。基于上述的核心步骤,可以再做一些细节的增强:

  1. 伴随 Emoji 表情落体运动,下方可以增添阴影的变化
  2. Emoji 表情旋转方向的变化优化
  3. 丰富不同的背景效果
  4. 等等等等

这样,最终整个效果就完成啦,效果如下:

看似很复杂的一个动画效果,经过拆解后,一步一步实现,其实也不难。

完整的动画效果,你可以戳这里:CodePen Demo -- Random 3D Emoji

总结一下

好了,本文到此结束,希望对你有帮助

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

最后,我的小册 《CSS 技术揭秘与实战通关》上线了,想了解更多有趣、进阶、系统化的 CSS 内容,可以猛击 - LINK

【动画进阶】有意思的 Emoji 3D 表情切换效果的更多相关文章

  1. 精致3D图片切换效果,最适合企业产品展示

    这是一个精致的立体图片切换效果,特别适合企业产品展示,可立即用于实际项目中.支持导航和自动播放功能, 基于 CSS3 实现,推荐使用最新的 Chrome,Firefox 和 Safari 浏览器浏览效 ...

  2. 极富创意的3D文件夹切换效果

    今天分享的是一个极富创意的文件夹切换效果.这个案例使用CSS 3动画实现了一个3D的平行六面体旋转效果.点击顶部的3个按钮可以旋转并切换.另外,每个六面体本身是一个文件夹,点击后可以展开查看里面的详情 ...

  3. jQuery鼠标悬停内容动画切换效果

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. ACtivity实现欢迎界面并添加动画切换效果

    先看效果: 中间切换动画没来得及截图,凑合着看吧. 主要是java代码的实现: Welcom.java package kehr.activity.welcome; import android.ap ...

  5. 8 cocos2dx加入场景切换效果,控制场景切换彻底完毕之后再运行动画

     1 加入场景切换效果 供场景切换的类: CCTransitionJumpZoom CCTransitionProgressRadialCCW CCTransitionProgressRadial ...

  6. jQuery+html5实现的3D动态切换焦点轮播幻灯片

    今天爱编程给网友们分享一款基于jQuery+html5实现的3D动态切换焦点轮播幻灯片,支持左右箭头和圆点按钮播放控制,支持多种不同的3D动态切换特效,自适应全屏显示,兼容360.FireFox.Ch ...

  7. iOS 视图控制器转场动画/页面切换效果/跳转动画 学习

    一 学习 在 UINavigationController 中 push 和 pop 的转场效果  (基于iOS7 以上的转场方式) 经过学习了解到,重点分三块: (1)pushAnimation:  ...

  8. [Swift通天遁地]九、拔剑吧-(15)搭建具有滑出、视差、3D变形等切换效果的引导页

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  9. iOS7之定制View Controller切换效果

    在iOS5和iOS6前,View Controller的切换主要有4种: 1. Push/Pop,NavigationViewController常干的事儿 2. Tab,TabViewControl ...

  10. jQuery实现多种切换效果的图片切换的五款插件

    1:Nivo SliderNivoslider:丰富的图片切换效果 官方网址:https://themeisle.com/plugins/nivo-slider 查看演示:https://www.he ...

随机推荐

  1. #Powerbi 利用动态格式字符串功能,实现百分数智能缩位(powerbi4月重磅更新功能)

    以下内容(基于POWERBI 23年4月更新的最新版本) 实际业务中,日常报表一般都有一个较为规范的百分数缩位要求,如果统一要求保留一位小数,那么在有些时候,我们会面临被缩成0.0%的尴尬,例如原有的 ...

  2. 2021-07-09:股票问题6。给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付

    2021-07-09:股票问题6.给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 :整数 fee 代表了交易股票的手续费用.你可以无限次地完成交易,但是你每笔交易都需要付 ...

  3. C++中的字符串编码处理

    今天由于在项目中用到一些与C++混合开发的东西 ,需要通过socket与C++那边交换数据,没啥特别的,字节码而已,两边确定一种编码规则就行了.我们确定的UTF-8.关于C++的 这种又是宽字节 又是 ...

  4. CF1825C LuoTianyi and the Show

    传送门(luogu) 传送门(CF) 前言 我来水题解力 简化题意 \(n\) 个人,\(m\) 个座位,每个人落座的方法有三种: 坐最左边的人的左边,没人的话就做 \(m\) 号座位,若最左边的为 ...

  5. Python对两个Excel操作

    简介 现在有个需求,我们根据需要 data.xlsx 中某些单元格的内容来查找 find.xlsx 中的某些內容. 数据内容(为了数据安全,所有数据均已模糊处理) data.xlsx内容: find. ...

  6. Galaxy 平台下 LEfSe 安装与使用教程

    LEfSe (Linear discriminant analysis Effect Size) 是一种用于发现和解释高维度数据生物标识(基因.通路和分类单元等)的分析工具,可以进行两个或多个分组的比 ...

  7. ics-05

    挺有意思的一题 攻防世界->web->ics-05 打开题目链接,就是一个很正常的管理系统,只有左侧的可以点着玩 并且点到**设备维护中心时,页面变为index.php 查看响应 发现云平 ...

  8. 深入探究for...range语句

    1. 引言 在Go语言中,我们经常需要对数据集合进行遍历操作.对于数组来说,使用for语句可以很方便地完成遍历.然而,当我们面对其他数据类型,如map.string 和 channel 时,使用普通的 ...

  9. docker 对容器中的文件进行编辑

    用途 有一些情况下,例如docker安装的redis.nacos.mysql等等,在docker容器中的安装未进行文件的映射,当需要对其进行更改配置信息时,就会遇到这种情况,需要去容器中进行编辑配置文 ...

  10. 1. SpringMVC 简介

    1. 什么是 MVC ‍ MVC是一种软件架构的思想,将软件按照模型.视图.控制器来划分 M:Model,模型层,指工程中的JavaBean,作用是处理数据 ‍ JavaBean分为两类: 一类称为实 ...