第三次重写个人网站,分享一些感想

前言

阅读时长 用脑度 前置知识
10min 30% React

最近更新了一波个人主页,正好整理一篇文章来分享一些想法。这篇文章会聊一聊网站中每个部分的实现思路,以及会说到我对设计的一些想法和思路。

如果你也想写自己的个人主页,希望这篇文章可以给你一些灵感。

新的主页:https://yanhaixiang.com

以前的版本

先给大家看看以前的版本。v1.0 还是 2018 年写的,年代真的太久远了,以至于本地跑 node-sass 都报错了。

v1.0(2018)

image.png

v2.0(2019)

image.png

定位

首先来聊聊定位,我觉得这是做个人主页的最重要的部分。我见过太多人做着做着就偏离目标,最终放弃,俗称 “需求不明确”

比如刚开始做文章博客,在 Markdown 样式各种纠结,然后放弃,又比如有的人喜欢啥都自己造,结果花时间太多在“造轮子”上,反而网页迟迟造不出来,最后放弃。

我以前做个人主页的目的只是为了收集在 和 Medium 上的文章,顺便写写个人简介,就当个主页了。

当时比较迷恋:内容为王 这个思路。所以我的想法很简单:一切以博客文章为主。绝不手写轮子,轮子给我啥我用啥,就用默认的样式。

可以看到无论是 v1.0 和 v2.0 都是一股强烈的 Element UI 味,很多组件连改都不改,只在 v2.0 在首页上稍微做了点改进。

而且我还非常的 鸡贼,我偏不踩 Markdown 和编辑器的坑,所以文章链接都是 外链 形式,点进去就跳转到 和 Medium 上。

image.png

一来引流(天真了,有人点进我主页就有鬼了),二来不想浪费时间在 Markdown 的样式了,三来不想自己搞后端服务、博客管理后台。

而对于新版 v3.0,我不再是放各种文章了,因为只要在平台上写好文章,自然就有人关注了。另一方面,别人点进我的主页可能仅仅是因为好奇。

所以,为了满足各位观众老爷的好奇心,我将其定位为个人落地页,也即 Landing Page。主要内容:突出个人,且花里胡哨。

风格

落地页最重要的特点就是 ,所以我看了市面上很多个人主页,主要归为几个类:微博类、大佬简约类和欧美巨大类。

中国微博类

第一类我称之为“中国微博类”,因为具有非常强烈的微博样式,主要元素有:文章主体、文章目录、文章分类、标签等。代表作:阮一峰的网络日志。

image.png

这类主要以文章内容为主,元素非常多和杂。国内使用这类的非常多,导致同质化非常严重,很难搞出新意,而且要做好文章的样式是一件非常麻烦且复杂的事情,与我的定位不符,所以 pass~

大佬简约类

这类算是意义上的落地页,但是元素实在太少,只有几行的介绍就完事了。代表作:尤雨溪的主页。

image.png

连个图片都没有,属于“爱看看,不看去学习”的风格。然而我不是大佬,pass~

“奇行种”类

这类一般非常的秀,几乎进去的就不是为了看内容的,而是为了 。比如这些:

  • 在我的世界里看博客
  • 终端里的主页
  • 马里奥主页

欧美巨大类

国外的人更喜欢 大图粗线条感重口味颜色 的风格。这类的设计图在 dribbble 和 behance 上面非常多。

image.png

主要特点就是:线条比较粗犷,色彩比较鲜艳或者淡雅,元素以“大”为主,同时能保持一种简约风格。

“欧美巨大类”是我比较喜欢的类型,跳脱了国内“类微博风”的博客主页,又能有好看的设计。唯一的难度就是自己设计不出来这么好看的,所以我在 dribbble 和 behance 上找了一个模板,再结合别的一些元素,边开发边融合。

为什么不?

这时有的老铁就会问了:

为什么你不去 Hexo 这些网站直接使用免费的模板呢?

主要还是因为上面的模板大多数都是“类微博风”,很少有 dribbble 网站上的设计风格。但是如果你没有十分特别的需求,我建议最好就用现成的,尽量不要“发明”轮子。

那 WordPress,Wix 国外的网站生成器也有“欧美巨大类”的呀?

一个原因是访问速度实在太慢了,不过,主要还是贵 。

技术栈

  • React
  • TypeScript
  • Sass
  • Ant Design

我相信有很多人都会觉得:

个人网站就应该自己手写,这样才能吸引到面试官。当面试官问起的时候,才可以滔滔不绝地讲如何攻克某个技术点。

我的想法是:不建议这么搞,能用轮子用轮子,除非万不得已,千万别手写!

要时刻记住我们究竟是在 练习 还是在 做产品。对于前者没什么好说的,手写轮子 + 发技术文章,这在掘金上经常见到。而对于后者,则不应该纠结技术,而是想方设法把 产品 做完美的。

自己造轮子的缺点有很多:

  • 喜欢 “发明” 轮子,而不是 “造” 轮子
  • 做出来还要维护,而且并不比市面上的轮子好用
  • 最重要的一点,很容易就钻入 “如何解决 XX 问题” 的牛角尖,然后忘了到底是来写主页还是来造轮子的

请相信另一句更真实的信条:

做出来就是 NB,做不出来就是 SB。

所以,放过自己,站在巨人肩膀上不香么?好了,废话不多说,下面就来说说我是怎么实现的吧。

导航栏 - Nav

Nav.jpg

经典的左边 Logo,右边 List 布局,实现方法非常多。

在以前,用 float: leftfloat: right 来实现会比较流行,然而,这个方法缺点是得手动清除一下浮动,且 float 已经成为过去式了,所以 pass~

这里直接用 flex 的 space-between 收工了。

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between; // 分开左右两边
}

导航栏另一个需求点就是要适配手机端,不然所有 Nav 标签都被挤在一起了。

[图片上传失败...(image-450826-1625280925104)]

我的实现是:做两个导航栏,然后通过 @media 媒体查询来控制两者的显示。


    ...
    ...
// 小屏样式
@media screen and (max-width: 992px) {
  ul.horizontal { // 水平的 Nav 别出来
    li {
      display: none;

      &.navBtn {
        display: block; // Toggle 按钮出来
      }
    }
  }

  ul.vertical {
    display: flex; // 垂直的 Nav 出来
  }
}

Very easy~ 导航栏还有一个需求点:点到哪个 Tab 就要下滑到对应的 Section。很多老哥的第一反应就是 标签 + div 里嵌入 id,用 url hash 来导航。缺点是:下滑动作太生硬了,没有动画

这里推荐使用 $el.scrollIntoView({ behavior: 'smooth' }) 这个 API,在 PC 端滑动效果还不错。缺点是不能适配手机端,小细节,可忽略~

滑动.gif
const scroll = (toEl: string) => {
  const $toEl = document.querySelector(toEl);
  if ($toEl) {
    setActiveItem(toEl);
    $toEl.scrollIntoView({ behavior: 'smooth' });
  }
};

加上 position: fixed 将 Nav 组件固定在屏幕头部,会更有 整体感

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 3;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1);
}

最好在外层加个 box-shadow 阴影效果,会让导航栏更有 立体感

但是注意,不要加得很明显,会让人感觉有点刻意而为之。最好的阴影效果是第一眼看不出来,仔细看才看出来。

像这种阴影效果、背景渐变效果,是很难纯手动调出来的,最好搭配 GUI 生成器来生成 CSS。Google 搜索 box-shadow generator,各种样式随便调!

image.png

要是你说:我 GUI 还是调不出来好看的效果咋办?答案是:。自己不专业,就看专业的人怎么做,比如掘金就的导航栏阴影就不错:

image.png

广告页 - Banner

home.gif

左边部分,一个

里面加个 搞定了。划线部分,可以用伪元素 ::before::after 生成两条横线,再通过 position: absolute 来调整位置就可以实现划线效果。

.del {
  position: relative;
  &::before, &::after {
    content: '';
    position: absolute;
    background: #bd0000;
    width: 120%; //宽度超出一点点比较好看
    height: 6px;
  }
  &::before {
    left: -10%; // 处理宽度超出的部分
    top: 40%;
  }
  &::after {
    left: -10%; // 处理宽度超出的部分
    bottom: 30%;
  }
}

打字效果 可以使用 typed.js 这个小库,用起来非常简单,这是官方的小 Demo:

// Can also be included with a regular script tag
import Typed from 'typed.js';

var options = {
  strings: ['First sentence.', '& a second sentence.'],
  typeSpeed: 40
};

var typed = new Typed('.element', options);

打字速度、删除速度、DOM 元素的获取逻辑都是可以重复使用的,所以我封装成一个 hook:

import Typed, { TypedOptions } from 'typed.js';

const useTyped = (strings: string[], extra?: TypedOptions) => {
  const el = useRef(null); // span 元素
  const typed = useRef(null); // Typed.js 对象

  useEffect(() => {
    const options = { // 默认属性
      strings,
      typeSpeed: 100,
      backSpeed: 60,
      ...extra,
    };

    typed.current = new Typed(el.current || '', options);

    return () => typed.current?.destroy(); // 擦屁股
  }, [strings]);

  return el;
};

使用的时候就很方便了:

const Home: FC = () => {
  const el = useTyped(strings, { loop: true });

  return (
    
  );
};
typed.gif

再来看右边,其实是一个使用 ::after 实现的动画效果 + Lottie 动画 效果。先来说这个 “波纹” 效果,其实英文名是 pulse。“波纹” 效果是另一种效果,叫 ripple

@keyframes pulse {
  0% {
    transform: scale(1);
    border-color: #e3342f;
  }

  100% {
    transform: scale(1.2); // 放大
    border-color: transparent; // 透明
  }
}

.ball {
  &::before {
    content: "";
    position: absolute; // 定位到同元素上
    width: 100%;
    height: 100%;
    border: 4px solid #e3342f; // 波纹
    border-radius: 50%; // 变圆
    background: transparent; // 不要背景颜色
    animation: pulse 2s cubic-bezier(.57, .06, .27, .84) infinite; // pulse 动画
    z-index: 1; // 放在原来元素之上
  }
}
sheep.gif

哦,是心动,不对,是心跳的感觉。

关于这个小羊的动画,它并不是一个 Gif 图片,而是 Lottie 动画。这是 Airbnb 开源的一套跨平台的完整的动画效果解决方案。说人话:就是高级版的 Gif。动画内容是通过 JSON 文件来驱动的,可以在 Lottie 官网 上找到免费的,自己找一下就可以了。

关于使用方式,我依然封装了一个 hook:

import lottie, { AnimationConfigWithData } from 'lottie-web';

const useLottie = (path: string, extra?: AnimationConfigWithData) => {
  const lottieRef = useRef(null); // 动画元素

  useEffect(() => {
    if (lottieRef.current) { // 默认参数
      lottie.loadAnimation({ // Load
        container: lottieRef.current,
        path,
        renderer: 'svg',
        loop: true,
        autoplay: true,
        ...extra,
      });
    }
  }, []);

  return lottieRef;
};

使用的时候传入 JSON url 就可以了:

const sheepLottie = 'https://assets3.lottiefiles.com/private_files/lf30_lgesk2nm.json';

const Home: FC = () => {
  const sheepLottieRef = useLottie(sheepLottie);

  return (
    
); };

至于下面的箭头,就是 Lottie + scrollIntoView({ behavior: 'smooth' }) 的组合,不赘述。

关于 - About

image.png

这一部分为个人介绍。左、中、右分别是

,简单。其中,左边中间那个正方体依然是个 Lottie 动画,右边的 “海怪” 用的是 HongLei 字体库。

Tip:强烈背影颜色情况下,依然可以使用阴影效果来突显页面的立体感,可以有效避免文字和背景在视觉上 “融合” 的问题。这里文字用了 text-shadow,头像用了 box-shadow。

下一个部分,Timeline 组件。

image.png

之前研究过 Ant Design 的 Timeline 组件,它是用定位来实现的。我不太喜欢内容 div 脱离文档流的实现方式,会经常就出现 div 高度坍塌的问题。所以,我的 Timeline 组件并没有用 position,而是用 Flex 布局 来实现的。。

来复习一下 Flex 布局的 align-items 属性,flex-start 靠上,flex-end 靠下展示:

image.png

如果把这个 div 加上 flex-direction: column,然后左右的 div 设计宽度 50% 不就可以实现左右两边展示了么?

.timelineItem {
  width: 50%;

  &.right {
    text-align: left;
    align-self: flex-end;
  }

  &.left {
    text-align: right;
    align-self: flex-start;
  }
}
image.png

然后就是轴体的实现,这里用了 ::before 伪元素 + position 来实现,但是如果设置宽度为 1px 的时候,会出现 “错位” 的问题。

image.png

这也很容易理解,因为定位后的 “轴体” 就是在原有 div 突出来的。可以设置 leftright 的值来解决,但是 1px 又会产生心理上的不对称,所以我把宽度设置为 2px 就 OK 了,同时轴体看上去也更饱满。

中间的自定义节点就传入一个 ReactChild 就 OK 了,字体、背景也不赘述。

Project - 个人项目

image.png

先说说瀑布流怎么实现的,你可以使用以下方式来实现:

  • multi-column 多栏布局
  • grid 布局
  • flexbox 布局
  • ...

还记得前面说的么?我们是在做产品,不是造轮子,所以不要纠结怎么实现,直接使用现有 NPM。这里推荐 react-masonry-css。顺便说一句,瀑布流的效果英文真不叫 Water Fall,而是 masonry。

const Project = () => {
  return (
    
      {projects.map((project) => (
        
      ))}
    
  );
};
.projectList {
  display: flex;
  list-style-type: none !important;
}

.projectListColumn {
  background-clip: padding-box;
}

这个库有一个叫 breakpointCols 的 props,可以在不同屏幕宽度下展示不同列数,非常实用的一个功能。

项目卡片的样式就不废话了,无非就是 color, font-size, text-shadow 之类的。

来说说这些 stars 1.2K 的图标是怎么来的吧。首先,如果你是用 Travis 或者 Coveralls,官网是有地方自动生成的,比如在 Travis CI 上点这个图标就会有图标的 Image URL。

image.png

那我岂不是要上每个网站上弄一遍图标地址?而且有的网站还可能没有呢。这里推荐使用 shields.io 这个网站,几乎可以自动生成我们常见的所有 Shield 图标。

image.png

唯一的缺点就是:点这个图标不会跳转到对应的网站。不过可以自动生成多类图标,还可以设定图标 style,还可以自定义 Shield,要啥自行车不是?

然后是这些小 icon 的获取,在 http://iconfont.cn/ 拿就行了,这个老铁们应该很熟了。

最后说一下这个动画效果。

jelly.gif

英文名叫 jello,中文名是 啫喱(zhě lí )

@keyframes jello {
  0% {
    transform: scale(1, 1);
  }
  25% {
    transform: scale(0.9, 1.1);
  }
  50% {
    transform: scale(1.2, 0.8);
  }
  75% {
    transform: scale(0.95, 1.05);
  }
}

Contact - 找到我

contact.gif

因为上面几个 section 的背景都是跟随页面滚动的,而且这个页面没有太多的动画效果,所以我把这里的背景设置为 background-attachment: fixed,让其不会显得那么单调。

然而 backgroud-attachment: fixed 在手机端是不能用的,会变成 cover 的样式,所以在手机端要设置为 initial 的值。

style={{ backgroundAttachment: isMobile ? 'initial' : 'fixed' }}

同时,背景这里我选择了 黑白 + 个人 比较单一的照片,并没有太多花里胡哨,也是因为这个 section 的元素太少了。

样式实现上很简单,就不多说了。

动画

上面把各个 section 都大概讲了一遍,这部分聊聊动画。

下面部分带有强烈主观色彩,不一定正确

我在主页里加入了很多入场动画,用到的库是 react-reveal。这个库的功能是:当滚动到当前元素时,使用动画入场效果展示元素。 很实用的一个库。虽然是个老库了,但是动画方面还是挺强的,用法也简单:

import Fade from 'react-reveal/Fade';

class FadeExample extends React.Component {
  render() {
    return (
      

React Reveal

); } }
fade-left.gif

动画并不是越多越好的,好的设计不是花里胡哨,而是克制。虽然我不是设计师,但是也尽量遵循 Ant Design 的设计原则。

自然

尽量不用很夸张的动画。虽然 react-reveals 提供了很多花里胡哨的动画,但是都太夸张了。

image.png

99% 我都用 fade-in 这个入场效果,过渡更自然点。

高效

对于 Banner 页面的文字,刚开始是想用 typed.js 将整段文字输出的:

complex.gif

同事看了后,说:“一般面试官是没有耐心看完的”,这让我意识到这么太低效。所以我改成直接展示 90% 的文字,只有最后的词语循环打印。

另一个场景是,我原来是用 fade in from bottom 来展示每个项目:

[图片上传失败...(image-8f2558-1625280925105)]

每个 ProjectItem 里的文字、图片、图标是比较多的,所以使用了向上动画会让人很难第一时间注意到内容,必须等动画结束了才能“看清楚”内容,而且在手机上尤为明显,同样是 低效 的。所以,我后来都改成直接 fade-in,稍快加速动画,同时兼顾了入场动画。

对比 & 对称

如果所有入场都用 fade in from bottom 就会显得有点重复了,所以加点 对比对称 可以稍微点缀一下。

首先是在小羊 Lottie 动画和向下箭头 Lottie 动画这里,前者向上渐入,后者向下渐入,形成对比。因为箭头是向下的,所以用 fade in from top 会更有 逻辑性

banner-transition.gif

另一个地方就是时间轴这里,左边内容使用向右入场,右边内容则向左入场,同时也遵循动画入场的 逻辑性

timeline-compare.gif

强调

唯一使用了夸张动画的地方,就是 “联系我” 的 “ 求点赞,求关注,求转发,一键三连!”

contact-transition.gif

主要还是因为这个 section 真的太单一了,加一个夸张动画增加一点动感,这里用作强调动画应该不过分吧,哈哈 。

颜色

颜色方面,国内的审美一般以 小清新 为主,而由于走了欧美的 粗犷 路线,所以颜色方面使用了重口味的颜色,以 红、黄、橙、黑 为主。

image.png

这里的颜色值都是比较相近的。千万不要选颜色跨度太大的颜色,不然你的页面就变成东一块,西一块,有很强的割裂感。

如果你对选颜色不是很敏感,可以上 Adobe 的 主题色推荐,里面有超多的主题色任君选择。

image.png

背景

背景真的太难选了!难点有:

  • 不能太花里胡哨。不然会喧宾夺主,内容会被背景抢去关注
  • 图片体积要小,很多人只会打开一次你的主页,这时是没有任何 HTTP 缓存的,所以体积大的背景加载时,会出现从头加载到脚的效果

虽然只说了两点,但是 90% 的图片都是不合格的:风景图、个人照、文艺照。然而,往往这些照片才能提升主页逼格的关键。那有什么图片,体积又小、又不那么单一的呢?答案是:

在 background-repeat: repeat 或者 background-size: cover|contain 情况下都不会有太多视觉上变化的图片,才能满足上面的要求。

这里推荐两个网站,自动生成高级 SVG 背景图:

  • svgbackgrounds,有 48 个免费 SVG 背景,而且都可以自定义一些样式的,付费有 200 多种,我觉得 48 个就够用了
  • loading.io,本来是个做 Loading 动画的网站,后面也做 SVG 背景图了,里面更多自定义的模板,免费版只能生成静态的,付费版可生成动态 SVG 背景,同样的,免费版就够用了

优化

代码层面,用 React 的 React.lazy 和 Suspense 做了分包:

const Home = React.lazy(() => import('./sections/Home'));
const About = React.lazy(() => import('./sections/About'));
const Project = React.lazy(() => import('./sections/Project'));
const Contact = React.lazy(() => import('./sections/Contact'));
const Footer = React.lazy(() => import('./sections/Footer'));

const App: FC = () => (
  
}>
);

对于 图片 的优化,本来想用 Webpack 的 imagemin 来做的,但是 creat-react-app 太坑了,试过 react-app-rewired 和 craco 都没什么效果,算了,还是手动自己压缩吧,反正没几个图。头像和背景图都转成 webp 格式,尽量减小体积。

最后是 字体库 的优化,上面说到我用了 HongLei 这个字体库,整个库有 1.1 MB,而我就用了“海怪”这两个字,有点划不来。所以,我用 fontmin 将字体库压缩到了 4 KB,能更快一点显示字体。

最后

整个主页在业余时间写了 2 周,大部分时间都是在试各种设计、颜色、背景,实现上也挺简单的。喜欢的话就在 我的 github 上点个 Star 吧,欢迎 fork 和魔改!

其实,还有 Github 部署和 CDN 加速这一块也非常重要。由于这期文章太长了,就放下一篇来讲吧。

对了,最近开了个公众号【写代码的海怪】,如果你觉得我写得不错也可以随缘关注一下喽~

你可能感兴趣的:(第三次重写个人网站,分享一些感想)