“PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛”
写在开头
体验地址先上为敬:虎年主题网站
再附上源码以表诚意:虎年主题网站-源码
(体验完你会给小编点个赞不?)
所用技术
整个网站倒是没什么技术难点,做完给我感觉最困难、最麻烦的是各种素材和配色,真的令人头秃抓狂。
下面稍微介绍用到的一些相关技术:
Vue3+Vite2
:用于快速开发,Vite
的快是众所周知的,用上了就等于爱上了,会无法自拔。
Animate.css
:这是一个 CSS3
动画库,相信各位都不会太陌生了。
JQuery
:会用到 jquery
主要是在雪景制作上,还有入场动画部分。
Waypoints
:一个用来实现捕获页面各种滚动事件的 JS
工具库,挺好用的。
剩下的一些页面效果就基本是手撸搞定,都不是很难,下面抽一些有意思的来介绍介绍。
正文
页面动画效果可能会比较多,为了第一眼看起来比较酷炫一点,俺左思右想,几乎是能加动画效果的地方,小编是一点都没放过。(^ω^)
大红门
网站入眼就是一个大红门,本来理想中的样子是:
但真实做出来却是:
呃......只能说是朴实无华了,简约才大气,是吧?(^ω^)
"福" 字效果是用 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;
}
入场动画
页面主要看点应该就是每个元素的入场动画了。
而这使用的是 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>
灯笼
过年肯定少不了灯笼啦,红红的灯笼才有过年的氛围。
最近几天在掘金看到好多倔友都画了很好看的灯笼,咱也得来画一个凑凑热闹呀。
<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
属性即可画出不错的弧度,当然配色才是关键!!!
阅读窗口
阅读窗口 这是一个可以放置大量内容的组件,一个网站总要有些内容,要不是没灵魂的。 (✪ω✪)
而它的实现原理也比较简单,如下图:
看图应该也比较好明白吧?我们仅需控制内层元素 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>
照片墙
照片墙 组件,呃...这...好像也没啥难度吧,就是摆放好看?哦,也不是,应该是素材好看?
好吧,不管如何,作为一名程序猿,咱还是得提一下代码才算走了流程,我们稍微来看看图中的六边形是如何做的?
首先,我们以两个 <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);
}
当然,这要控制好宽度和高度才行,得慢慢微调才行。
这样子我们可以得到一个红色的六边形,但是,这还没完,你发没发现图中的文字也被旋转了!这可不是我们想要的,我们再来增加一个 <div />
元素。
<div class="main-box">
<div class="box bg1">
<div class="box bg2">
<div class="box bg3">橙某人</div>
</div>
</div>
</div>
再看,神奇的事情发生了,文字正回来了。其实这也没啥,就是刚好第三个 <div />
旋转了 360°
度,所以和没旋转一样。
因为每个元素都是设置了 absolute
属性并旋转 120°
度,下一个元素会在上一个元素旋转了 120°
度的基础上再旋转 120°
度,所有第三个 <div />
元素一共会旋转了 360°
度,就是这么个原理了,最后记得把相关背景颜色去掉,就大功告成了。
雪景
而页面中雪景效果的实现,小编是直接在 Github
上拉一个 snow.js 库实现的,比较简单,直接导入即可使用。不过,它依赖 JQuery
库,是比较老旧的技术了,现在用 Canvas
技术能更简单、方便地实现这种效果。
轮播
最后,要介绍的就是这个有点像3D的轮播效果了,它实现起来虽然不难,但是挺巧妙的。o(^▽^)o
我们先来看看它的结构和样式:
<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()
函数就是利用这个过程,当轮播在切换的时候,我们需要给每张图片的样式都重新赋新的属性值,但是这个赋值过程可不是直接就是把目标值往上怼就行了,我们是先获取了元素当前原本的属性值,再通过定时器慢慢去增加(当叠加值为负数, 就是减少)一个"叠加值",当增加到当前值等于目标值时,即可停止定时器,从而制造一种切换的动画效果。而这个"叠加值"和定时器的时长,是控制切换效果的关键,动画效果是否自然、会不会生硬就靠他俩来决定,可以根据自己的需要来调整。
知道大概的原理后,应该就很好读懂了吧?
总结
文章放的 gif
素材比较多,没办法,全部为了展示效果,如果你加载不出来,直接看 线上体验 即可啦,都以一样的,放素材纯粹为了吸引人,哈哈哈。
倔友们,告诉你们一个秘密,我发现30M的 gif
竟然都能上传,这...嘿嘿,虽然会被压缩。
对了,最后申明一下,页面上部分素材来源于网络,如有侵权之处,还望告知,立马整改,感谢感谢。
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。