CSS+HTML<3D无人机>
2020-08-22 本文已影响0人
誰在花里胡哨
效果图:

主要是因为前几天买了一架大疆无人机(😁这里没算打广告啊,大疆就是好~~~),然后公司这几天也不是很忙,所以就想着搞一个无人机模型。起初是想学下3D Max建一个无人机模型,后来不知道怎么。就想试试看看html能不能拼凑一个,虽然很费时间,但是我成功了!!

这里基本就是用的css+html的一些基础知识,我只不是通过vue框架来写的。主要用于组件化的封装,这样就会节省时间了。下面主要跟大家说下涉及到的知识点和实现思路~~~~
知识点:
1.css+html,涉及了一点js(主要是用来控制无人机的移动用的);
2.transform-style: preserve-3d; css的3d旋转属性;
3.perspective css的景深属性;
4.filter: drop-shadow ; css的滤镜阴影穿透;
5.svg的path路径填充和描边;
6.animation css动画;
实现思路:

主要讲一下怎么拼成的模型吧。其实并不难,主要是耗时间,慢慢的拼凑。分为3个部分:
机桨(yepian.vue),机身(shenti.vue),机架(jijia.vue),合成部份(index.vue)。


机架和机桨可以合成一个组件,然后其它三个就是公用的了,只需要旋转下角度就可以了。
机桨(yepian.vue):

<template>
<div id="yepian" :class="flying?'flying':''">
<div class="yepian z">
<div class="uav ye_t">
<svg>
<path
id="svg_3"
d="m0.27471,0.21492l49.39565,34.69736l0,164.41747l-49.45058,-43.14501l0.05493,-155.96983z"
/>
</svg>
</div>
<div class="uav ye_b">
<svg>
<path
id="svg_4"
d="m-0.55553,-0.29852l49.8888,42.52069l0,108.44431l-18.66664,0l-31.22216,-150.965z"
/>
</svg>
</div>
</div>
<div class="yepian f">
<div class="uav ye_t">
<svg>
<path
id="svg_3"
d="m0.27471,0.21492l49.39565,34.69736l0,164.41747l-49.45058,-43.14501l0.05493,-155.96983z"
/>
</svg>
</div>
<div class="uav ye_b">
<svg>
<path
id="svg_4"
d="m-0.55553,-0.29852l49.8888,42.52069l0,108.44431l-18.66664,0l-31.22216,-150.965z"
/>
</svg>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
flying: Boolean
}
};
</script>
<style lang="scss" scoped>
.flying {
animation: xuanzhuan .1s linear infinite;
filter: blur(30px);
}
#yepian {
transform: translateZ(45px);
transform-style: preserve-3d;
.uav {
border: none !important;
svg {
width: 100%;
height: 100%;
path {
opacity: 0.8;
stroke-width: 1;
}
}
}
}
@keyframes xuanzhuan {
0% {
transform: rotate(0deg) translateZ(45px);
}
100% {
transform: rotate(360deg) translateZ(45px);
}
}
.ye_t,
.ye_b {
width: 50px;
// background: red;
overflow: hidden;
}
.ye_t {
height: 200px;
path {
fill: rgb(94, 94, 94);
}
}
.ye_b {
height: 151px;
position: relative;
top: -45px;
transform-origin: 50% 0;
transform: rotateX(-20deg) rotateY(-18deg) translateZ(8px) rotateZ(-3deg);
path {
fill: rgb(56, 56, 56);
}
}
.yepian {
transform-style: preserve-3d;
position: relative;
&.z {
left: -5px;
top: 50px;
transform: rotateY(20deg);
}
&.f {
left: 5px;
top: -50px;
transform: scaleY(-1) scaleX(-1) rotateY(20deg);
}
}
</style>
机身(shenti.vue):

<template>
<div id="shenti">
<!-- 身体组件 -->
<div class="uav shen_t">
<svg>
<path
id="svg_1"
d="m0.71429,138.29017l139.28571,-138.29017l227.14286,0l132.85714,138.57143l-122.85714,661.42857l-235.71429,0l-140.71429,-661.70983z"
/>
</svg>
<div class="uav shen_tr">
<svg>
<path
id="svg_2"
d="m0,-0.99554l347.14286,153.85268l0,518.57143l-347.14286,0l0,-672.42411z"
/>
</svg>
</div>
<div class="uav shen_tl">
<svg style="transform: scaleX(-1);">
<path
id="svg_2"
d="m0,-0.99554l347.14286,153.85268l0,518.57143l-347.14286,0l0,-672.42411z"
/>
</svg>
</div>
<div class="uav shen_b">
<svg>
<path
id="svg_2"
d="m0.71429,138.29017l139.28571,-138.29017l227.14286,0l132.85714,138.57143l-122.85714,661.42857l-235.71429,0l-140.71429,-661.70983z"
/>
</svg>
<div class="b_b" v-for="item in 3" :key="item" :style="`transform: scale(${.1*item + .4}) translateY(${-70*item}px);`">
<div class="l uav-boder"></div>
<div class="r uav-boder"></div>
</div>
</div>
<div class="uav shen_ht">
<svg>
<path
id="svg_4"
d="m0.15986,-0.09215l235.45647,0.09215l-40.75344,150.68487l-162.0769,-0.45662l-32.62613,-150.32039z"
/>
</svg>
</div>
<div class="uav shen_hb">
<svg>
<path
id="svg_4"
d="m0.15986,-0.09215l235.45647,0.09215l-40.75344,150.68487l-162.0769,-0.45662l-32.62613,-150.32039z"
/>
</svg>
</div>
<div class="uav shen_hbr">
<svg>
<path id="svg_1" d="m-0.25,0.22656l349.75,-0.22656l-90,151l-259.75,-150.77344z" />
</svg>
</div>
<div class="uav shen_hbl">
<svg style="transform: scaleX(-1)">
<path id="svg_1" d="m-0.25,0.22656l349.75,-0.22656l-90,151l-259.75,-150.77344z" />
</svg>
</div>
<div class="uav logo">
<svg>
<path
id="svg_4"
d="m86.8605,43.58286l-21.74425,-21.7224l-42.79068,42.79068l40.93022,18.13953l-39.99998,47.44184l70.69765,-45.58138l-40.4651,-26.97673l33.37215,-14.09153z"
/>
<path
id="svg_5"
d="m130.1163,43.11774l23.83713,65.2543l17.67441,-35.81394l21.39534,33.95347l22.32557,-63.7209l-26.0465,29.30231l-16.27906,-31.16278l-18.60464,30.69766l-24.30225,-28.51013z"
/>
<path
id="svg_6"
d="m279.88368,64.51309l-31.04657,-33.35031l24.18603,50.69765l-21.86045,33.95348l29.76743,-22.79069l27.44185,22.32557l-18.60465,-37.20929l27.44185,-49.76742l-37.32549,36.14101z"
/>
</svg>
</div>
</div>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
#shenti {
position: relative;
transform-style: preserve-3d;
.uav {
border: none ;
svg {
width: 100%;
height: 100%;
path {
stroke-width: 1;
opacity: 0.9;
}
}
}
}
.shen_t,
.shen_b {
width: 501px;
height: 801px;
position: relative;
}
.shen_t {
path {
fill: #808080;
}
}
.shen_b {
transform: scale(0.76) translateZ(-173px) translateY(115px);
position: absolute;
bottom: 0;
path {
fill: #ccc;
}
.b_b {
width: 300px;
height: 30px;
display: flex;
justify-content: space-between;
position: absolute;
left: 110px;
bottom: 250px;
.l {
width: 100px;
height: 30px;
background: #808080;
transform: skewX(45deg);
}
.r {
width: 100px;
height: 30px;
background: #808080;
transform: skewX(-45deg);
}
}
}
.shen_tr,
.shen_tl {
width: 350px;
height: 672px;
position: absolute;
bottom: 0;
path {
fill: #a0a0a0;
}
}
.shen_tr {
right: -225px;
transform-origin: 0 100%;
transform: rotateY(100deg) rotateX(-10.5deg) rotateZ(-2deg) scaleX(0.5);
}
.shen_tl {
left: -210px;
transform-origin: 100% 100%;
transform: rotateY(-100deg) rotateX(-11.5deg) rotateZ(2deg) scaleX(0.5);
}
.shen_ht,
.shen_hb {
position: absolute;
bottom: -150px;
left: 141px;
width: 240px;
height: 151px;
}
.shen_ht {
transform-origin: 50% 0;
transform: rotateX(-60deg);
path {
fill: #a0a0a0;
}
}
.shen_hb {
transform-origin: 50% 0;
transform: translateZ(-173px) translateY(-9px) scaleX(0.75) scaleY(0.58)
rotateX(17deg);
path {
fill: #ccc;
}
}
.shen_hbr,
.shen_hbl {
width: 350px;
height: 150px;
position: absolute;
bottom: -149px;
path {
fill: #a0a0a0;
}
}
.shen_hbr {
right: -225px;
transform-origin: 0% 0%;
transform: rotateY(100deg) rotateZ(-2deg) rotateX(-12deg) scaleY(0.53)
scaleX(0.5);
}
.shen_hbl {
left: -210px;
transform-origin: 100% 0%;
transform: rotateY(-100deg) rotateZ(2deg) rotateX(-8deg) scaleX(0.5)
scaleY(0.53);
}
.logo {
width: 350px;
height: 150px;
transform: scale(0.5) translateZ(1px);
z-index: 6;
position: absolute;
left: 85px;
bottom: 50px;
path {
fill: white;
}
}
</style>
机架(jijia.vue):
这里我直接把机桨和机架合到了一起,方便后面的直接使用。

<template>
<div id="jijia">
<div :id="jiwei?'ji':''">
<div class="uav ji-1 t"></div>
<div class="uav ji-1 b"></div>
<div class="uav ji-1 q"></div>
<div class="uav ji-1 h"></div>
<div class="uav ji-2 t"></div>
<div class="uav ji-2 b"></div>
<div class="uav ji-2 q"></div>
<div class="uav ji-2 h"></div>
<uav-yepian class="uav-yepian" :flying="flying"></uav-yepian>
</div>
</div>
</template>
<script>
import yepian from "./yepian";
export default {
props: {
flying: Boolean,
jiwei: Boolean
},
components: {
"uav-yepian": yepian
}
};
</script>
<style lang="scss" scoped>
#jijia {
position: relative;
transform-style: preserve-3d;
width: 3px;
.uav{
opacity: .7;
}
}
#ji {
transform-style: preserve-3d;
transform-origin: 0 50%;
transform: scaleX(0.7);
}
.uav-yepian {
position: absolute;
left: 400px;
top: -430px;
}
.ji-1 {
width: 600px;
height: 50px;
position: absolute;
left: -300px;
&.t {
transform: skewX(-45deg);
background: #a3a3a3;
}
&.b {
transform: translateZ(-50px) skewX(-45deg);
background: #ccc;
}
&.q {
transform: rotateX(-90deg) translateY(25px) translateZ(-25px)
translateX(25px);
background: #b6b6b6;
}
&.h {
width: 658px;
transform: rotateX(-90deg) translateY(25px) translateZ(25px)
translateX(-25px);
background: #b6b6b6;
}
}
.ji-2 {
width: 60px;
height: 150px;
position: absolute;
top: -100px;
right: -408px;
&.t {
transform: skewX(-45deg);
background: #a3a3a3;
}
&.b {
transform: translateZ(-50px) skewX(-45deg);
background: #ccc;
}
&.q {
width: 50px;
transform: rotateY(90deg) rotateX(-45deg) translateX(25px) translateZ(-25px)
translateY(-10px) scaleY(0.95);
background: #ccc;
}
&.h {
width: 50px;
height: 225px;
transform: rotateY(90deg) rotateX(-45deg) translateX(25px) translateZ(-10px)
translateY(-44px) scaleY(0.945);
background: #ccc;
}
}
</style>
合成部份(index.vue):

<template>
<div class="background" :class="uav_line?'black':''">
<div class="cover" v-if="!fly">
<div class="center">
<h1>start</h1>
</div>
<!-- <div class="btm-msg">
<div>
<p>W:垂直向上 上升</p>
<p>S:垂直向下 下降</p>
<p>A:水平向左 左转</p>
<p>D:水平向右 右转</p>
</div>
<div>
<p>I:向前偏移 前进</p>
<p>K:向后偏移 后退</p>
<p>J:向左偏移 左飞</p>
<p>L:向右偏移 右飞</p>
</div>
</div>-->
</div>
<div
class="body"
:style="`filter: drop-shadow(0px ${uav_shadow}px ${uav_shadow/10}px ${fly&&uav_vertical>20?'rgba(0, 0, 0, 0.7)':'rgba(0, 0, 0, 0.0ƒ)'});`"
>
<!-- 无人机 -->
<div
@click="uavFly()"
id="container"
:style="`transform:translateY(${-uav_vertical}px) translateX(${uav_lr_to}px) translateZ(${-uav_qh_to}px) scale(.15) rotateX(${container_rotate}deg);`"
>
<div
:id="uav_line?'uav':''"
class="uav"
:style="`transform: rotateZ(${uav_level}deg) rotateY(${uav_lr}deg) rotateX(${uav_qh}deg)`"
>
<uav-jijia :flying="fly" class="uav-jijia-qr"></uav-jijia>
<uav-jijia :flying="fly" class="uav-jijia-ql"></uav-jijia>
<uav-shenti></uav-shenti>
<uav-jijia :flying="fly" :jiwei="true" class="uav-jijia-hr"></uav-jijia>
<uav-jijia :flying="fly" :jiwei="true" class="uav-jijia-hl"></uav-jijia>
</div>
</div>
</div>
</div>
</template>
<script>
import { shenti, jijia } from "./components/index";
export default {
components: {
"uav-shenti": shenti,
"uav-jijia": jijia
},
data() {
return {
window_w: window.innerWidth,
window_h: window.innerHeight,
fly: false, //是否已经起飞
uav_line: false, //是否转线条或者实物无人机
container_rotate: 0,
uav_shadow: 0, //默认无人机阴影位置
uav_sensitivity: 2, //灵敏度 默认为1
uav_level: 0, //无人机水平角度
uav_vertical: 0, //无人机垂直高度
uav_lr: 0, //无人机左右偏移
uav_qh: 0, //无人机前后偏移
uav_lr_to: 0, //无人机左右偏移飞行距离
uav_qh_to: 0 //无人机前后偏移飞行距离
};
},
mounted() {
this.listenKeydown();
},
methods: {
//监听键盘事件
listenKeydown() {
let that = this;
let sen = that.uav_sensitivity;
let level_num = 0; //初始水平偏移为 0
let vertical_num = 0; //初始垂直偏移为 0
let lr_num = 0; //初始左右偏移量为 0
let qh_num = 0; //初始前后偏移量为 0
let lr_ing = false; //左右偏移是否正在执行
let qh_ing = false; //前后偏移是否正在执行
document.addEventListener(
"keydown",
e => {
var ev = e || window.event;
console.log(ev.keyCode);
switch (ev.keyCode) {
//用户按 A 键,无人机水平往左旋转
case 65:
level_num = -1;
break;
//用户按 D 键,无人机水平往右旋转
case 68:
level_num = 1;
break;
//用户按 W 键,无人机垂直上升
case 87:
//达到最大高度 200 时不再上升
if (that.uav_vertical > that.window_h / 2) {
vertical_num = 0;
} else {
vertical_num = 1;
}
break;
//用户按 S 键,无人机垂直下降
case 83:
//达到最低高度时不再下降
if (that.uav_vertical < 100) {
vertical_num = 0;
} else {
vertical_num = -1;
}
break;
//用户按 J 键,无人机往左偏移
case 74:
if (that.uav_lr_to < -that.window_w / 3) {
lr_num = 0;
} else {
lr_num = -0.5;
}
lr_ing = true;
break;
//用户按 L 键,无人机往右偏移
case 76:
if (that.uav_lr_to > that.window_w / 3) {
lr_num = 0;
} else {
lr_num = 0.5;
}
lr_ing = true;
break;
//用户按 I 键,无人机往前偏移
case 73:
qh_num = 0.5;
qh_ing = true;
break;
//用户按 K 键,无人机往后偏移
case 75:
qh_num = -0.5;
qh_ing = true;
break;
//用户按 空格 键,转换为线条或者实体无人机
case 32:
that.uav_line = !that.uav_line;
break;
default:
break;
}
},
false
);
document.addEventListener(
"keyup",
e => {
var ev = e || window.event;
switch (ev.keyCode) {
case 65:
level_num = 0;
break;
case 68:
level_num = 0;
break;
case 87:
vertical_num = 0;
break;
case 83:
vertical_num = 0;
break;
case 74:
lr_num = -1;
lr_ing = false;
break;
case 76:
lr_num = 1;
lr_ing = false;
break;
case 73:
qh_num = 1;
qh_ing = false;
break;
case 75:
qh_num = -1;
qh_ing = false;
break;
default:
break;
}
},
false
);
//为了实现无人机上升和旋转同时操作
function init() {
if (that.fly) {
//水平垂直偏移处理
that.uav_level += level_num * sen;
that.uav_vertical += vertical_num * sen;
//阴影偏移
that.uav_shadow += vertical_num * sen;
//如果正在执行,偏移时有最大值
//左右偏移处理 达到临界值松开回正归0
if (lr_ing) {
that.uav_lr = Math.min(40, that.uav_lr + lr_num * sen);
that.uav_lr = Math.max(-40, that.uav_lr + lr_num * sen);
// that.uav_lr += lr_num * sen;
//是否允许左右移动
that.uav_lr_to += lr_num * sen * 2;
} else {
if (lr_num > 0) {
if (0 <= that.uav_lr) {
that.uav_lr -= lr_num * sen;
}
} else {
if (that.uav_lr <= 0) {
that.uav_lr += -lr_num * sen;
}
}
}
if (qh_ing) {
that.uav_qh = Math.min(20, that.uav_qh + qh_num * sen);
that.uav_qh = Math.max(-20, that.uav_qh + qh_num * sen);
// that.uav_qh += qh_num * sen;
//是否允许前后移动
that.uav_qh_to += qh_num * sen * 5;
} else {
if (qh_num > 0) {
if (0 <= that.uav_qh) {
that.uav_qh -= qh_num * sen;
}
} else {
if (that.uav_qh <= 0) {
that.uav_qh += -qh_num * sen;
}
}
}
}
window.requestAnimationFrame(init);
}
init();
},
//点击无人机起飞
uavFly() {
let container = document.getElementById("container");
container.style.top = "20%";
let timer = setInterval(() => {
this.container_rotate += 1;
if (80 <= this.container_rotate) {
clearInterval(timer);
}
}, 10);
setTimeout(() => {
this.fly = true;
}, 1000);
}
}
};
</script>
<style lang="scss" >
:root {
--uavColor: #ccc;
--uavBorder: white;
}
#uav {
transform-origin: 50% 50%;
.uav,
.uav-boder {
background: transparent !important;
border: 5px solid var(--uavBorder);
box-sizing: border-box;
svg path {
opacity: 1 !important;
fill: transparent !important;
stroke: var(--uavBorder);
stroke-width: 5 !important;
}
}
}
</style>
<style lang="scss" scoped>
.cover {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
.center {
position: relative;
z-index: 99;
width: 150px;
height: 150px;
border-radius: 50%;
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
text-transform: uppercase;
// animation: change 0.5s linear infinite alternate;
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background: white;
animation: change 1s linear infinite alternate;
opacity: 0.3;
}
&::after {
opacity: 0.3;
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background: white;
animation: change 1s linear infinite alternate-reverse;
}
h1 {
position: relative;
z-index: 5;
font-size: 17px;
letter-spacing: 0.2rem;
font-weight: 100;
color: white;
text-shadow: 0 0 3px black;
}
}
@keyframes change {
0% {
transform: scale(1);
}
100% {
transform: scale(0.2);
}
}
.btm-msg {
width: 100%;
padding: 50px 100px;
box-sizing: border-box;
position: absolute;
bottom: 0;
display: flex;
justify-content: space-between;
p {
font-size: 12px;
font-weight: bold;
margin-bottom: 5px;
}
}
}
.background {
// 整体背景颜色
background: #f1f1f1;
transition: .3s;
&.black{
background: rgb(34, 34, 34);
}
.body {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
perspective: 10000;
}
}
//无人机容器
#container {
cursor: pointer;
transform-style: preserve-3d;
position: relative;
top: 0;
transition: top 0.5s;
}
.uav {
transform-style: preserve-3d;
}
.shenti {
width: 100px;
height: 200px;
background: #ccc;
}
.dibu {
width: 100px;
height: 100px;
background: #808080;
transform-origin: 0 0;
transform: rotateX(-50deg);
}
.uav-jijia-qr {
left: 570px;
transform: rotateZ(-33deg) translateZ(-50px) translateY(200px);
}
.uav-jijia-ql {
left: -70px;
transform: rotateZ(33deg) translateZ(-50px) translateY(200px) scaleX(-1);
}
.uav-jijia-hr {
left: 585px;
transform: rotateZ(47deg) translateZ(-80px) translateY(130px) scaleY(-1);
}
.uav-jijia-hl {
left: -77px;
transform: rotateZ(-47deg) translateZ(-80px) translateY(130px) scaleY(-1)
scaleX(-1);
}
</style>