本项目使用React + Vite快速代建,之后会适配vanilla版本开箱即用。主要实现效果:鼠标悬停hover 3D视觉体验,统一UI设计,动画平滑,卡片易扩展
直接上效果
因为最近在准备写简历,然后看网上要么就是word模板或者直接md文档生成pdf/word,感觉比较没意思。索性做一个在线简历,以示前端雄风、、、? 为了不涉及个人隐私就找了动漫人物宇智波鼬来练练手(小时候很喜欢😍)
前期准备
技术选型
因为项目很小,不涉及用户交互只是简单的页面展示,就选用小而巧的vite搭建
bash
npm create vite@latest yuzhiboyou
把没有用的css和测试文件删掉就开始code
信息收集
google一下鼬的帅照
因为已经好久没追火影了,宇智波鼬的好多技能都忘记了。。。。还是查一下资料
梳理一下主要信息,把重复的内容抽取出来成一个单独的config配置文件,方便之后渲染
按照程序员写技术了解程度的惯词 (精通,擅长,熟悉,了解)来描述宇智波鼬的忍术🫰
jsx
const config = {
baseInfo: [
'生日 :6月9日(双子座)',
'等级:暗部B级、S级叛忍',
'忍者登记号码:012110',
'兴趣:逛甜品屋、回想以前愉快的事情',
],
resume: [
{
name: '木叶忍者',
intro: '木叶村的天才 | 六岁 ~ 十岁',
details: [
'七岁以第一名的成绩从忍者学校毕业(六岁入学)',
'八岁时因目睹带土杀光他的同伴而开启写轮眼',
'十岁晋升为中忍,被誉为为"天才忍者"',
],
},
{
name: '暗部',
intro: '担任暗部分队长 | 十岁 ~ 十三岁',
details: ['守卫木叶村安宁', '为顾全大局同宇智波带土灭族,唯独留下弟弟佐助'],
},
{
name: '晓组织成员',
intro: '为保护木叶以间谍加入晓组织 | 十三岁 ~ 二十一岁',
details: [
'为晓组织献身 但未真正捕捉尾兽',
'协助佐助觉醒万花筒写轮眼',
'在世界大战中作出重大贡献',
],
},
],
skills: [
'万花筒血轮眼',
'天照',
'须佐能乎',
'豪火球之术',
'鸦分身之术',
'豪龙火之术',
'水龙弹之术',
'影分身之术',
'月读',
'幻术',
],
skillStack: [
'精通血继结界 如万花筒写轮眼(天照,伊邪那美,须佐能乎,八坂勾玉)',
'精通火遁忍术 例如凤仙火之术 豪火球之术 凤仙花爪红 豪龙火之术 豪焰球',
'擅长封印型 转写封印 别天神',
'擅长组合忍术 天照·二重 须佐能乎·双神雷临 凌乱雪月花',
'熟悉辅助型忍术 熟悉的有鸦分身之术 分身大爆破 影分身之术 水分身之术 替身术',
'了解水遁忍术 例如水牙弹 水龙弹之术',
],
chakras: ['风', '火', '水', '阴', '阳'],
}
export default config
resume结构设计
简历主要分左右俩大板块,左边是人物图像和基本信息,右边是五个卡片card组成的人物细节描述
左部分jsx结构
jsx
import imgUrl from './img/Yuzhi-Wave-Weasel.jpg'
export default function Resume(){
return (
<div>
<main>
<div className="profile">
<div className="card">
<img src={imgUrl}></img>
<h2>
宇智波鼬 <small>うちはイタチ</small>
</h2>
<h3>宇智波一族 晓组织</h3>
<h5>火之国·木叶隐村</h5>
<br />
<p>
<small>
宇智波佐助的兄长,特征是黑色长发,从近鼻翼的眼角延伸至眼下的深邃纹路(泪沟),代表「朱雀」的「朱」字戒指,佩戴在右手无名指上,脖子戴有项链。为人沉着冷静,天资聪颖的他,五岁就将家族基本忍术:「火遁」豪火球之术一学就会,七岁以第一名的成绩从忍者学校毕业(六岁入学),八岁时因目睹带土杀光他的同伴而开启写轮眼,十岁晋升为中忍,被称为「天才忍者」,十三岁就当上暗部分队长,是家族的骄傲。不料却突然在一个晚上之内将家族几乎全灭后,离开木叶成为叛忍,并加入「晓」(实则是为了保护木叶而加入晓作为间谍)。他的弟弟宇智波佐助成为家族中唯一的活口,自始不惜一切以杀死他为目标,为族人复仇。
</small>
</p>
<br />
<p>
<div>基本信息</div>
{config.baseInfo.map((item, index) => (
<a key={index}>{item}</a>
))}
</p>
</div>
</div>
</main>
</div>
)}
css和框架
css基本样式
- 将默认浏览器css重置 确定简历基本色调
- grid实现响应式俩栏布局
- media实现移动端适配
grid-template-columns: 320px 1fr;
当屏幕宽度大于 768px 时,main 元素的列布局将改为两列。左边的的宽度为 320px,右边的宽度使用剩余空间(1fr),小于768px则是一列
css
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
line-height: 1.4;
font-weight: 420;
font-family: 'DM Sans', sans-serif;
background-color: #0f0f0f;
color: #e4dcdc;
}
main {
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
display: grid;
gap: 2rem;
}
img {
display: block;
max-width: 100%;
height: 100%;
}
h1,h2,h3,h4,h5,h6 {
color: white;
font-weight: 700;
}
text {
font-size: small;
}
@media (min-width: 768px) {
main {
grid-template-columns: 320px 1fr;
}
}
效果展示
card样式
- 首次加载动画
- entry关键帧从小到大 透明到透明
- 缓动函数ease 从慢到快 再从快到慢
- card 背景图像
- 用伪元素before来定位
background-image: linear-gradient(120deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05));
- 120deg:从左上角向右下角渐变
- 从第二个参数颜色渐变到第三个参数
css
.card {
padding: 1.5rem;
border-radius: 2rem;
position: relative;
box-shadow: -1px -1px 1px rgba(255, 255, 255, 0.1);
overflow: hidden;
animation: entry 1s ease;
}
.card::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: -1;
background-image: linear-gradient(120deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05));
opacity: 0.25;
border-radius: 2rem;
}
@keyframes entry {
from {
transform: scale(0.5);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
a标签动画
实现效果
- 当悬停于a标签时,伪元素before显现
opacity: 1;
- 在a标签之前显示选中图标'>' (伪元素before实现)
transition: padding-left 0.2s ease, color 0.2s ease;
:链接文本的左内边距和颜色会以0.2秒的时间以 "ease" 缓动函数的方式进行平滑过渡
css
a {
color: #9f4941;
text-decoration: none;
padding-left: 0.2rem;
display: block;
position: relative;
transition: padding-left 0.2s ease, color 0.2s ease;
}
a::before {
content: '>';
position: absolute;
bottom: 0;
left: 0;
opacity: 0;
color: white;
transition: opacity 0.2s ease;
}
a:hover {
color: hotpink;
padding-left: 1.2rem;
}
a:hover::before {
opacity: 1;
}
左部分-- 人物图像3D设计
参照 codepen的实现
-
.reflection-grid-cell-#{( ($r*10) + $c - 10)} { top: ($r * 10%)-10%; left: ($c * 10%)-10%; }
通过sass中的双重循环,分别用于 <math xmlns="http://www.w3.org/1998/Math/MathML"> r (行)和 r(行)和 </math>r(行)和c(列)标记一个10*10的网格,并分别设置类名reflection-grid-cell-[1,2,3...] 在图像之上(z-index)均匀分布100个行内标签,当然也可以更加颗粒 -
transform: rotateX((($r * -5)+45deg)) rotateY((-45deg+ ($c * 5))
;监听每个标签的hover属性,悬停时会变换X,Y轴坐标使产生3D效果。这里的旋转角度基于 <math xmlns="http://www.w3.org/1998/Math/MathML"> r 和 r和 </math>r和c的值,是动态的 -
&:before:{transform: translateY(45-(5% * $r));}
设置伪元素的垂直位移,根据$r伪元素将产生垂直位移效果
fragment code
jsx
const ImgCard = () => {
return (
<div className="reflection-container">
{Array.from({ length: 100 }, (_, cell) => (
<span key={cell} className={`reflection-grid-cell reflection-grid-cell-${cell}`}></span>
))}
<img src={imgUrl} className="reflection-content"></img>
</div>
)
}
scss
.reflection-container {
position: relative;
display: inline-block;
vertical-align: middle;
transform-style: preserve-3d;
perspective: 1000px;
height: 100%;
.reflection-content {
height: 50vh;
width: 90vw;
background-size: cover;
background-position: center;
transform: rotateX(0) rotateY(0);
pointer-events: none;
transition: 100ms linear transform;
overflow: hidden;
}
.reflection-grid-cell {
position: absolute;
z-index: 1;
width: 10%;
height: 10%;
}
@for $r from 1 to 11 {
@for $c from 1 to 11 {
.reflection-grid-cell-#{( ($r*10) + $c - 10)} {
top: ($r * 10%)-10%;
left: ($c * 10%)-10%;
}
.reflection-grid-cell-#{( ($r*10) + $c - 10)}:hover ~ .reflection-content {
transform: rotateX((($r * -5)+45deg)) rotateY((-45deg+ ($c * 5)));
&:before {
transform: translateY(45-(5% * $r));
}
}
}
}
}
右部分卡片信息样式
- h4之下有个a标签,可以复用上面提到的a标签样式
list-style-type: disclosure-closed;
ul的标签样式选择图中的的实心大于符号.info .card.red::after{}
给每个card右上角加不同颜色的书签页样式
jsx和相关css代码片段
jsx
<div className="info">
<div className="card red">
<h3>人生履历</h3>
{config.resume.map(item => (
<div className="block" key={item.name}>
<h4>
<a href="#">{item.name}</a>
</h4>
<p>{item.info}</p>
<ul>
{item.details.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
))}
</div>
<div className="card orange">
<h3>教育经历</h3>
<div className="block">
<h4>
<a href="#">忍者学校</a>
</h4>
<p>GPA:4.9(5.0)</p>
<p>国家级奖学金 * 3 , 获ACM世界总决赛金牌</p>
<ul>
<li>同龄人中的佼佼者</li>
<li>不到一年晋升中忍</li>
</ul>
</div>
</div>
</div>
css
.block + .block {
margin-top: 1rem;
}
.block ul {
font-weight: 300;
}
.block p {
color: white;
}
ul {
padding-left: 1rem;
list-style-type: disclosure-closed;
}
.info .card + .card {
margin-top: 2rem;
}
ul.pills {
list-style-type: none;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0.25rem 0.5rem;
align-items: baseline;
}
ul.pills > li {
background-color: #534f4f;
border-radius: 1rem;
padding: 0.25rem 0.75rem;
color: white;
font-size: 0.8rem;
display: block;
}
.info .card::after {
content: '';
width: 0;
height: 0;
border-style: solid;
border-width: 0 4rem 4rem 0;
position: absolute;
right: 0;
top: 0;
}
.info .card.red::after {
border-color: transparent red transparent transparent;
}
.info .card.orange::after {
border-color: transparent orange transparent transparent;
}
.info .card.pink::after {
border-color: transparent pink transparent transparent;
}
.info .card.green::after {
border-color: transparent limegreen transparent transparent;
}
.info .card.teal::after {
border-color: transparent turquoise transparent transparent;
}
做完一个card卡片样式其他的就是粘贴复制按需增删改一下样式就ok辽🤓 如果对你的简历写法有帮助的话可以点赞收藏哦😉
总结
- 多看多尝试比较有视觉冲击的设计(codepen,css tricks,codrops),思想不要被固化
- 能用css做就尽量不用js(3D card)
- 多考虑样式的可复用性 jsx尽量写简洁易懂,配置信息另存文件