效果
静态皮卡丘效果:♥Pikachu (yuyuanw.github.io)
代码演示的效果:♥Pikachu代码展示版 (yuyuanw.github.io)
静态皮卡丘源码:看index2和css2就行了,1是我做失败的
动态皮卡丘源码:加了展示代码和倍速播放的功能面向对象实现
如何找到模仿对象?
首先找一个简单些的卡通人物,搜索到英文单词,例如:皮卡丘----> Pikachu
随后可以去浏览图片。找自己满意的简单一下的卡通图。
这里使用的是codepen平台,找到的一个适合模仿的网页。
我的目标就是做出一只卡哇伊的皮卡丘,如上图所示。
如何画出静态的皮卡丘?
css是层叠样式表,就是说我要用一个个盒子做出Pikachu的眼睛、鼻子、嘴巴、脸颊……
首先在,html中写入元素,一张皮skin,两只眼睛eye left/right,一个鼻子nose,一个嘴巴mouth,两个脸颊cheek left/right,做好布局后,接下来写css
<body>
<div class="skin">
<div class="eye left">
<div class="eyeball left"></div>
</div>
<div class="eye right">
<div class="eyeball right"></div>
</div>
<div class="nose"></div>
<div class="mouth">
<div class="lip left"></div>
<div class="lip right"></div>
<div class="lipdown"><div class="cop"></div></div>
<div class="x1"></div>
<div class="x2"></div>
</div>
<div class="cheek left"><img src="../img/pikachu-flash.gif"></div>
<div class="cheek right"><img src="../img/pikachu-flash.gif"></div>
</div>
</body>
css写的思路是:
首先给定盒模型和基础设定
*{
box-sizing: border-box;
margin: 0;
}
::after,::before{
box-sizing: border-box;
margin: 0;
}
然后来写黄黄的皮肤。
首先是给皮肤这个盒子设定大小,设置height和width,然后刷上颜色,background,这个我是用qq截图的识色复制的颜色。定位和布局,定位,因为是其他的元素都会在skin上,所以采用相对定位,relative,布局,一张皮有什么好布局的....
.skin{
background: rgb(255,230,0);
min-height: 100vh;
width: 100vw;
position: relative;
}
接下来画鼻子。
脸颊腮红和鼻子是相对而言简单一些的,但是因为要做定位,所以先画鼻子会好一些。
开始画鼻子咯。
我确定这个可以用一个盒子做出来。
首先给这个盒子一个大小,让它显示出来,设置width和heigth还有border,随后做定位,因为skin是relative,所以鼻子可以用absolute来定位,接下来给它放到中间去,居中显示,left和top的50%,然后负marigin实现整体居中。接下来就是变形了。变形考验操作一点。
鼻子的下面是一个三角形,上面是一个扇形,有点类似半圆。三角形可以用border-color显示1/4的颜色,其余颜色transparent透明,就可以显示一个三角了,但是需要border很宽。半圆border-radius来设置相邻的两条边。最后绘色,鼻子的颜色就是border-color未被设置成透明的那个颜色。
.nose{
border: 20px solid red;
width:40px;
height: 40px;
position: absolute;
left:50%;
top:50%;
margin-left:-20px;
margin-top: -20px;
border-radius: 50% 50% 0 0 ;
border-color: rgb(0,0,0) transparent transparent transparent;
}
画眼睛
两只眼睛除了位置不一样,其余的布局,大小,颜色,形状都是一样的,因此统一设置。 首先定位元素eye,然后给两个盒子一样的大小width和height,之后做定位,也是absolute,居中left,top50%,移动的话,再分开移动给位置。形状,都是圆,用border-radius来做,颜色,去色写成background就行了。
.eye{
border:3px solid black;
width: 84px;
height: 84px;
position: absolute;
left:50%;
top:50%;
border-radius: 50%;
background: rgb(46,46,46);
}
两只眼睛的位置,都是要往上移,一只往左移,一只往右移,用负margin来移动。 虽然transform也能移动,但我还是优先考虑margin,因为transfrom涉及定位。
.eye.left{
margin-left: -160px;
margin-top: -64px;
}
.eye.right{
margin-left:76px;
margin-top:-64px;
}
画完眼睛外部了,还有眼球没有画。首先是设置html,在eye left和eye right的div里面再写盒子 eyeball left和eyeball right。 两只眼球相对于外面的眼睛而言,设置都是一样的,无论是在眼睛的位置,还是大小,形状,颜色。 因此选定eyeball进行统一设置,先给盒子大小,width和height,border,然后是定位,它是eye中的元素,定位也不用写了,写位置吧,用margin给它移到左上角的位置,然后是形状,圆形,border-radius,最后是绘色,直接设置背景色background-color就行了。
.eyeball{
border:3px solid black;
width: 36px;
height: 36px;
margin-left: 14px;
margin-top: 2px;
border-radius: 50%;
background-color: white;
}
画脸颊腮红
为什么画脸颊腮红,不先画嘴巴,因为嘴巴最复杂。。。 又是两个除了位置不一样其余都一样的盒子,对cheek left和cheek right 一起设置cheek。 首先是给两个盒子大小,width和height还有border,然后是定位,absolute,居中位置,left,top50%,然后是形状,border-radius设置两个圆,最后绘色,background-color写上取到的颜色。
.cheek{
border:4px solid black;
width:128px;
height: 128px;
position: absolute;
left:50%;
top:50%;
border-radius: 50%;
background-color: #FF0000;
}
接下来就是分别给left 和right 位置了。 和眼睛一样,用margin来做。
.cheek.left{
margin-left: -250px;
margin-top:64px;
}
.cheek.right{
margin-left:120px;
margin-top:64px;
}
最后,画嘴巴了。
嘴巴一看需要用五个盒子来画,总体的一个盒子mouth作为总的容器,然后上嘴唇用两个盒子,下嘴唇用一个盒子,舌头用一个盒子。先把html中的div盒子写了。
<div class="mouth">
<div class="lip left"></div>
<div class="lip right"></div>
<div class="lipdown">
<div class="cop"></div>
</div>
</div>
先把容器mouth的位置放好。 给大小,居中显示,负margin,
.mouth{
width:200px;
height:250px;
position: absolute;
left:50%;
margin-left: -100px;
top:50%;
margin-top: 16px;
画上嘴唇,这个形状像是圆角矩形盒子截一部分。先把大小位置弄好,
.lip{
border:4px solid black;
width:100px;
height:30px;
position: absolute;
margin-left: 50px;
margin-top: -6px;
background-color: #FFE600;
}
然后就是嘴唇的形状了,
.lip.left{
transform: translateX(-48px) rotate(-15deg);
border-radius: 0px 0px 0px 100px;
border-color: transparent transparent transparent black;
}
.lip.right{
transform: translateX(48px) rotate(15deg);
border-radius: 0px 0px 100px 0px;
border-color: transparent black transparent transparent;
}
发现有瑕疵,上嘴唇的中间颜色显示有明显的bug,遮住!
<div class="mouth">
<div class="lip left"></div>
<div class="lip right"></div>
<div class="lipdown"><div class="cop"></div></div>
<div class="x1"></div>
<div class="x2"></div>
</div>
用x1遮住嘴唇拼接处的瑕疵,用x2把外面细线的瑕疵遮住
.x1{
width:8px;
height:4px;
background-color: black;
border-radius: 50% 0 0 0;
position: absolute;
left:50%;
margin-left: -5px;
margin-top: 8px;
z-index: 5;
}
.x2{
width:180px;
height:0px;
margin-left: 8px;
border:4px solid #FFE600;
z-index: 8;
position: absolute;
background-color: #FFE600;
}
画下嘴唇 下嘴唇是一个很长很尖的圆角矩形截取一部分,并且外面那一部分要隐藏,overflow。
.mouth > .lipdown{
border: 4px solid black;
width: 150px;
height: 400px;
margin-left: 25px;
position: absolute;
bottom: 0;
border-radius: 150px/400px;
background-color: rgb(155,0,10);
}
隐藏的话,要在mouth元素的位置加上overflow: hidden;为了不让其他元素被遮住,skin,nose,x1都加上z-index: 5
画舌头
舌头像是一个小点的圆角矩形或者圆,截取上面一部分,
.lipdown > .cop{
border:1px solid red;
width:150px;
height:180px;
position: absolute;
bottom:0px;
margin-left: -4px;
border-radius: 150px/180px;
background-color: rgb(255,72,95);
}
超出的部分隐藏,在.mouth > .lipdown里面写overflow: hidden;
这样一只皮卡丘就画好了
如何用代码演示过程?
1. 如何让屏幕一个一个动态地敲出字?
先让屏幕呈现出内容。
<div id = 'test1'></div>
test1.innerHTML = 'halo'
然后让屏幕呈现出我们想展示的内容。
const string = '我是Yuyuan,接下来我要展示我的作品了。'
test1.innerHTML = string
然后让屏幕一个字一个字呈现出我们想展示的内容。借助延时函数。
const string = '我是Yuyuan,接下来我要展示我的作品了。'
let n = 0
let step = ()=>{
setInterval(()=>{
if(n<string.length){
test1.innerHTML = string[n]
console.log(string[n])
n=n+1
step()
}
},1000)
}
step()
效果如下:
接下来,让屏幕展示的内容连贯起来,借助substring。
const string = '我是Yuyuan,接下来我要展示我的作品了。'
let n = 0
let step = ()=>{
setInterval(()=>{
if(n<string.length){
test1.innerHTML = string.substring(0,n)
console.log(string[n])
n=n+1
step()
}
},1000)
}
step()
这样我们就实现了第一个小目标:让字一个一个跳动入场屏幕。
这个要点就是,借助延时函数,并且,利用递归,在本函数中调用本函数,实现循环,并且可以通过条件判断来终结循环。
2.如何同时展示css和text的内容?
翻译一下的意思就是,我想要一边打字显示出我写代码的过程,一边还能画出皮卡丘。
如何让css生效?
当我将string的内容换成css呢?背景能不能让我写的样式生效?
const string = '我是Yuyuan,接下来我要展示我的作品了。<style>body{background:pink}</style>'
let n = 0
let step = ()=>{
setInterval(()=>{
if(n<string.length){
test1.innerHTML = string.substring(0,n)
console.log(string[n])
n=n+1
step()
}
},1000)
}
step()
它居然正确地识别了我写的废话和我要加的样式,这就很nice。也就是innerHTML可以识别样式。那我就用innerText来加文字,innerHTML来加样式画图了咯。
<div id="test1"></div>
<style id="test2"></style>
const string = '我是Yuyuan,接下来我要展示我的作品了。<style>body{background:pink}</style>'
let n = 0
let step = ()=>{
setInterval(()=>{
if(n<string.length){
console.log(string.substring(0,n))
test1.innerText = string.substring(0,n)
test2.innerHTML = string.substring(0,n)
n=n+1
step()
}
},1000)
}
step()
3.如何一边打印css的内容,一边将皮卡丘在屏幕上画出来?
①打印css内容——模拟写代码过程
将原本的css内容放入string中就可以了
②画皮卡丘
css要生效,就要将HTML中的布局标签都写好。
<body>
<div id="test1"></div>
<style id="test2"></style>
<div id="html">
<div class="skin">
<div class="eye left">
<div class="eyeball left"></div>
</div>
<div class="eye right">
<div class="eyeball right"></div>
</div>
<div class="nose"></div>
<div class="mouth">
<div class="lip left"></div>
<div class="lip right"></div>
<div class="lipdown"><div class="cop"></div></div>
<div class="x1"></div>
<div class="x2"></div>
</div>
<div class="cheek left"></div>
<div class="cheek right"></div>
</div>
</div>
<script src="yanshi.js"></script>
</body>
拷贝原先css的内容到string中
string = `
*{
box-sizing: border-box;
margin: 0;
}
::after,::before{
box-sizing: border-box;
margin: 0;
}
.skin{
background: rgb(255,230,0);
min-height: 50vh;
width: 100vw;
position: relative;
z-index: 1;
}
.nose{
border: 20px solid red;
width:40px;
height: 40px;
position: absolute;
left:50%;
top:140px;
margin-left:-20px;
margin-top: -20px;
border-radius: 50% 50% 0 0 ;
border-color: rgb(0,0,0) transparent transparent transparent;
z-index: 4;
}
@keyframes wave {
0%{
transform: rotate(0deg);
}
25%{
transform: rotate(10deg);
}
50%{
transform: rotate(0deg);
}
75%{
transform: rotate(-10deg);
}
100%{
transform: rotate(0deg);
}
}
.nose:hover{
animation: wave 300ms infinite;
}
.eye{
border:3px solid black;
width: 84px;
height: 84px;
position: absolute;
left:50%;
top:140px;
border-radius: 50%;
background: rgb(46,46,46);
}
.eye.left{
margin-left: -160px;
margin-top: -64px;
}
.eye.right{
margin-left:76px;
margin-top:-64px;
}
.eyeball{
border:3px solid black;
width: 36px;
height: 36px;
margin-left: 14px;
margin-top: 2px;
border-radius: 50%;
background-color: white;
}
.cheek{
border:4px solid black;
width:128px;
height: 128px;
position: absolute;
left:50%;
top:140px;
border-radius: 50%;
background-color: #FF0000;
}
.cheek.left{
margin-left: -250px;
margin-top:64px;
}
.cheek.right{
margin-left:120px;
margin-top:64px;
}
.mouth{
width:200px;
height:250px;
position: absolute;
left:50%;
margin-left: -100px;
top:140px;
margin-top: 16px;
overflow: hidden;
}
.lip{
border:4px solid black;
width:100px;
height:30px;
position: absolute;
margin-left: 50px;
margin-top: -6px;
border-radius: 0px 0px 0px 100px;
background-color: #FFE600;
z-index: 2;
}
.lip.left{
transform: translateX(-48px) rotate(-15deg);
border-radius: 0px 0px 0px 100px;
border-color: transparent transparent transparent black;
}
.lip.right{
transform: translateX(48px) rotate(15deg);
border-radius: 0px 0px 100px 0px;
border-color: transparent black transparent transparent;
}
.x1{
width:8px;
height:4px;
background-color: black;
border-radius: 50% 0 0 0;
position: absolute;
left:50%;
margin-left: -5px;
margin-top: 8px;
z-index: 5;
}
.x2{
width:180px;
height:0px;
margin-left: 8px;
border:4px solid #FFE600;
z-index: 8;
position: absolute;
background-color: #FFE600;
}
.mouth > .lipdown{
border: 4px solid black;
width: 150px;
height: 400px;
margin-left: 25px;
position: absolute;
bottom: 0;
border-radius: 150px/400px;
background-color: rgb(155,0,10);
overflow: hidden;
}
.lipdown > .cop{
border:1px solid red;
width:150px;
height:180px;
position: absolute;
bottom:0px;
margin-left: -4px;
border-radius: 150px/180px;
background-color: rgb(255,72,95);
}
`
js中剩余的内容:
let n = 1
let step = ()=>{
setInterval(()=>{
if(n<string.length+1){
console.log(string.substring(0,n))
test1.innerText = string.substring(0,n)
test2.innerHTML = string.substring(0,n)
n=n+1
step()
}
},10)
}
step()
这个时候已经做出粗略的效果了,但是还有很多的bug。
4.浅修一下bug
① 如果string读取的长度超过了length
它的循环是退出来了,但是对浏览器而言,它的行为还被记录在案。 因此,代码更改为:
let n = 1
let step = ()=>{
setInterval(()=>{
if(n<string.length+1){
console.log(string.substring(0,n))
test1.innerText = string.substring(0,n)
test2.innerHTML = string.substring(0,n)
n=n+1
step()
}else{
window.clearInterval(step)
return
}
},100)
}
step()
② 皮卡丘不固定
皮卡丘要拖滚动条拖到底才能看到,html中加入布局
<style>
#html{
position: fixed;
left:0;
bottom:0;
width:100%;
height: 50vh;
}
#test1{
position: fixed;
top:0;
left: 0;
width:100%;
height: 50vh;
overflow-x: hidden;
overflow-y: auto;
}
#test2{
display: none;
}
</style>
③皮卡丘固定了,但是上方的代码像写死了
加滚动条。
#test1{
position: fixed;
top:0;
left: 0;
width:100%;
height: 50vh;
overflow: scroll; //滚动条
}
加了滚条,,隐藏左右的,保留上下的,并让滚条动起来。
#test1{
position: fixed;
top:0;
left: 0;
width:100%;
height: 50vh;
overflow-x: hidden; //滚条样式
overflow-y: auto; //滚条样式
}
let step = ()=>{
setInterval(()=>{
if(n<string.length+1){
test1.innerText = string.substring(0,n)
test2.innerHTML = string.substring(0,n)
test1.scrollTop = test1.scrollHeight || 9999999
//滚条保持最下端
n=n+1
step()
}else{
window.clearInterval(step)
return
}
},100)
}
5.精进功能
①string模块化
string太长了,占位。把它弄到另外一个文件中,然后引入。
新建文件new.js:
const string = `……`
export default string
导入原来的文件
import string from './new.js'
这样做有个小问题,就是我使用parcel对我的代码进行预览,但是parcel2好像并不太支持这样做。
②添加暂停、播放、倍速按钮
先去html中加按键的组件
<body>
<div id="buttons">
<button id="btnPause">暂停</button>
<button id="btnPlay">播放</button>
<button id="btnSlow">慢速</button>
<button id="btnNormal">中速</button>
<button id="btnFast">快速</button>
</div>
</body>
然后在css中修改样式
<style>
#buttons{
position: fixed;
top:0;
right:0;
display: flex;
flex-direction: column;
z-index: 20;
margin-right: 24px;
}
#buttons > button{
margin-top: 10px;
}
</style>
复制粘贴过来,代码有些没有对齐……
好了,接下来在js中实现它的功能。
暂停:通过window.clear,清空step来实现
在写代码之前,发现了之前代码的错误,来自于对window.clearInterval的理解错误。
之前的使用:
let n = 1
let step = ()=>{
setInterval(()=>{
if(n<string.length+1){
console.log(string.substring(0,n))
test1.innerText = string.substring(0,n)
test2.innerHTML = string.substring(0,n)
n=n+1
step()
}else{
window.clearInterval(step)
return
}
},100)
}
step()
但是,写暂停写不出,查看了文档WindowTimers.clearInterval() - Web API 接口参考 | MDN (mozilla.org)
发现错误在,intervalID要取消的定时器的 ID。是由 setInterval() 返回的。写一个返回,再设置一个计时器ID。
更正为:
let step = ()=>{
return setInterval(()=>{ //新增返回
if(n>string.length){
window.clearInterval(timeId)
return
}else{
test1.innerText = string.substring(0,n)
test2.innerHTML = string.substring(0,n)
test1.scrollTop = test1.scrollHeight || 9999999
n=n+1
}
},1)
}
let timeId = step() //新增计时器ID,将step的返回值给ID
所以暂停功能的代码为:
btnPause.onclick = ()=>{
window.clearInterval(timeId)
}
播放:重新写入计时器
btnPlay.onclick = ()=>{
timeId = step()
}
倍速:清空计时器,设置事件time,再写入计时器
重新写入计时器太麻烦了吧,要写一堆。 更改下:
let n = 1
let time = 100
let step = (time)=>{
return setInterval(()=>{
if(n>string.length){
window.clearInterval(timeId)
return
}else{
test1.innerText = string.substring(0,n)
test2.innerHTML = string.substring(0,n)
test1.scrollTop = test1.scrollHeight || 9999999
n=n+1
}
},time)
}
let timeId = step(time)
// const pause = document.getElementById('btnPause')
btnPause.onclick = ()=>{
window.clearInterval(timeId)
}
btnPlay.onclick = ()=>{
timeId = step(time)
}
btnSlow.onclick = ()=>{
window.clearInterval(timeId)
time = 100
timeId = step(time)
}
btnNormal.onclick = ()=>{
window.clearInterval(timeId)
time = 10
timeId = step(time)
}
btnFast.onclick = ()=>{
window.clearInterval(timeId)
time = 0
timeId = step(time)
}
6.高级写法
封装代码+面向对象编程
封装代码:
- 重复代码封装成箭头函数
- 重复代码部分的调用,看源代码是调用还是占位,占位就只写函数名,不要写
() - 重复代码写入函数时,要注意return,重复部分有返回值,函数也要return 因为代码重构太容易出错,而且vscode提示功能不完善,我就不演示了。
面向对象:
- 定义一个对象Object
- 将方法写入对象,const去掉,
=改成: - 在对方法进行调用时候,要加对象Object的名字
- 初始化,写一个init对象,后续的引用放入init函数体,最后的时候调用init
- 变量申明,
const n = 0=>n : 0 - 重复的部分,写哈希表和函数
代码重构后如下:
const player = {
n : 0,
time : 100,
id : undefined ,
init:()=>{
player.bindEvents()
player.play()
},
events :{
'btnPause' :'pause',
'btnPlay' :'play',
'btnSlow' :'slow',
'btnNormal' :'normal',
'btnFast' :'fast'
},
bindEvents:()=>{
for(let key in player.events){
if(player.events.hasOwnProperty(key)){
const value = player.events[key]
document.querySelector('#'+key).onclick = player[value]
}
}
},
Pikachu : ()=>{
if(player.n>string.length){
window.clearInterval(player.id)
return
}else{
player.n += 1;
test1.innerText = string.substring(0,player.n)
test2.innerHTML = string.substring(0,player.n)
test1.scrollTop = test1.scrollHeight
}
},
play : ()=>{
player.id = setInterval(player.Pikachu,player.time)
},
pause : ()=>{
window.clearInterval(player.id)
},
slow : ()=>{
player.pause()
player.time = 300
player.play()
},
normal : ()=>{
player.pause()
player.time = 100
player.play()
},
fast : ()=>{
player.pause()
player.time = 0
player.play()
}
}
player.init()
我的错误和过程缺陷
- 首先是对css盒模型的理解不够,明白后,后面修改代码的过程顺利了很多
- 然后是因为嘴巴画不出泄气了很久
- 另外,我第一次做定位,是盲干,根本没考虑啥子相对定位绝对定位,没考虑布局,居中还是干啥,全是靠开发者工具盲调。正确的过程应该是发现问题,确认自己的目标,回头看看自己之前是怎么实现,它应该怎么实现,而不是盲干。这个地方浪费了很多很多时间,
- 再者,在使用之中巩固了css的知识,加深了对盒模型,定位,布局的认识。
刚开始完全不会画舌头嘴唇,定位布局也是乱做
画出了下嘴唇但是舌头还是不会画
终于画出来了