Three.js实现脸书元宇宙3D动态Logo效果

目录
  • 背景
  • 什么是元宇宙
  • 实现效果
    • 试炼一:THREE.TorusGeometry
    • 试炼二:THREE.TorusKnotGeometry
    • 试炼三:THREE.TubeGeometry
    • 试炼四:Blender + Three.js
  • 用Blender建模
  • 加载Logo模型
  • 添加材质
  • 展示加载进度
  • 点击更换材质
  • 加载人物模型
  • 总结
  • 参考资料

本文主要讲述通过 Three.js + Blender 技术栈,实现 Meta 公司炫酷的 3D 动态 Logo,内容包括基础模型圆环、环面扭结、管道及模型生成、模型加载、添加动画、添加点击事件、更换材质等。

背景

Facebook 近期将其母公司改名为 Meta,宣布正式开始进军 元宇宙 🪐领域。本文主要讲述通过 Three.js + Blender 技术栈,实现 Meta 公司炫酷的 3D 动态 Logo,内容包括基础模型圆环、环面扭结、管道及模型生成、模型加载、添加动画、添加点击事件、更换材质等。

什么是元宇宙

元宇宙 Metaverse 一词源于 1992 年尼尔·斯蒂芬森的 《雪崩》,该书描述了一个平行于现实世界的虚拟世界 Metaverse,所有现实生活中的人都有一个网络分身 Avatar维基百科 对元宇宙的描述是:通过虚拟增强的物理现实,呈现收敛性和物理持久性特征的,基于未来互联网,具有链接感知和共享特征的 3D 虚拟空间。

元宇宙的内涵是吸纳了信息革命 5G/6G、互联网革命 web3.0、人工智能革命,以及 VRARMR,特别是游戏引擎在内的虚拟现实技术革命的成果,向人类展现出构建与传统物理世界平行的全息数字世界的可能性;引发了信息科学、量子科学,数学和生命科学的互动,改变科学范式;推动了传统的哲学、社会学甚至人文科学体系的突破;囊括了所有的数字技术。正如电影 《头号玩家》 的场景,在未来某一天,人们可以随时随地切换身份,自由穿梭于物理世界和数字世界,在虚拟空间和时间节点所构成的元宇宙中生活学习。

实现效果

进入正题,先来看看本文示例的实现效果。

🔗 在线预览:https://dragonir.github.io/3d-meta-logo (由于模型较大,加载进度可能比较缓慢,需要耐心等待)

开发实现

📌 注意:上述示例动图展示的是试炼四,不想看试错过程(试炼一、试炼二、试炼三)的,可直接跳转到试炼四段落查看详细实现流程。失败流程中都列出了难点,知道解决方案的大佬请在评论区不吝赐教。

开发之前我们先观察一下 Meta Logo,可以发现它是一个圆环经过对折扭曲形成的,因此实现它的时候可以从实现圆环开始。

试炼一:THREE.TorusGeometry

Three.js 提供的基础几何体 THREE.TorusGeometry(圆环),它是一种看起来像甜甜圈 🍩 的简单图形。主要参数:

  • radius:可选。定义圆环的半径尺寸。默认值是 1
  • tube:可选。定义圆环的管子半径。默认值是 0.4
  • radialSegments:可选。定义圆环长度方向上的分段数。默认值是 8
  • tubularSegments:可选。定义圆环宽度方向上的分段数。默认值是 6
  • arc:可选。定义圆环绘制的长度。取值范围是 02 * π。默认值是 2 * π(一个完整的圆)。

语法示例:

THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc);

😭 失败:没有找到扭曲圆环的方法。

试炼二:THREE.TorusKnotGeometry

THREE.TorusKnotGeometry 可以用来创建三维环面扭结,环面扭结是一种比较特别的结,看上去像一根管子绕着它自己旋转了几圈。主要参数:

  • radius:可选。设置完整圆环的半径,默认值是 1
  • tube:可选。设置管道的半径,默认值是 0.4
  • radialSegments:可选。指定管道截面的分段数,段数越多,管道截面圆越光滑,默认值是 8tubularSegments:可选。指定管道的分段数,段数越多,管道越光滑,默认值是 64
  • p:可选。决定几何体将绕着其旋转对称轴旋转多少次,默认值是 2
  • q:可选。决定几何体将绕着其内部圆环旋转多少次,默认值是 3

语法示例:

THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments , p, q);

😭 失败:没找到能够控制手动扭曲程度的方法。

试炼三:THREE.TubeGeometry

THREE.TubeGeometry 沿着一条三维的样条曲线拉伸出一根管。你可以指定一些定点来定义路径,然后使用 THREE.TubeGeometry 创建这根管。主要参数:

  • path:该属性用一个 THREE.SplineCurve3 对象来指定管道应当遵循的路径。
  • segments:该属性指定构建这个管所用的分段数。默认值为 64.路径越长,指定的分段数应该越多。
  • radius:该属性指定管的半径。默认值为 1.
  • radiusSegments:该属性指定管道圆周的分段数。默认值为 8,分段数越多,管道看上去越圆。
  • closed:如果该属性设置为 true,管道的头和尾会连起来,默认值为 false

代码示例

// ...
var controls = new function () {
  // 点的位置坐标
  this.deafultpoints = [
    [0, 0.4, -0.4],
    [0.4, 0, 0],
    [0.4, 0.8, 0.4],
    [0, 0.4, 0.4],
    [-0.4, 0, 0],
    [-0.4, 0.8, -0.4],
    [0, 0.4, -0.4]
  ]
  this.segments = 64;
  this.radius = 1;
  this.radiusSegments = 8;
  this.closed = true;
  this.points = [];
  this.newPoints = function () {
    var points = [];
    for (var i = 0; i < controls.deafultpoints.length; i++) {
      var _x = controls.deafultpoints[i][0] * 22;
      var _y = controls.deafultpoints[i][1] * 22;
      var _z = controls.deafultpoints[i][2] * 22;
      points.push(new THREE.Vector3(_x, _y, _z));
    }
    controls.points = points;
    controls.redraw();
  };
  this.redraw = function () {
    redrawGeometryAndUpdateUI(gui, scene, controls, function() {
      return generatePoints(controls.points, controls.segments, controls.radius, controls.radiusSegments,
        controls.closed);
    });
  };
};
controls.newPoints();
function generatePoints(points, segments, radius, radiusSegments, closed) {
  if (spGroup) scene.remove(spGroup);
  spGroup = new THREE.Object3D();
  var material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: false });
  points.forEach(function (point) {
    var spGeom = new THREE.SphereGeometry(0.1);
    var spMesh = new THREE.Mesh(spGeom, material);
    spMesh.position.copy(point);
    spGroup.add(spMesh);
  });
  scene.add(spGroup);
  return new THREE.TubeGeometry(new THREE.CatmullRomCurve3(points), segments, radius, radiusSegments, closed);
}
// ...

😊 勉强成功:但是管道连成的圆环不够圆,实现完美的圆弧需要精确的坐标,暂时没找到坐标计算方法。

试炼四:Blender + Three.js

虽然使用 THREE.TubeGeometry 可以勉强实现,但是效果并不好,要实现圆滑的环,需要为管道添加精确的扭曲圆环曲线路径函数。由于数学能力有限 🤕️,暂时没找到扭曲圆弧路径计算的方法。因此决定从建模层面解决。

成功 😄:但是手残的我使用 Blender 建模花费了大量的时间 💔

建模教程

B站 的时候发现了这位大佬发的宝藏视频,刚好解决了自己的难题。

🎦 传送门:【动态设计教程】AE+blender能怎么玩?脸书元宇宙Meta动态logo已完全解析,100%学会

用Blender建模

使用 Blender 进行建模,并导出可携带动画的 fbx 格式,导出的时候不要忘记勾选 烘焙动画 选项。

加载依赖

<script src="./assets/libs/three.js"></script>
<script src="./assets/libs/loaders/FBXLoader.js"></script>
<script src="./assets/libs/inflate.min.js"></script>
<script src="./assets/libs/OrbitControls.js"></script>
<script src="./assets/libs/stats.js"></script>

场景初始化

var container, stats, controls, compose, camera, scene, renderer, light, clickableObjects = [], mixer, mixerArr = [], manMixer;
var clock = new THREE.Clock();
init();
animate();
function init() {
  container = document.createElement('div');
  document.body.appendChild(container);
  // 场景
  scene = new THREE.Scene();
  scene.transparent = true;
  scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);
  // 透视相机:视场、长宽比、近面、远面
  camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(0, 4, 16);
  camera.lookAt(new THREE.Vector3(0, 0, 0));
  // 半球光源:创建室外效果更加自然的光源
  light = new THREE.HemisphereLight(0xefefef);
  light.position.set(0, 20, 0);
  scene.add(light);
  // 平行光
  light = new THREE.DirectionalLight(0x2d2d2d);
  light.position.set(0, 20, 10);
  light.castShadow = true;
  scene.add(light);
  // 环境光
  var ambientLight = new THREE.AmbientLight(0xffffff, .5);
  scene.add(ambientLight);
  // 网格
  var grid = new THREE.GridHelper(100, 100, 0xffffff, 0xffffff);
  grid.position.set(0, -10, 0);
  grid.material.opacity = 0.3;
  grid.material.transparent = true;
  scene.add(grid);
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 背景色设置为透明
  renderer.setClearAlpha(0);
  // 开启阴影
  renderer.shadowMap.enabled = true;
  container.appendChild(renderer.domElement);
  // 添加镜头控制器
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.target.set(0, 0, 0);
  controls.update();
  window.addEventListener('resize', onWindowResize, false);
  // 初始化性能插件
  stats = new Stats();
  container.appendChild(stats.dom);
}
// 屏幕缩放
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

📌 想了解场景初始化的详细流程,可阅读我的另一篇文章《使用three.js实现炫酷的酸性风格3D页面》。

加载Logo模型

使用 FBXLoader 加载模型,并设置模型的位置和大小。

var loader = new THREE.FBXLoader();
loader.load('assets/models/meta.fbx', function (mesh) {
  mesh.traverse(function (child) {
    if (child.isMesh) {
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
  mesh.rotation.y = Math.PI / 2;
  mesh.position.set(0, 1, 0);
  mesh.scale.set(0.05, 0.05, 0.05);
  scene.add(mesh);
});

添加材质

本文 Logo 使用的是 MeshPhysicalMaterial材质,它是一种 PBR 物理材质,可以更好的模拟光照计算,相比较高光网格材质 MeshPhongMaterial 渲染效果更逼真。使用 THREE.TextureLoader 为材质添加 map 属性来加载模型贴图。下图是金属质感的纹理贴图。

var texLoader = new THREE.TextureLoader();
loader.load('assets/models/meta.fbx', function (mesh) {
  mesh.traverse(function (child) {
    if (child.isMesh) {
      if (child.name === '贝塞尔圆') {
        child.material = new THREE.MeshPhysicalMaterial({
          map: texLoader.load("./assets/images/metal.png"),
          metalness: .2,
          roughness: 0.1,
          exposure: 0.4
        });
      }
    }
  });
})

添加动画

  • AnimationMixer 对象是场景中特定对象的动画播放器。当场景中的多个对象独立动画时,可以为每个对象使用一个 AnimationMixer
  • AnimationMixer 对象的 clipAction 方法生成可以控制执行动画的实例。
loader.load('assets/models/meta.fbx', function (mesh) {
  mesh.animations.map(item => {
    mesh.traverse(child => {
      // 因为模型中有多个物体,并且各自有不同动画,示例中只为贝塞尔圆这个网格添加动画
      if (child.name === '贝塞尔圆') {
        let mixer = new THREE.AnimationMixer(child);
        mixerArr.push(mixer);
        let animationClip = item;
        animationClip.duration = 8;
        let clipAction = mixer.clipAction(animationClip).play();
        animationClip = clipAction.getClip();
      }
    })
  })
});

添加动画之后,不要忘了要在 requestAnimationFrame 中更新动画。

function animate() {
  renderer.render(scene, camera);
  // 获得前后两次执行该方法的时间间隔
  let time = clock.getDelta();
  // 更新logo动画
  mixerArr.map(mixer => {
    mixer && mixer.update(time);
  });
  // 更新人物动画
  manMixer && manMixer.update(time);
  stats.update();
  requestAnimationFrame(animate);
}

展示加载进度

FBXLoader 同时返回两个回调函数,可以像下面这样使用,用来展示模型加载进程展示以及加载失败的逻辑实现。

<div class="loading" id="loading">
  <p class="text">加载进度<span id="progress">0%</span></p>
<div>

var loader = new THREE.FBXLoader();
loader.load('assets/models/meta.fbx', mesh => {
}, res => {
  // 加载进程
  let progress = (res.loaded / res.total * 100).toFixed(0);
  document.getElementById('progress').innerText = progress;
  if (progress === 100) {
    document.getElementById('loading').style.display = 'none';
  }
}, err => {
  // 加载失败
  console.log(err)
});

实现效果

点击更换材质

监听页面的点击事件,通过 HREE.Raycaster 拿到当前点击对象,为了展示例子,我为点击对象更换了一种材质 THREE.MeshStandardMaterial,并赋予它随机的 color 颜色、metalness 金属质感以及 roughness 粗糙程度。

//声明raycaster和mouse变量
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick(event) {
  // 通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  // 通过鼠标点的位置和当前相机的矩阵计算出raycaster
  raycaster.setFromCamera(mouse, camera);
  // 获取raycaster直线和所有模型相交的数组集合
  let intersects = raycaster.intersectObjects(clickableObjects);
  if (intersects.length > 0) {
    console.log(intersects[0].object)
    let selectedObj = intersects[0].object;
    selectedObj.material = new THREE.MeshStandardMaterial({
      color: `#${Math.random().toString(16).slice(-6)}`,
      metalness: Math.random(),
      roughness: Math.random()
    })
  }
}
window.addEventListener('click', onMouseClick, false);

📌 更多关于网格材质的知识,可参考文章末尾的链接。

加载人物模型

人物模型的加载流程和 Logo 模型加载流程是一样的。我添加了一个正在施展龟派气功的人物,没想到与 Logo 模型的旋转动画非常契合 😂

loader.load('assets/models/man.fbx', function (mesh) {
  mesh.traverse(function (child) {
    if (child.isMesh) {
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
  mesh.rotation.y = Math.PI / 2;
  mesh.position.set(-14, -8.4, -3);
  mesh.scale.set(0.085, 0.085, 0.085);
  scene.add(mesh);
  manMixer = new THREE.AnimationMixer(mesh);
  let animationClip = mesh.animations[0];
  let clipAction = manMixer.clipAction(animationClip).play();
  animationClip = clipAction.getClip();
}, res => {
  let progress = (res.loaded / res.total * 100).toFixed(0);
  document.getElementById('progress').innerText = progress + '%';
  if (Number(progress) === 100) {
    document.getElementById('loading').style.display = 'none';
  }
}, err => {
  console.log(err)
});

本文示例人物模型来源于mixamo.com,该网站有有上百种人物和上千种动作可自由组合,免费 下载。大家可以挑选自己喜欢的人物和动画动作来练习 Three.js

总结

本文中涉及到的主要知识点包括:

  • THREE.TorusGeometry:圆环。
  • THREE.TorusKnotGeometry:环面扭结。
  • THREE.TubeGeometry:管道。
  • Blender: 建模。
  • FBXLoader: 加载模型,显示加载进度
  • TextureLoader:加载材质。
  • THREE.AnimationMixer:加载动画。
  • THREE.Raycaster:捕获点击模型。

🔗 完整代码:https://github.com/dragonir/3d-meta-logo

参考资料

[1]. 使用three.js实现炫酷的酸性风格3D页面

[2]. ThreeJs认识材质

[3]. Three之Animation初印象

[4]. 什么是元宇宙?

作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/15574412.html

到此这篇关于Three.js实现脸书元宇宙3D动态Logo的文章就介绍到这了,更多相关Three.js3D动态Logo 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • three.js如何实现3D动态文字效果

    前言 大家好,这里是 CSS 魔法使--alphardex. 之前在逛国外网站的时候,发现有些网站的文字是刻在3D图形上的,并且能在图形上运动,视觉效果相当不错,于是笔者就也想用three.js来尝试复现出这种效果 上图只是所有效果的其中之一,接下来让我们一起开干吧~ 准备工作 笔者自行封装的three.js模板:Three.js Starter 读者可以点击右下角fork一份后再开始本项目 本项目需要用到位图字体,可以直接复制demo的HTML里的font字体代码 一个注意点:three-bm

  • 使用three.js 绘制三维带箭头线的详细过程

    需求:这个需求是个刚需啊!在一个地铁场景里展示逃生路线,这个路线肯定是要有指示箭头的,为了画这个箭头,我花了不少于十几个小时,总算做出来了,但始终有点问题.我对这个箭头的要求是,无论场景拉近还是拉远,这个箭头不能太大,也不能太小看不清,形状不能变化,否则就不像箭头了. 使用到了 three.js 的 Line2.js 和一个开源库MeshLine.js 部分代码: DrawPath.js: /** * 绘制路线 */ import * as THREE from '../build/three.

  • javascript Three.js创建文字初体验

    目录 效果 开始创建文本 首先创建字体加载器 加载字体库 创建文字几何体 计算文字几何体外边界矩形 创建镜像文字 创建半透明平面 渲染 关于文本构造器参数 当curveSegments设置越低时,可以看到文字没有那么圆滑 当开启了斜角时,可以观察到字体的边棱变得圆滑,不再锐利 设置斜角参数 完整代码 总结 效果 首先引入必要组件 import './build/three.js'; import './libs/js/controls/OrbitControls.js' import { Fon

  • 使用three.js实现炫酷的酸性风格3D页面效果

    本文内容主要介绍,通过使用React+three.js技术栈,加载3D模型.添加3D文字.增加动画.点击交互等,配合样式设计,实现充满设计感的

  • three.js中多线程的使用及性能测试详解

    前言 今天郭先生说一下WebWorker以及WebWorker在three.js中的应用.我们都知道Javascript是单线程的,比如执行js代码的同时UI渲染就会停止,对于多核CPU的点脑,这一点让人难以接受,好在Web Worker的出现多少解决了一些问题.官方说Web Worker指的是一种可由脚本创建的后台任务,任务执行中可以向其创建者收发信息.要创建一个 Worker ,只须调用 Worker(URL) 构造函数,函数参数 URL 为指定的脚本.关于Web Worker的更多知识请阅

  • three.js 实现露珠滴落动画效果的示例代码

    前言 大家好,这里是 CSS 魔法使--alphardex. 本文我们将用three.js来实现一种很酷的光学效果--露珠滴落.我们知道,在露珠从一个物体表面滴落的时候,会产生一种粘着的效果.2D平面中,这种粘着效果其实用css滤镜就可以轻松实现.但是到了3D世界,就没那么简单了,这时我们就得依靠光照来实现,其中涉及到了一个关键算法--光线步进(Ray Marching).以下是最终实现的效果图 撒,哈吉马路由! 准备工作 笔者的 three.js模板 :点击右下角的fork即可复制一份 正片

  • Three.js实现脸书元宇宙3D动态Logo效果

    目录 背景 什么是元宇宙 实现效果 试炼一:THREE.TorusGeometry 试炼二:THREE.TorusKnotGeometry 试炼三:THREE.TubeGeometry 试炼四:Blender + Three.js 用Blender建模 加载Logo模型 添加材质 展示加载进度 点击更换材质 加载人物模型 总结 参考资料 本文主要讲述通过 Three.js + Blender 技术栈,实现 Meta 公司炫酷的 3D 动态 Logo,内容包括基础模型圆环.环面扭结.管道及模型生成

  • 原生JS实现逼真的图片3D旋转效果详解

    本文实例讲述了原生JS实现逼真的图片3D旋转效果.分享给大家供大家参考,具体如下: 实现效果: 实现过程: 步骤一:先写一个简单的html结构,创建一个box盒子,里面放对应的图片(也可以用js创建图片,这里为了好理解,我们直接用html创建). <body><div class="box"> <img src="./img/1.jpg" alt=""> <img src="./img/2.jp

  • 正则 js分转元带千分符号详解

    可以通过缩放来进行分到元的转换,同时使用正则对处理后的数字进行千分位格式化 方法1:(不丢失精度) function Fen2Yuan( num ) { if ( typeof num !== "number" || isNaN( num ) ) return null; return ( num / 100 ).toFixed( 2 ); } 方法2: var num = 370825 num=num*0.01;//分到元 num+='';//转成字符串 var reg=num.in

  • JS+CSS实现鼠标滑过时动态翻滚的导航条效果

    本文实例讲述了JS+CSS实现鼠标滑过时动态翻滚的导航条效果.分享给大家供大家参考.具体如下: 这是一款鼠标悬停时动态翻滚的导航条,注意这里用了两个背景图,请等待网页加载完成或多刷新几次,这个是使用JavaScript实现的,不过代码好像是从jQuery里摘出来的,有点像. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/js-css-mouse-over-nav-scroll-style-codes/ 具体代码如下: <!DOCTYPE htm

  • JS实现下拉框的动态添加(附效果)

    效果展示: 页面初加载时:      选择车类型后:      选择车颜色后:     JS实现下拉框的动态添加,网页代码如下: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www

  • JS小游戏之宇宙战机源码详解

    本文实例讲述了JS小游戏的宇宙战机源码,分享给大家供大家参考.具体介绍如下: 一.游戏介绍: 这是一款飞行射击游戏,纵向,共六关. 二.游戏需求: 1.战机可发射子弹,子弹可通过获取道具升级. 2.战机可放bomb,可获取道具增加数量. 3.战机可蓄力攻击. 4.道具有三种,分别是升级子弹,增加bomb数量,增加战机数量. 5.每关音乐不同. 6.战机被击落后再进入战场,有保护状态. 7.敌机AI设计. 游戏运行如下图所示: 完整实例代码点击此处本站下载. 三.Javascript源码部分: /

  • JS中利用localStorage防止页面动态添加数据刷新后数据丢失

    非常不多说了,直接给大家贴代码了,具体代码如下所示: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,use

  • JS使用ajax从xml文件动态获取数据显示的方法

    本文实例讲述了JS使用ajax从xml文件动态获取数据显示的方法.分享给大家供大家参考.具体分析如下: 下面的JS代码通过ajax检索xml文件的内容动态展示到网页,真个页面无刷新 <!DOCTYPE html> <html> <head> <script> function loadXMLDoc(url) { var xmlhttp; var txt,x,xx,i; if (window.XMLHttpRequest) {// code for IE7+,

  • vue.js 嵌套循环、if判断、动态删除的实例

    Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.相比于Angular.js,Vue.js提供了更加简洁.更易于理解的API app.html <!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title> vuejs 嵌套循环.if判断 </title> <

随机推荐