更新记录

1.1.2(2025-03-21) 下载此版本

  • 修复上个版本设置devicePixelRatio导致小程序点击模型位置偏移问题 对应的方法是/threeJs/utils/useThree.js getContainerSize获取canvas尺寸相关逻辑
  • 修复isInit一直为false导致不执行下一步 这是因为上次兼容vue2把isInit变成普通变量了,这次修改为了对象的形式const isInit = {value:false},export依然可以引用到最新的值
  • 修复代码中的注释错别字

提示,群里有兄弟不建议renderjs+setup,在论坛也能搜索到其他开发遇到的一些问题,我没有深入研究使用,大家自行斟酌吧。

1.1.1(2025-03-13) 下载此版本

  • 修复傻瓜预览版demo无法切换模型问题
  • 修改devicePixelRatio最小值为2 主要为了抗锯齿的---renderer.setPixelRatio(devicePixelRatio)
  • 增加vue组件stage-v2.vue 只有组件没有演示demo,使用方式参考/page/index/index.vue中代码,改写成vue2写法即可。 同时核心逻辑代码去除vue3相关代码,为了兼容vue2

1.1.0(2025-01-16) 下载此版本

  • 修改preview3D组件中lib.js的引入路径为相对路径
  • preview3D增加自动旋转功能
复制代码// 如果是app的话建议自己写逻辑,而不是使用预览组件,外部调用renderjs中的方法很麻烦,
// 如果是同一个vue文件的话就可以直接调用renderjs中的方法,这样可以定制自己的页面逻辑和功能
// 参考如下,或者查看stageApp.vue 是通过three.init初始化的

<template>
<div>
<button @click="three.xx"></button>
</div>
</template>

<script module="three" lang="renderjs">
    export default {
        methods: {
            xx() {
                console.log('xxx')
            }
        }
    }
</script>
查看更多

平台兼容性

uni-app

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × - - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 快应用-华为 快应用-联盟
× × × × - × × × ×

其他

多语言 暗黑模式 宽屏模式
× ×

简介

此组件仅为快速展示 3D 模型的 demo。 使用 vue3 + three-platformize 实现。 h5和app用的是官方的threejs,app是基于renderjs实现的 目前仅支持微信小程序、抖音小程序,其他小程序可以参考代码修改,或者查看 three-platformize 官网

相关库 github 地址 vuethree.jsthree-platformize

使用说明

建议先下载示例代码运行试试

插件命名原则导致需要组件拼接一个前缀,请直接下载示例工程

  • 将 components 下的 threeJs 组件移动到自己项目 omponents 下
  • 下载 three-platformize
复制代码npm i three-platformize@1.133.3
  • 引入到组件和相关工具方法
复制代码<template>
  <view class="container">
    <!-- onload事件代表初始化完毕 -->
    <Stage @onload="onload"> </Stage>
  </view>
</template>
复制代码import * as THREE from "three-platformize";
import Stage from "@/components/threeJs/stage.vue";
import useThreeJs from "@/components/threeJs/index.js";

const {
  getInstance, // 获取canvas、camera、等实例
  centerAndScale, // 用于调整模型大小和位置
  setEnv, // 设置环境贴图
  screenshot, // 截图
} = useThreeJs();

const loadModel = () => {
  const { group, loader } = getInstance();
  // 如果是.gltf+.bin+texture 请手动转成glb,相关nodejs代码可以参考使用说明
  loader
    .load(
      "https://www.ccnihao.fun/public-resource/DamagedHelmet.gltf.glb",
      (p) => {
        // 这里是加载进度
        // 需要后台正确的设置Content-Length才会有进度
      }
    )
    .then((gltf) => {
      group.add(gltf.scene);
      // 居中并缩放到合适的位置
      centerAndScale();
    });
};

// 初始化完毕,开始设置环境贴图,并加载模型
const onload = async () => {
  console.log("onload");
  // 设置环境贴图,传入hdr文件在线地址即可
  await setEnv(
    "https://www.ccnihao.fun/public-resource/studio_small_09_1k.hdr",
    false
  );
  // 加载模型逻辑
  loadModel();
};

工具相关

  • obj 转 glb
复制代码# 先安装obj2gltf
npm i obj2gltf
复制代码// nodejs
const obj2gltf = require("obj2gltf");
const fs = require("fs");
const path = require("path");

(async () => {
  // obj 2 glb
  const outPath = "需要导出的路径";
  const inPath = "需要转换的obj文件路径";
  const options = {
    binary: true, // 不传默认导出gltf
  };
  const glb = await obj2gltf(inPath, options);
  const outFile = path.join(
    outPath,
    `newmodel.${options.binary ? "glb" : "gltf"}`
  );
  if (options.binary) {
    fs.writeFileSync(outFile, glb);
  } else {
    const data = Buffer.from(JSON.stringify(glb));
    fs.writeFileSync(outFile, data);
  }
})();
  • gltf 转 glb
复制代码# 先安装gltf-pipeline
npm i gltf-pipeline
复制代码// nodejs
const gltfPipeline = require("gltf-pipeline");
const fsExtra = require("fs-extra");
const path = require("path");
(() => {
  // gltf 2 glb
  const outPath = "需要导出的路径";
  const inPath = "需要转换的obj文件路径";
  const gltfToGlb = gltfPipeline.gltfToGlb;
  const gltf = fsExtra.readJsonSync(inPath);
  const dir = path.dirname(inPath);
  const options = { resourceDirectory: dir };
  gltfToGlb(gltf, options).then(function (results) {
    const outFile = path.join(outPath, `newmodel.glb`);
    fsExtra.writeFileSync(outFile, results.glb);
  });
})();

常见问题

  • ios 端 bounce 特性问题

    主要是会与 OrbitControls 控制器冲突

复制代码// 在ios端即使页面尺寸刚好是100vw * 100vh 没有滚动条情况,也会出现拖动回弹的情况(就是页面可以拖动然后松开手指回到初始为止)
// 这时在pages.json中对应的page添加disableScroll即可
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "threeJs",
        "navigationStyle": "custom", // 隐藏原生导航栏
        "enablePullDownRefresh": false, // 禁止下拉刷新
        "disableScroll": true // 在内容刚好沾满屏幕的时候,ios设备可以拖动然后立马回弹,有些情况不是很好用。禁止ios页面回弹(bounce效果)
      }
    }
  ]
}
  • 在抖音小程序上使用本插件
  1. three-platformize 的 BytePlatform 代码中有个 bug,导致一直报错无法使用控制器
复制代码// 主要是修改/node_modules/three-platformize/src/BytePlatform/index.js 文件
// 找到dispatchTouchEvent方法,在if (changedTouches.length)上面加一行 const changedTouches = e.changedTouches;

  dispatchTouchEvent(e = {}) {
    const target = { ...this };

    const event = {
      changedTouches: e.changedTouches.map(touch => new Touch(touch)),
      touches: e.touches.map(touch => new Touch(touch)),
      targetTouches: Array.prototype.slice.call(
        e.touches.map(touch => new Touch(touch)),
      ),
      timeStamp: e.timeStamp,
      target: target,
      currentTarget: target,
      type: e.type,
      cancelBubble: false,
      cancelable: false,
    };

    this.canvas.dispatchEvent(event);
    const changedTouches = e.changedTouches; // 新加的
    if (changedTouches.length) {
      const touch = changedTouches[0];
      const pointerEvent = {
        pageX: touch.pageX,
        pageY: touch.pageY,
        pointerId: touch.identifier,
        type: {
          touchstart: 'pointerdown',
          touchmove: 'pointermove',
          touchend: 'pointerup',
        }[e.type],
        pointerType: 'touch',
      };

      this.canvas.dispatchEvent(pointerEvent);
    }
  }
  1. 如果你确定使用本插件,请使用 patch-package 给 three-platformize 打补丁(主要是解决重新下载 node_modules 会自动添加上次修改的内容)
复制代码    # 1. 安装patch-package
    npm install patch-package -D
    # 2. 在package.json中增加脚本(每次npm i的时候会自动执行postinstall): "postinstall": "patch-package"
    # 3. 创建补丁,会在文件目录中生成一个patches文件夹
    npx patch-package three-platformize
  1. 如果你走到了这里说明那你成功了一大半了,我还要告诉你一些其他的问题 (1) 开发者工具抖音基础库 3.x.x.x 不会显示模型,具体需要你自己测试 (2) 开发者工具抖音基础库 2.97.0.2 以上控制器没有效果,但是真机调试没有问题 (3) 在组件内获取 canvas 开发者工具没有问题,但是真机调试会报错,如果你上传体验版没有问题可以忽略,否则请参考示例项目在组件外获取 canvas

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议
mr.***@foxmail.com

2025-04-09

现在都不支持支付宝吗

119***@qq.com 2025-04-10

你好,目前是不支持支付宝的,你看文档自己尝试实现。参考three-platformize构建一个支付宝的platfrom。

https://www.npmjs.com/package/three-platformize 搜索”自定义平台“

180***@qq.com

2025-04-01

麻烦问一下3ds格式的模型如何预览呢?

119***@qq.com 2025-04-01

3ds是3dmax的文件格式把不能直接加载的,需要导出一下其他模型格式,glb、fbx、obj等等,目前插件只能预览glb,其他格式的需要小改一下代码,讨论群里面有人搞过

180***@qq.com 2025-04-01

可以问一下在小程序端,我直接使用的three-platformize,不知道为什么放大缩小模型,有时候会突然没反应,旋转也不行了,要不就是变成一个手指也可以操作放大缩小了,调用的dispatchTouchEvent

119***@qq.com 2025-04-01

微信小程序真机调试的吗,你要自己排查一下了,可以尝试在手指缩放时console一下看看是不是报错了

740***@qq.com

2025-03-29

我在renderjs 引入 网络模型没问题 但是用的loader.load('./static/2014__bmw_m4_f82_razor.glb', function (){引入本地模型在app 中报错Fetch API cannot load file:///storage/emulated/0/Android/data/io.dcloud.HBuilder/apps/HBuilder/www/static/2014__bmw_m4_f82_razor.glb. URL scheme "file" is not supported. at app-renderjs.js:15398

119***@qq.com 2025-03-29

本地的话,你不能使用这个相对路径,你要使用那个IP地址访问的,或者你在网上搜搜怎么把这个地址转成本地的临时链接。这样应该也可以。

119***@qq.com 2025-03-29

你是APP的话,你应该就走把本地的文件转成临时链接的方式

137***@qq.com

2025-03-17

为啥启动提示:[vite] [plugin:vite:import-analysis] Failed to resolve import "three" from "src/components/threeJs/utils/animation.js". Does the file exist? at components/threeJs/utils/animation.js:1:25 1 | import * as THREE from 'three' | ^ 2 |
3 |

119***@qq.com 2025-03-17

我刚测试了一下,代码应该没有问题。 你可以看看node_modules依赖是否自动下载了。更新HbuilderX等操作试试看

羊羊不想写代码

2025-03-12

太棒的插件了,完美运行到小程序。对于我这一点都不会threejs的,太多不会的了,作者还很耐心的解答。

331***@qq.com

2025-01-22

怎么加载压缩过的模型 Error: THREE.GLTFLoader: No DRACOLoader instance provided.

119***@qq.com 2025-01-22

文档

你把\node_modules\three\examples\jsm\libs 目录下的 draco 整个文件夹复制到项目的资源目录(类似正常 vue 开发的 public 目录,uniapp 的话 需要你看下文档)

119***@qq.com 2025-01-22
// DRACOLoader是基于wasm的目前应该只有h5和app(renderjs)可以使用
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/");
dracoLoader.preload();
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
// 在需要的地方释放解码器
//   dracoLoader && dracoLoader.dispose()
sin***@sina.com

2025-01-20

自定义动画需要用nexttick实现吗?

119***@qq.com 2025-01-20

你说的自定义动画是KeyframeTrack这个吗?

sin***@sina.com 2025-01-20

比如原来用requestanimationframe时,postionx+=0.01这种

119***@qq.com 2025-01-20

哦哦 这个和nexttick没有关系吧 你可以用gsap或者tween实现你自己的动画 或者用我上一条回复的

sin***@sina.com 2025-01-21

好,谢谢~

查看更多
豆胖

2025-01-11

怎么会这么好的作者

117***@qq.com

2024-12-03

请问一下,如何动态更换模型展示,我手动group.clear()然后添加新的模型,无法正常显示

119***@qq.com 2024-12-03

你好,没有clear这个方法的。 建议阅读一下threejs的文档:https://threejs.org/docs/index.html?q=obj#api/zh/core/Object3D。 你的问题可以使用group.remove(glb),或者glb.removeFromParent()移除模型。然后重新使用loader加载模型在添加到group中。 这里的glb就是group.children里面的子项。

119***@qq.com 2024-12-03

我更新了示例文件,你可以看一下