美文网首页重构&设计模式前端饥人谷技术博客
第三次重写个人网站,分享一些感想

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

作者: 写代码的海怪 | 来源:发表于2021-07-03 10:56 被阅读0次

    前言

    阅读时长 用脑度 前置知识
    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~

    “奇行种”类

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

    欧美巨大类

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

    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 媒体查询来控制两者的显示。

    <!-- 水平 -->
    <ul class="horizontal">
      ...
      <li class="navBtn" onclick="打开vertical list">垂直打开按钮</li>
    </ul>
    
    <!-- 垂直 -->
    <ul class="vertical">
      ...
    </ul>
    
    // 小屏样式
    @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。很多老哥的第一反应就是 <a href="#id"> 标签 + 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

    左边部分,一个 <p/> 里面加个 <span/> 搞定了。划线部分,可以用伪元素 ::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: ['<i>First</i> sentence.', '&amp; 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<HTMLElement | null>(null); // span 元素
      const typed = useRef<Typed | null>(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 (
        <span ref={el} />
      );
    };
    
    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<HTMLDivElement | null>(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 (
        <div ref={sheepLottieRef} />
      );
    };
    

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

    关于 - About

    image.png

    这一部分为个人介绍。左、中、右分别是 <p><img><p>,简单。其中,左边中间那个正方体依然是个 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 (
        <Masonry
          breakpointCols={{ default: 3, 992: 2, 786: 1 }}
          className={styles.projectList}
          columnClassName={styles.projectListColumn}
        >
          {projects.map((project) => (
            <Item {...project} key={project.logo} />
          ))}
        </Masonry>
      );
    };
    
    .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 (
          <div>
            <Fade left>
              <h1>React Reveal</h1>
            </Fade>
          </div>
        );
      }
    }
    
    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 = () => (
      <div id="app">
        <Suspense fallback={<Loading />}>
          <Nav />
    
          <main>
            <Home />
            <About />
            <Project />
            <Contact />
          </main>
    
          <Footer />
        </Suspense>
      </div>
    );
    

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

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

    最后

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

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

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

    相关文章

      网友评论

        本文标题:第三次重写个人网站,分享一些感想

        本文链接:https://www.haomeiwen.com/subject/skyuultx.html