🐯新春活动没想法?没创意?🔥我花三天直接整一个网站🔥🔥🔥(可体验、附源码)

6,139 阅读8分钟

“PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

写在开头

体验地址先上为敬:虎年主题网站

再附上源码以表诚意:虎年主题网站-源码

(体验完你会给小编点个赞不?)

14CCA34C.png

所用技术

整个网站倒是没什么技术难点,做完给我感觉最困难、最麻烦的是各种素材和配色,真的令人头秃抓狂。

下面稍微介绍用到的一些相关技术:

Vue3+Vite2:用于快速开发,Vite 的快是众所周知的,用上了就等于爱上了,会无法自拔。
Animate.css:这是一个 CSS3 动画库,相信各位都不会太陌生了。
JQuery:会用到 jquery 主要是在雪景制作上,还有入场动画部分。
Waypoints:一个用来实现捕获页面各种滚动事件的 JS 工具库,挺好用的。

剩下的一些页面效果就基本是手撸搞定,都不是很难,下面抽一些有意思的来介绍介绍。

正文

页面动画效果可能会比较多,为了第一眼看起来比较酷炫一点,俺左思右想,几乎是能加动画效果的地方,小编是一点都没放过。(^ω^)

大红门

网站入眼就是一个大红门,本来理想中的样子是:

image.png

但真实做出来却是:

image.png

呃......只能说是朴实无华了,简约才大气,是吧?(^ω^)

"福" 字效果是用 CSS 简单弄出来的,本来门锁部分想画个狮子头的,奈何功力有限,一直画不像,无奈只能随便找个图片顶一下了。

// 一个"福"字
<div class="fu yd-flex-h-hC-vC">
  <div class="fu__1 yd-flex-h-hC-vC">
    <div class="fu__2 yd-flex-h-hC-vC">福</div>
  </div>
</div>

.fu{
  margin-top: 10%;
  width: 120px;
  height: 120px;
  box-sizing: border-box;
  border: 6px solid #e5d0a6;
  transform: rotate(45deg);
  padding: 6px;
  box-shadow: 0 0 50px #e5d0a6;
}
.fu__1{
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  border: 2px solid #e5d0a6;
}
.fu__2{
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  transform: rotate(-45deg);
  font-size: 70px;
  color: #e8d4ab;
  font-weight: bold;
  font-family: cursive;
}
.yd-flex-h-hC-vC { 
  display: flex;
  justify-content: center;
  align-items: center;
}

入场动画

页面主要看点应该就是每个元素的入场动画了。

2.gif

而这使用的是 animate.css 库定义的一些动画,如标题的向下淡入动画:

.fadeInDown {
  animation-name: fadeInDown;
}
@keyframes fadeInDown {
  1% {
    opacity: 0;
    transform: translateY(-20px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

写动画应该不难,但是,不是说直接把动画加在元素上就可以了,我们需要在合适的时机才给元素添加动画。而这个时机可以分为两种情况,一种是元素从打开页面时,就直接出现在可视区域内,另一种是元素一开始并没有出现在可视区域内,只有当我们往下滚动时才能见到。

其实说到底就是来判断元素是否在可视区域内,如果在就添加动画,如果不在就不管。

而这个判定过程,我们交给 waypoints.js 库,下面我们先来看看它的基本使用方式。

waypoints 基本使用:

<!DOCTYPE html>
  <html>
    <head>
      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    </head>
    <body>

      <br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
      <h1 class="title">橙某人</h1>
      <br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />

      <script src="./jquery.js"></script>
      <script src="./waypoints.min.js"></script>
      <script>
          $(() => {
              $('#title').waypoint(() => {
                  console.log('你的标题元素已经触碰到顶部');
              });
              $('#title').waypoint(() => {
                  console.log('你的元素已经触碰到底部');
              }, {
                  offset: '100%'
              })
          })
      </script>
    </body>
  </html>

写了个小 Demo 应该挺好理解吧?(^m^) 我们接着来看看下面这些代码:

var appMaster = {
  animateScript() { 
    // this 会是每个元素本身
    $('.scrollpoint.sp-effect1').waypoint(function () { $(this).toggleClass('active'); $(this).toggleClass('animated fadeInLeft');}, { offset: '100%' });
    $('.scrollpoint.sp-effect2').waypoint(function () { $(this).toggleClass('active'); $(this).toggleClass('animated fadeInRight'); }, { offset: '100%' });
    $('.scrollpoint.sp-effect3').waypoint(function () { $(this).toggleClass('active'); $(this).toggleClass('animated fadeInDown'); }, { offset: '100%' });
    $('.scrollpoint.sp-effect4').waypoint(function () { $(this).toggleClass('active'); $(this).toggleClass('animated fadeIn'); }, { offset: '100%' });
    $('.scrollpoint.sp-effect5').waypoint(function () { $(this).toggleClass('active'); $(this).toggleClass('animated fadeInUp'); }, { offset: '100%' });
    $('.scrollpoint.sp-effect6').waypoint(function () { $(this).toggleClass('active'); $(this).toggleClass('animated tada'); }, { offset: '100%' });
  }
};

$(document).ready(() => {
    // 延迟等待大红门效果结束
    setTimeout(() => {
       appMaster.animateScript();
    }, 500)
});

jquery 的一些基本用法就不多说,主要用来获取 Dom 元素,上面代码大概意思是先定义一些预置的类名。当我们需要给某个元素添加动画时,如给标题添加一个向下淡入动画:scrollpoint sp-effect3

<div id="传统文化" class="title scrollpoint sp-effect3">
    <h1 class="title__h1">传统文化</h1>
    <div class="title__line"></div>
    <h3 class="title__h3">Traditional Culture</h3>
    <img class="title__icon" src="~@/assets/images/san.png" />
</div>

154E4A76.gif

灯笼

过年肯定少不了灯笼啦,红红的灯笼才有过年的氛围。

3.gif

最近几天在掘金看到好多倔友都画了很好看的灯笼,咱也得来画一个凑凑热闹呀。

<template>
  <div class="deng cursor">
    <div class="line"></div>
    <div class="deng-a">
      <div class="deng-b">
        <div class="deng-t">{{text}}</div>
      </div>
    </div>
    <div class="shui shui-a"></div>
    <div class="shui shui-b"></div>
    <div class="shui shui-c"></div>
  </div>
</template>

<script>
export default {
  name: "Deng",
  props: ["text"],
};
</script>

<style scoped>
.deng {
  position: relative;
  width: 120px;
  height: 90px;
  margin: 50px;
  background: #d8000f;
  background: #bb1314;
  border-radius: 50% 50%;
  transform-origin: 50% -100px;
  animation: swing 3s infinite ease-in-out;
  box-shadow: -5px 5px 50px 4px rgba(250, 108, 0, 1);
}
.deng:before {
  position: absolute;
  top: -7px;
  left: 29px;
  height: 12px;
  width: 60px;
  content: " ";
  display: block;
  z-index: 999;
  border-radius: 5px 5px 0 0;
  border: solid 1px #dc8f03;
  background: #ffa500;
}
.deng:after {
  position: absolute;
  bottom: -7px;
  left: 10px;
  height: 12px;
  width: 60px;
  content: " ";
  display: block;
  margin-left: 20px;
  border-radius: 0 0 5px 5px;
  border: solid 1px #dc8f03;
  background: #ffa500;
}
.line {
  position: absolute;
  top: -20px;
  left: 60px;
  width: 2px;
  height: 20px;
  background: #dc8f03;
}
.shui {
  width: 5px;
  height: 40px;
  background: #ffa500;
  border-radius: 0 0 5px 5px;
}
.shui-a {
  margin: -10px 0 0 40px;
  animation: swing 4s infinite ease-in-out;
  transform-origin: 50% -20px;
}
.shui-b {
  margin: -35px 0 0 59px;
  animation: swing 4s infinite ease-in-out;
  transform-origin: 50% -45px;
}
.shui-c {
  margin: -45px 0 0 77px;
  animation: swing 4s infinite ease-in-out;
  transform-origin: 50% -25px;
}
.deng-a {
  width: 100px;
  height: 90px;
  background: #d8000f;
  background: rgba(216, 0, 15, 0.2);
  margin: 12px 8px 8px 8px;
  border-radius: 50% 50%;
  border: 2px solid #dc8f03;
}
.deng-b {
  width: 45px;
  height: 90px;
  background: #d8000f;
  background: rgba(216, 0, 15, 0.2);
  margin: -4px 8px 8px 26px;
  border-radius: 50% 50%;
  border: 2px solid #dc8f03;
}
.deng-t {
  font-size: 17px;
  color: #ffc610;
  font-weight: bold;
  line-height: 85px;
  text-align: center;
}
@keyframes swing {
  0% {
    transform: rotate(-10deg);
  }
  50% {
    transform: rotate(10deg);
  }
  100% {
    transform: rotate(-10deg);
  }
}
</style>

整体来说这个灯笼用 CSS 画起来不难,关键点在于画弧度,就是要注意把握宽和高不一样长,然后添加 border-radius 属性即可画出不错的弧度,当然配色才是关键!!!

阅读窗口

阅读窗口 这是一个可以放置大量内容的组件,一个网站总要有些内容,要不是没灵魂的。 (✪ω✪)

4.gif

而它的实现原理也比较简单,如下图:

image.png

看图应该也比较好明白吧?我们仅需控制内层元素 contentContainer 移动即可,而至于应该往上移动还是往下移动,我们可以再叠盖两个小块就能区分了。

<template>
  <div id="previewContainer">
     <div :style="{top: top + 'px'}" id="contentContainer">
      ...
     </div>
     <div @mouseover="mouseover('up')" class="read__btn up cursor"></div>
     <div @mouseover="mouseover('down')" class="read__btn down cursor"></div>
  </div>
</template>

<script>
import { defineComponent, onMounted, ref } from "vue";
export default defineComponent({
  setup() {
    let top = ref(0);
    let timer = null;
    let previewHeight = 0;
    let contentHeight = 0;

    onMounted(() => {
       previewHeight = document.querySelector('#previewContainer').getBoundingClientRect().height;
       contentHeight = document.querySelector('#contentContainer').getBoundingClientRect().height;
    });
       
    return {
      top,
      mouseover(flag) {
	 clearInterval(timer);
	 timer = setInterval(() => {
	   if(flag === 'up') {
	      if ((top.value * -1) > contentHeight - previewHeight) return clearInterval(timer);
	      top.value -= 2;
           }else if(flag === 'down') {
              if (top.value >= 0) return clearInterval(timer);
              top.value += 2;
           }
         },30);
      },
    },
});
</script>

照片墙

照片墙 组件,呃...这...好像也没啥难度吧,就是摆放好看?哦,也不是,应该是素材好看?

image.png

好吧,不管如何,作为一名程序猿,咱还是得提一下代码才算走了流程,我们稍微来看看图中的六边形是如何做的?

首先,我们以两个 <div /> 元素来叠加再加上旋转,就能完成。

<div class="main-box">
  <div class="box bg1">
    <div class="box bg2">橙某人</div>
  </div>
</div>

.main-box{
  position: relative;
  width: 160px;
  height: 190px;
  margin-left: 50%;
  overflow: hidden;
  color: #fff;
  font-size: 20px;
  line-height: 190px;
  text-align: center;
}
.box{
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  overflow: hidden; 
  transform: rotate(120deg);
}

当然,这要控制好宽度和高度才行,得慢慢微调才行。

image.png

这样子我们可以得到一个红色的六边形,但是,这还没完,你发没发现图中的文字也被旋转了!这可不是我们想要的,我们再来增加一个 <div /> 元素。

<div class="main-box">
  <div class="box bg1">
    <div class="box bg2">
      <div class="box bg3">橙某人</div>
    </div>
  </div>
</div>

再看,神奇的事情发生了,文字正回来了。其实这也没啥,就是刚好第三个 <div /> 旋转了 360° 度,所以和没旋转一样。

image.png

因为每个元素都是设置了 absolute 属性并旋转 120° 度,下一个元素会在上一个元素旋转了 120° 度的基础上再旋转 120° 度,所有第三个 <div /> 元素一共会旋转了 360° 度,就是这么个原理了,最后记得把相关背景颜色去掉,就大功告成了。

雪景

5.gif

而页面中雪景效果的实现,小编是直接在 Github 上拉一个 snow.js 库实现的,比较简单,直接导入即可使用。不过,它依赖 JQuery 库,是比较老旧的技术了,现在用 Canvas 技术能更简单、方便地实现这种效果。

18EB659C.jpg

轮播

最后,要介绍的就是这个有点像3D的轮播效果了,它实现起来虽然不难,但是挺巧妙的。o(^▽^)o

6.gif

我们先来看看它的结构和样式:

<template>
  <div class="banner">
    <div class="banner__wrap">
        <div class="banner__slide">
            <img class="banner__img" src="~@/assets/images/banner/2.jpg" />
            <img class="banner__img" src="~@/assets/images/banner/1.jpg" />
            <img class="banner__img" src="~@/assets/images/banner/5.jpg" />
            <img class="banner__img" src="~@/assets/images/banner/4.jpg" />
            <img class="banner__img" src="~@/assets/images/banner/3.jpg" />
            <img @click="change(false)" class="banner__arrow l" src="~@/assets/images/banner/prev.png" />
            <img @click="change(true)"  class="banner__arrow r" src="~@/assets/images/banner/next.png" />
        </div>
    </div>
  </div>
</template>
<style scoped>
.banner {
  width: 100%;
  margin-top: 20px;
  user-select: none;
}
.banner__wrap {
  width: 1000px;
  margin: 0px auto;
}
.banner__slide {
  height: 500px;
  position: relative;
}
.banner__img {
  position: absolute; /*每张图片都是绝对定位*/
  width: 100%;
  box-shadow: 0 0 10px rgba(255, 255, 255, 0.4);
  border-radius: 2px;
}
.banner__arrow {
  width: 76px;
  height: 112px;
  position: absolute;
  bottom: 24px;
  z-index: 10;
  opacity: 0.6;
  transform: scale(1);
  transition: all 0.4s;
}
.banner__arrow:hover{
  opacity: 1;
  transform: scale(1.2);
}
.banner__arrow.l {
  left: 0;
}
.banner__arrow.r {
  right: 0;
}
</style>

从上面代码可以看到,每张图片都是绝对定位的形式,如果在加上一些方向的定位信息再配合 z-index 我们就能让它呈现出3D层级的摆放,相信这难不到你。但是,这些属性我们不能直接写死在样式里,因为我们等等还需要切换,所以我们把它们写在 JS 逻辑处理中,如下:

<script>
import { defineComponent, onMounted, ref } from "vue";
export default defineComponent({
  setup() {
     let json = [{
        width: 400,
        top: 20,
        left: 50,
        opacity: 60,
        zIndex: 2
    }, {
        width: 490,
        top: 70,
        left: 0,
        opacity: 88,
        zIndex: 3
    }, {
        width: 600,
        top: 100,
        left: 200,
        opacity: 100,
        zIndex: 4
    }, {
        width: 490,
        top: 70,
        left: 510,
        opacity: 80,
        zIndex: 3
    }, {
        width: 400,
        top: 20,
        left: 550,
        opacity: 88,
        zIndex: 2
    }];
    let images = [];
    let throttle = true; // 控制节流
    onMounted(() => {
        images = document.querySelectorAll('.banner__img');
        json.forEach((attr, index) => {
            if(index < images.length) animate(images[index], attr);
        });
    }); 
    
    // 获取元素原本的属性信息
    function getStyle(obj, attr) {
	return obj.currentStyle ? obj.currentStyle[attr] : window.getComputedStyle(obj, null)[attr];
    };
    // 主要逻辑
    function animate(obj, json, fn) {
      clearInterval(obj.timer);
      // 开启一个定时器
      obj.timer = setInterval(() => {
        let flag = true;
        for (let attr in json) {
          let current = 0; 
	  // 获取元素属性的当前值, 没有则取 0, 如果是透明度则先转为整数
	  current = attr == "opacity" ? Math.round(parseInt(getStyle(obj, attr) * 100)) || 0 : parseInt(getStyle(obj, attr));
	  // 计算一个 "叠加值", (目标值 - 当前值) / 10, 10不是固定, 可根据效果来调整, 就好比控制时间 
          let step = (json[attr] - current) / 10;
          // 大于零向上取整, 小于零向下取整
          step = step > 0 ? Math.ceil(step) : Math.floor(step);
          if (attr == "opacity") {
	    // 判断是否支持透明度属性
            if ("opacity" in obj.style) {
              obj.style.opacity = (current + step) / 100;
            } else {
              obj.style.filter = "alpha(opacity = " + (current + step) * 10 + ")";
            }
          } else if (attr == "zIndex") {
            obj.style.zIndex = json[attr];
          } else {
            obj.style[attr] = current + step + "px"; // 不断添加 "叠加值" 让当前值最终等于目标值
          }
          if (current != json[attr]) flag = false; // 当前值等于目标值, 即可停止定时器
        }
        if (flag) {
          clearInterval(obj.timer);
          // 执行回调
          fn && fn();
        }
      }, 10);
    };
    
    // 切换
    function changeBanner(flag) {
        if(flag) {
            json.push(json.shift());
        }else {
            json.unshift(json.pop());
        }
        // 重新调整样式
        json.forEach((attr, index) => {
            animate(images[index], attr, (() => {
                throttle = true;
            }))
        });
    }
    
    return {
        change(flag) {
           throttle && changeBanner(flag);
        }
    };
  };
});

代码量不多,我们先直接全放出来,再做详细解释,可能上面代码比较难的地方就是 animate() 这个函数了,但其实它的原理是前端很早以前做动画的经典实现过程。

回想前端以前,在没有 CSS3 、没有 Canvas 、更加没有 Vue 的时候,那会前端要实现一个动画需要怎么做呢?答案是 JS 硬撸,很多时候都是利用定时器然后不断去改变 Dom 元素的样式来实现。

而上面的 animate() 函数就是利用这个过程,当轮播在切换的时候,我们需要给每张图片的样式都重新赋新的属性值,但是这个赋值过程可不是直接就是把目标值往上怼就行了,我们是先获取了元素当前原本的属性值,再通过定时器慢慢去增加(当叠加值为负数, 就是减少)一个"叠加值",当增加到当前值等于目标值时,即可停止定时器,从而制造一种切换的动画效果。而这个"叠加值"和定时器的时长,是控制切换效果的关键,动画效果是否自然、会不会生硬就靠他俩来决定,可以根据自己的需要来调整。

知道大概的原理后,应该就很好读懂了吧?

194881BB.gif

总结

文章放的 gif 素材比较多,没办法,全部为了展示效果,如果你加载不出来,直接看 线上体验 即可啦,都以一样的,放素材纯粹为了吸引人,哈哈哈。

倔友们,告诉你们一个秘密,我发现30M的 gif 竟然都能上传,这...嘿嘿,虽然会被压缩。

对了,最后申明一下,页面上部分素材来源于网络,如有侵权之处,还望告知,立马整改,感谢感谢。



至此,本篇文章就写完啦,撒花撒花。

image.png

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。