组件(库)的概念 包含 控件(UI组件)、插件(基物上的组件)

jQuery

位置与尺寸

$(window).scrollTop()   //鼠标滚动高度

$(window).height()        //窗口高度

$(window).width()         //窗口宽度

$(“#”).outerHeight()      //元素高度

$(“#”).outerWidth()      //元素宽度

$(“#”).offset().top        //元素相对于文档的位置

$("#").offset().left        //元素相对于文档的位置

$(“#”).position().top        //元素相对于父元素的位置

$("#").position().left        //元素相对于父元素的位置

$("").css("left")           //元素相对于参考元素的位置

$("").css("top")           //元素相对于参考元素的位置

事件
$(document).ready();
$(window).resize();
$(window).scroll();   // window 上的事件不好追踪,可以把滚动事件绑在DOM上

jQuery源码

jQuery源码的基本结构:

定义了jQuery函数,并使它成为全局变量   window.jQuery = window.$ = jQuery;

jQuery函数定义:

var jQuery = function( selector, context ) {
                  return new jQuery.fn.init( selector, context );
              }

jQuery函数的原型:

jQuery.prototype = {

........................

length: 0,
  sort: [].sort,
  splice: [].splice

有了这几个属性,它就是一个类数组对象

}

jQuery初始化函数:

jQuery.fn.init = function(selector, context){

查找元素

return this;

}

jQuery函数有多个成员(JS里函数可以有成员,成员与函数体无关)其中:

jQuery.fn.init.prototype = jQuery.fn =jQuery.prototype;

jQuery.fn.extend=jQuery.extend;

几个使用过程的原理:

1、通过选择器获得jQuery对象 $("#id")

即调用了jQuery函数,返回jQuery.fn.init构造的对象,这个对象以jQuery.fn.init.prototype为原型,即以jQuery.fn 、jQuery.prototype为原型,因此jQuery对象也是一个类数组对象,并且有jQuery.fn的所有成员,数组的元素为DOM对象。

2、扩展jQuery(插件开发)

jQuery.fn.extend=jQuery.extend = function(object){把object的成员扩展到this}

$.fn.extend(object);方法体里的this指向jQuery.fn即jQuery.prototype,因此扩展到jQuery原型。

$.extend(object);方法体里的this指向$,因此扩展到jQuery函数。

jQuery使用

1、jQuery对象的结构:
[DOM对象]
{
length:DOM对象的长度
context:document对象(也是个DOM对象)
selector:选择器的字符串
__proto__:各种继承而来的jQuery方法
}

2、页面加载完成事件

$(function)等效于$(document).ready(function)或 $(window).on("load",function)

3、$也可以把非DOM数组打包成jQuery对象,从而可以使用jQuery对象的那些操作数组的函数。

4、jquery插件式的UI组件

UI 组件的对外接口应该包括:构建方法、事件、实例方法、属性值

(function($) {
    // 给jquery对象添加一个成员,传入参数
// 这个插件方法 作为 UI 组件的构建方,使用:$('#id').plugin(options);
    $.fn.plugin = function(options) {
        // each 对jquery对象里的DOM对象一一处理,后,返回当前jquery对象,使得能被链式调用
        return this.each(function() {
   // 可以把一些数据绑到DOM对象上
   this.pluginId = 1;
            var $this = $(this);
// 插件逻辑 
/**************************************************/
// 某种情况下触发事件,使用:$('#id').on('eventName',function(event,data){});
$this.trigger('eventName','data');
        });
    }

// 实例方法 和 属性值 也通过插件方法实现

})(jQuery);
//匿名立即执行函数,闭包

选择器

先后流行的选择器引擎有:getElementBySelector、cssQuery

jquery采用的选择器sizzle 的工作步骤: 1、切割选择符(便于后面,先通过选择符的后段查找,再以选择符前段过滤)2、查找(通过四大原生查找函数,最差的情况就是getElementByTagName(*)),过滤,去重

最新的浏览器自带原生选择器API:querySelecter、querySelecterAll

静态展示网站

考虑到SEO,不适合使用MVVM框架,但要考虑国际化问题

动态效果,用jQuery 和 CSS 实现

共用的页头页脚用 $(".header").load("header.html");  导入

可以在项目里嵌套一个MVVM框架用于开发表单和复杂交互的页面,配置 publicPath / base

绘图组件

图表(数据可视化):Chart.js 、ECharts.js、Highcharts.js、D3.js

3D(webGL):Three.js

矢量图(SVG,VML):SVG.js

地图(GIS):Leaflet.js、Mapbox、OpenLayer 地图数据:地图数据 | Highcharts 、 DataV.GeoAtlas地理小工具系列

富文本 wangEditor

GitHub - wangeditor-team/wangEditor: wangEditor —— 开源 Web 富文本编辑器

输出:html 片段

媒体文件:在 html 片段中插入 base64,或者 后端提供上传接口,html 片段中插入 上传后的url

wangEditor + Vue3

1、安装依赖

yarn add @wangeditor/editor
yarn add @wangeditor/editor-for-vue@next

2、使用

<template><div style="border: 1px solid #ccc"><Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef":defaultConfig="toolbarConfig" mode="default" ref="toolbar"/><Editor style="height: 200px;" v-model="form.content":defaultConfig="editorConfig" mode="default" @onCreated="handleCreated"/></div>
</template>
<script>import '@wangeditor/editor/dist/css/style.css' // 引入 cssimport {Editor, Toolbar} from '@wangeditor/editor-for-vue'import {DomEditor} from '@wangeditor/editor'import {onBeforeUnmount, onMounted, ref, shallowRef} from 'vue'export default {name: "demo",components: {Editor, Toolbar},data() {return {form: {content: ''},toolbarConfig: {excludeKeys: ['fullScreen']   // 排除 全屏 按钮},editorConfig: {placeholder: '请输入内容,字数2000字内',maxLength: 2000,autoFocus: false,MENU_CONF: {uploadImage: {server: '/api/upload'  // 后端提供的上传图片接口},uploadVideo: {server: '/api/upload'  // 后端提供的上传视频接口}}}};},setup() {const editorRef = shallowRef();onBeforeUnmount(() => {if (editorRef.value == null) {editorRef.value.destroy();}});return {editorRef,handleCreated: (editor) => {editorRef.value = editor;}};},methods: {submit() {// console.log(DomEditor.getToolbar(this.$refs.toolbar.editor));  // 查看toolbar全部配置}}};
</script>

PDF

HTML内容导出为PDF

1、安装依赖

npm install  html2canvas --savenpm install  jspdf --save

2、下载 与 分页

<div id="pdfDom"><div class="report_page" v-for="(page,index) in pages" :key="index"><div :ref="'page_'+index)"><table><tr></tr><tr v-for="(item,index) in page" :key="index"></tr></table></div><div class="page_num">第 {{index+1}} 页</div></div>
</div>
            .report_page {box-sizing: border-box;height: 1705px;position: relative;padding: 40px;}
    import html2Canvas from 'html2canvas'import JsPDF from 'jspdf'           downloadPdf (title, selector) {html2Canvas(document.querySelector(selector), {allowTaint: true,taintTest: false,scale: '2',dpi: '192',background: '#fff'}).then(function (canvas) {let contentWidth = canvas.width;let contentHeight = canvas.height;let pageHeight = contentWidth / 592.28 * 841.89;let leftHeight = contentHeight;let position = 0;let imgWidth = 595.28;let imgHeight = 592.28 / contentWidth * contentHeight;let pageData = canvas.toDataURL('image/jpeg', 1.0);let PDF = new JsPDF('', 'pt', 'a4');if (leftHeight < pageHeight) {PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)} else {while (leftHeight > 0) {PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);leftHeight -= pageHeight;position -= 841.89;if (leftHeight > 0) {PDF.addPage()}}}PDF.save(title + '.pdf')})},makePage(){  // 分页,将要显示的 items 加工成 二维数组 pagesif(this.pages.length === 0){  // 初始时 将所有 items 放到第一页this.pages = [[].concat(this.items)];this.$nextTick(()=>{this.makePage();});}
// 倒数第二页太长则将倒数第二页的最后一个item推到倒数第一页else if(this.pages.length-2 >=0 && this.$refs['page_'+(this.pages.length-2)].offsetHeight > 1625){   let lastItem = this.pages[this.pages.length-2].pop();this.pages[this.pages.length-1].unshift(lastItem);this.pages = [].concat(this.pages);this.$nextTick(()=>{this.makePage();});}
// 倒数第一页太长则新增一页,将最后一个item放到新增页else if(this.$refs['page_'+(this.pages.length-1)].offsetHeight > 1625){ let lastItem = this.pages[this.pages.length-1].pop();this.pages.push([lastItem]);this.pages = [].concat(this.pages);this.$nextTick(()=>{this.makePage();});}},

3、使用

// 将一部分 dom 导出为 pdf
this.downloadPdf("报告","#pdfDom");

HTML内容打印

1、生成PDF内容,参考上文

2、借用 iframe

<iframe ref="printIframe" style="display: none;" @load="iframeLoad" :src="pdfUrl"></iframe>

3、生成 iframe 用的 url

// PDF.output("datauristring") 得到的 DataURL 太长的话,iframe 不能正常加载;ObjectURL则无限制
this.report.pdfUrl = URL.createObjectURL(PDF.output('blob'));

4、使用

this.$refs.printIframe.contentWindow.print();  // 需保证 iframe 已经加载完成

另外,可以用 window.print() 来打印,但是无法控制布局 和 分页

let subWindow = window.open();
subWindow.document.body.innerHTML=window.document.getElementById("pdfDom").innerHTML;
subWindow.print();
subWindow.close();

浏览PDF

方式一、window.open(pdfUrl),浏览器默认方式

方式二、PDF.js,可以做一些定制

1、下载 PDF.js Prebuilt 包,放到项目中,作为单独的网页应用

2、打开 pdf 浏览器,传入pdfUrl,可以是 createObjectURL 产物

window.open('/pdfjs/web/viewer.html?file=' + pdfUrl);

Excel

SheetJS 导出 Excel

限制:设置单元格样式 需要交费购买SheetJS专业版

1、安装依赖 SheetJS

npm install xlsx --save

2、table 转 sheet 转 blob 转 ObjectUrl

import XLSX from 'xlsx'makeExcel() {let sheet = XLSX.utils.table_to_sheet(this.$refs.table);let workbook = {SheetNames: ['总览'],Sheets: {'总览': sheet}};let wopts = {bookType: 'xlsx',bookSST: false,type: 'binary'};let wbout = XLSX.write(workbook, wopts);let blob = new Blob([this.s2ab(wbout)], {type: "application/octet-stream"});this.xlsxUrl = URL.createObjectURL(blob);},s2ab(s) {let buf = new ArrayBuffer(s.length);let view = new Uint8Array(buf);for (let i = 0; i != s.length; ++i) {view[i] = s.charCodeAt(i) & 0xFF;}return buf;}

3、使用

<a :href="xlsxUrl" download="文件名.xlsx">文件下载</a>

ExcelJS 导出 Excel

1、安装依赖

npm install exceljs --save

2、生成

const ExcelJS = require('exceljs/dist/exceljs');makeExcel() {const workbook = new ExcelJS.Workbook();const worksheet = workbook.addWorksheet('工作簿1');worksheet.columns = [{header: '列头1', key: 'column_1', width: 50},{header: '列头2', key: 'column_2', width: 50}];worksheet.addRow(["值1", "值2"]);workbook.xlsx.writeBuffer().then((buffer)=>{let blob = new Blob([buffer], {type: "application/octet-stream"});this.xlsxUrl = URL.createObjectURL(blob);});},

3、使用

<a :href="xlsxUrl" download="报告.xlsx">下载</a>

国际化

1、main.js

import VueI18n from 'vue-i18n'
new Vue({i18n: new VueI18n({locale: localStorage.getItem('locale') || 'en-US',messages: {}}),components: { App }
});

2、App.vue

watch: {"$i18n.locale": function () {this.fetchLang();}
},
methods: {fetchLang(){// 异步加载国际化词条if(this.$i18n.locale == 'en-US'){import('element-ui/lib/locale/lang/en').then((lang)=>{locale.use(lang);});import('./lang/en').then((lang)=>{this.$i18n.setLocaleMessage('en-US', lang)// 或者 mergeLocaleMessage});}else{import('element-ui/lib/locale/lang/zh-CN').then((lang)=>{locale.use(lang);});import('./lang/zh').then((lang)=>{this.$i18n.mergeLocaleMessage('zh-CN', lang)});}},changeLang(locale) {this.$i18n.locale = locale;localStorage.setItem('locale', locale);  // 语言选择保存到本地}
},
mounted() {this.fetchLang();
}

3、词条文件

export const m = {"common": {"submit": "Submit"}
}

4、使用

$t('m.common.submit')

vue-i18n@9

<script main.js>
import { createApp } from 'vue';
import App from './App.vue';
import { createI18n } from 'vue-i18n';
import zhCN from './assets/lang/zhCN';
import en from './assets/lang/en';let locale = localStorage.getItem('locale') || 'zhCN';
const i18n = createI18n({locale,legacy: false,messages: {en,zhCN,},
});createApp(App).use(i18n).mount('#app');
</script><script>this.$t('dashboard.1');this.$i18n.locale;
</script><script>import { useI18n } from 'vue-i18n';setup() {const { t, locale } = useI18n();t('dashboard.1');}
</script><script setup>import { useI18n } from 'vue-i18n';const { t, locale } = useI18n();t('dashboard.1');
</script><template><div>{{ $t('dashboard.1') }}{{$i18n.locale}}</div>
</template>

Three.js

概念

indices 顶点
PBR 基于物理的渲染
贴图库 poliigon
资源:Threejs/examples/js

Float32Array 32位浮点数数组

几何体

BufferGeometry 几何体,每三个点组成一个三角形面
attributes.position.count 顶点数量,多个三角形面之间重合的顶点分别算
attributes.position.array 顶点坐标数组,一个顶点占三个轴坐标
attributes.position.uv    几何体展开图,用于确定贴图位置
attributes.position.normal 确定姿态

BoxGeometry 立方体 attributes.position.count 是24,估计是顶点复用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="three.min.js"></script><!--  threejs项目源码中 /examples/js 下有很多插件   --><script src="../examples/js/controls/OrbitControls.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
</head>
<body>
<script>import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';// 场景const scene = new THREE.Scene();// 物体const geometry = new THREE.BoxGeometry(100, 150, 200);  // 几何体const material = new THREE.MeshLambertMaterial({ // 材质color: 0x00ff00,transparent: true,opacity: 0.6});const mesh = new THREE.Mesh(geometry, material);   // 物体(由几何体和材质确定)mesh.position.set(0, 0, 100);   // 物体质心的位置mesh.scale.set(2, 1, 3);  // 缩放/* 参考世界空间坐标系旋转,绕着穿过质心的轴线(平行于世界坐标系的轴)旋转,设置最终旋转弧度 */// 无论书写顺序如何,都是先绕Z轴,再绕Y轴,再绕X轴// 观察者朝轴正方向观察,物体绕轴顺时针转动的弧度mesh.rotation.x = Math.PI / 4;mesh.rotation.y = Math.PI / 4;mesh.rotation.z = Math.PI / 4;/* 参考局部空间坐标系旋转,即以穿过质心的轴线(平行于世界坐标系的轴)为初始参考轴线;参考轴线随物体旋转,累加旋转 */mesh.rotateX(Math.PI / 4);mesh.rotateY(Math.PI / 4);mesh.rotateZ(Math.PI / 4);/* 参考局部空间坐标系旋转,即以穿过质心的向量为参考轴线;参考轴线随物体旋转,累加旋转 */// 适用于欧拉角位姿(yaw,pitch,roll)carMesh.rotateOnAxis(new THREE.Vector3(0, 0, 1), yaw);carMesh.rotateOnAxis(new THREE.Vector3(0, 1, 0), pitch);carMesh.rotateOnAxis(new THREE.Vector3(1, 0, 0), roll);scene.add(mesh);  // 往场景里添加物体// 光源const light = new THREE.PointLight(0xffffff, 1, 10000);  // 点光源light.position.set(300, 400, 500);  // 光源位置scene.add(light);// 坐标轴const axesHelper = new THREE.AxesHelper(500); // x红 y绿 z蓝scene.add(axesHelper);// 可视化点光源const pointLightHelper = new THREE.PointLightHelper(light, 1);scene.add(pointLightHelper);// 透视相机(fov水平视场角,fov和aspect间接确定了垂直视场角,near和far确定了相机观察的距离区间)const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.set(600, 600, 600);  // 相机位置// 拍摄目标,即朝向;或者用camera.up.set(0, 1, 0);camera.lookAt(0, 0, 0);  /* 相机姿态:1、相机鼻线、视线会与穿过target且平行于Y轴的轴线在同一个平面,且鼻线正方向,指向Y轴正方向2、如果视线指向Y轴正方向,此时鼻线垂直于Y轴,则鼻线指向Z轴正方向3、如果视线指向Y轴负方向,此时鼻线垂直于Y轴,则鼻线指向Z轴负方向*/// 渲染器,即canvas画布const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);  // canvas 尺寸,单位为像素,与场景里的尺寸无关renderer.setClearColor(0xffffff);  // 画布颜色renderer.render(scene, camera);document.body.appendChild(renderer.domElement);  // 将canvas加入到 dom// 相机控制器,改变的是相机的位置// 滚轮,改变相机位置-朝向保持不变(即相机在视线上移动,始终朝向拍摄目标)// 鼠标右键拖动,平移,改变拍摄目标// 鼠标左键拖动,改变相机位置-与拍摄目标距离保持不变(即相机在球面上移动,始终朝向拍摄目标)// 左键左右拖动,场景水平旋转,即绕Y轴旋转// 右键上下拖动,场景垂直旋转,即绕穿过target且平行于相机双眼线的轴旋转const controls = new THREE.OrbitControls(camera, renderer.domElement);controls.target.set(0, 0, 0); // 拍摄目标,即朝向controls.update();  // 会覆盖 camera.lookAtcontrols.addEventListener("change", () => {renderer.render(scene, camera);   // 移动相机后,重新渲染画布});// 动画const clock = new THREE.Clock();function animate() {// console.log(clock.getDelta());  // 间隔时间,用于获取渲染耗时renderer.render(scene, camera);mesh.rotateY(0.01);window.requestAnimationFrame(animate)}animate();// GSAP 动画库let animate1 = gsap.to(mesh.position, {x: 300,duration: 5,ease: "bounce.inOut", // 速度曲线delay: 2,repeat: 2,yoyo: true,   // 往返onStart: ()=>{console.log('动画开始');},onComplete: () => {console.log('动画结束');}});window.addEventListener('click', (event) => {if(animate1.isActive){animate1.pause();  // 暂停动画}else{animate1.resume();  // 恢复动画}});// 画布点投射,即画布上的一点沿着视锥线画一条射线;用于寻找与射线交汇的物体,即鼠标拾取,进而实现交互window.addEventListener('click', (event) => {const pointer = new THREE.Vector2();      // 画布点pointer.x = (event.clientX / window.innerWidth) * 2 - 1;pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;const raycaster = new THREE.Raycaster();  // 射线raycaster.setFromCamera(pointer, camera);const intersects = raycaster.intersectObjects(scene.children);  // 找出与射线交汇的物体for (let i = 0; i < intersects.length; i++) {console.log(intersects[i]);}});/* webgl坐标转画布坐标,画布外的webgl坐标仍然有效 */function webgl2screen(webglVector) {const centerX = window.innerWidth / 2;const centerY = window.innerHeight / 2;const standardVector = webglVector.project(camera);const screenX = Math.round(centerX * standardVector.x + centerX);const screenY = Math.round(-centerY * standardVector.y + centerY);return new THREE.Vector2(screenX, screenY);}/* 加载3D模型 */let loader = new GLTFLoader();loader.load(`https://**.glb`, (gltf) => {// 如果比较暗淡,需要 自发光处理gltf.scene.traverse(function (child) {if (child.isMesh) {child.material.emissive = child.material.color;child.material.emissiveMap = child.material.map;}});// 如果C4D的设计单位是mm,导出比率是1米,即设计稿里的1米为glb里的1单位长度;例如设计长度为4000mm,glb里为4单位长度scene.add(gltf.scene);});// 释放资源function clear(){// 递归遍历所有后代scene.traverse(function(obj) {if (obj.type === 'Mesh') {obj.geometry.dispose();obj.material.dispose();}});scene.remove(...scene.children);}/* 计算 */let box = new THREE.Box3().setFromObject(mesh);console.log(box.max.x - box.min.x);  // 物体的坐标范围// 视觉尺寸保持,传入需要保持的视角大小function getSizeByDeg(deg) {// 等腰三角形的底边垂线h,底边l,底边对角rad,tan(rad/2)*h=l/2let rad = THREE.MathUtils.degToRad(deg);  // 角度转弧度let h = camera.position.z;let l = Math.tan(rad / 2) * h * 2;return l;}// 俯视一个物体及其周边,横向前后100fitViewToMesh(mesh) {// 求出纵向let y = 100 * (renderer.domElement.clientHeight / renderer.domElement.clientWidth);// fov是视场纵向角度let z = y / Math.tan(THREE.MathUtils.degToRad(camera.fov / 2));camera.position.set(mesh.position.x, mesh.position.y, z + mesh.position.z);camera.lookAt(mesh.position.x, mesh.position.y, mesh.position.z);},
</script>
</body>
</html>

向量

let vector1 = new THREE.Vector3(1, 0, 0);
let vector2 = new THREE.Vector3(0, 1, 0);
vector1.angleTo(vector2);  // 向量之间的夹角
vector1.distanceTo(vector2); // 两个点之间的距离// 向量绕着指定穿过世界坐标原点的轴旋转
vector.applyAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI/2);// 向量1 转到与 向量2 平行,所需位姿调整(yaw,pitch)function getYawAndPitch(vector1, vector2) {// vector2在 XY 平面上的投影let projectionVector = new THREE.Vector3(vector2.x, vector2.y, 0);// vector2 投影 与 vector1 的夹角let yaw = vector1.angleTo(projectionVector);// yaw 是 观察者朝z轴正方向观察,物体绕z轴顺时针转动的弧度yaw = vector2.y > 0 ? yaw : 2 * Math.PI - yaw;// vector2 的XY平面投影,与自身的夹角let pitch = projectionVector.angleTo(vector2);// pitch 是 观察者朝y轴正方向观察,物体绕y轴顺时针转动的弧度pitch = vector2.z < 0 ? pitch : 2 * Math.PI - pitch;return {yaw,pitch,};}

let group = new THREE.Group();
// 世界坐标转局部坐标
let localPoint = group.worldToLocal(new THREE.Vector3(x, y, z));
// 局部坐标转世界坐标
let worldPosition = group.localToWorld(new THREE.Vector3(x, y, z));

正交相机

    /* 保持正交视场长宽比与画布一致,物体才不会变形 */let width = 200;let height = width * (canvas_wrap.clientHeight / canvas_wrap.clientWidth);// 以camera.position为原点,垂直于camra.up,画一个矩形;参数值都是相对于原点camera = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 0.01, 10000);// 滚轮控制的是 camera.zoomcontrols = new OrbitControls(camera, renderer.domElement);// 视觉尺寸保持,传入需要保持的画布占比getSizeByPercent(percent) {let width = (camera.right - camera.left) / camera.zoom;return width * percent;},// 俯视一个物体及其周边,横向前后100fitViewToMesh(mesh) {camera.zoom = 1;// 横向前后100米let width = 200;camera.left = -width / 2;camera.right = width / 2;// 正交视场长宽比与画布保持一致,物体才不会变形let height = width * (canvas_wrap.clientHeight / canvas_wrap.clientWidth);camera.top = height / 2;camera.bottom = -height / 2;camera.position.set(mesh.position.x, mesh.position.y, mesh.position.z + 100);camera.updateProjectionMatrix();controls.target.set(mesh.position.x, mesh.position.y, mesh.position.z);controls.update();},

截图

camera.updateProjectionMatrix();
renderer.clear();
renderer.render(scene, camera);
const dataURL = renderer.domElement.toDataURL('image/png');

方案

一、给 圆CircleGeometry包边

1、边缘几何体EdgesGeometry,设置线宽无效

2、椭圆曲线EllipseCurve,设置线宽无效

3、圆环几何体RingGeometry、TorusGeometry

二、给矩形描边,解决 THREE.Line 设置 linewidth 无效的问题

import { Line2 } from 'three/examples/jsm/lines/Line2';
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry';
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial';const planeMeshBox = new THREE.Box3().setFromObject(planeMesh);const pointArr = [areaMeshBox.min.x,areaMeshBox.max.y,0,areaMeshBox.max.x,areaMeshBox.max.y,0,areaMeshBox.max.x,areaMeshBox.min.y,0,areaMeshBox.min.x,areaMeshBox.min.y,0,areaMeshBox.min.x,areaMeshBox.max.y,0,];const geometry = new LineGeometry();geometry.setPositions(pointArr);  // 不能用edge_geom.setFromPoints()let material = new LineMaterial({ linewidth: 5 });material.resolution.set(window.innerWidth, window.innerHeight);  // 这一句必须有const lineMesh = new Line2(geometry, material);

裁剪

// 用于裁剪的平面
// 第一个参数必须是单位向量,即.normalize()过
// 第二个参数为平面到原点的距离,正负以法向量方向为基准
const plane = new THREE.Plane(new THREE.Vector3(1,0,0), -Math.sqrt(2));
const geometry = new THREE.CylinderGeometry( 2, 2, Math.sqrt(8), 64);
// clippingPlanes 缺点:裁剪平面为世界坐标系里的平面,物体移动旋转时,裁剪平面也要相应处理,否则裁剪部分变化
const material = new THREE.MeshBasicMaterial( {color: 0x00ffff, clippingPlanes: [plane] } );
const cylinder = new THREE.Mesh( geometry, material );

案例

1、视锥体,由圆柱侧面+2个圆锥内表面+2个三角形 组成

可进行视场角、视场宽高比、位姿 调整

        // 摄像机视锥体,已知 水平fov,图像宽高比let cameraVisionCone = (horizontal_fov_deg, image_size_x, image_size_y) => {let horizontal_fov = THREE.MathUtils.degToRad(horizontal_fov_deg);let fovSign = horizontal_fov > Math.PI ? Math.PI * 2 - horizontal_fov : horizontal_fov;// 视锥横向三角的底边let width = horizontal_fov > Math.PI ? radius * 2 : radius * Math.sin(fovSign / 2) * 2;// 视锥体前弧面的高度let height = (width * image_size_y) / image_size_x;return getVisionCone(horizontal_fov_deg, height / 2, height / 2);};// 毫米波雷达视锥体,已知 水平fov,垂直fovlet getRadarSensor = (horizontal_fov_deg, vertical_fov_deg) => {let vertical_fov_half = THREE.MathUtils.degToRad(vertical_fov_deg / 2);// 视锥体前弧面的高度let height_half = Math.tan(vertical_fov_half) * radius;return getVisionCone(horizontal_fov_deg, height_half, height_half);};// 激光雷达视锥体,已知 水平fov,上下垂直fovlet lidarVisionCone = (horizontal_fov_deg, upper_fov_deg, lower_fov_deg) => {let upper_fov = THREE.MathUtils.degToRad(upper_fov_deg);let lower_fov = THREE.MathUtils.degToRad(lower_fov_deg);// 视锥体前弧面的高度let height_upper = Math.tan(upper_fov) * radius;let height_lower = Math.tan(lower_fov) * radius;return getVisionCone(horizontal_fov_deg, height_upper, height_lower);};let getVisionCone = (horizontal_fov_deg, height_upper, height_lower) => {let radius = 5;let horizontal_fov = THREE.MathUtils.degToRad(horizontal_fov_deg);let fovSign = horizontal_fov > Math.PI ? Math.PI * 2 - horizontal_fov : horizontal_fov;const group = new THREE.Group();let material = new THREE.MeshBasicMaterial({ color, side: THREE.DoubleSide, transparent: true, opacity });let edgeMaterial = new THREE.LineBasicMaterial({ color, side: THREE.DoubleSide, transparent: true, opacity: opacity + 0.2 });// 视锥体前弧面let geometry = new THREE.CylinderGeometry(radius, radius, height_upper + height_lower, 64, 1, true, (Math.PI - horizontal_fov) / 2, horizontal_fov);const cylinder = new THREE.Mesh(geometry, material);cylinder.position.y = (height_upper - height_lower) / 2;group.add(cylinder);// 补上圆锥内表面geometry = new THREE.ConeGeometry(radius, height_upper, 64, 1, true, (Math.PI - horizontal_fov) / 2, horizontal_fov);let cone = new THREE.Mesh(geometry, material);cone.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI);cone.position.y = height_upper / 2;group.add(cone);// 补下圆锥内表面geometry = new THREE.ConeGeometry(radius, height_lower, 64, 1, true, (Math.PI - horizontal_fov) / 2, horizontal_fov);cone = new THREE.Mesh(geometry, material);cone.position.y = -height_lower / 2;group.add(cone);// 描边let curve = new THREE.EllipseCurve(0, 0, radius, radius, (Math.PI - horizontal_fov) / 2, (Math.PI + horizontal_fov) / 2);geometry = new THREE.BufferGeometry().setFromPoints(curve.getPoints(64));let ellipse = new THREE.Line(geometry, edgeMaterial);ellipse.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI / 2);ellipse.rotateOnAxis(new THREE.Vector3(0, 0, 1), -Math.PI / 2);ellipse.position.y = height_upper;group.add(ellipse);ellipse = ellipse.clone();ellipse.position.y = -height_lower;group.add(ellipse);// 补侧面三角形
if(horizontal_fov_deg < 360){let xSign = horizontal_fov > Math.PI ? -1 : 1;geometry = new THREE.BufferGeometry();const vertices = new Float32Array([0,0,0,Math.cos(fovSign / 2) * radius * xSign,height_upper,Math.sin(fovSign / 2) * radius,Math.cos(fovSign / 2) * radius * xSign,-height_lower,Math.sin(fovSign / 2) * radius,0,0,0,Math.cos(fovSign / 2) * radius * xSign,height_upper,-Math.sin(fovSign / 2) * radius,Math.cos(fovSign / 2) * radius * xSign,-height_lower,-Math.sin(fovSign / 2) * radius,]);geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));let mesh = new THREE.Mesh(geometry, material);group.add(mesh);// 描边let edge = new THREE.EdgesGeometry(geometry);let line = new THREE.LineSegments(edge, edgeMaterial);group.add(line);
}group.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI / 2);const result = new THREE.Group();result.add(group);return result;};

JSZip

用于前端生成zip文件 和 解压zip文件

npm install jszip --save

import jszip from 'jszip';jszip.loadAsync(file).then((zip) => {// zip.files 里既有文件也有目录,而且是平铺而不是嵌套,name里包含路径Object.values(zip.files).forEach((item) => {// 非目录if (!item.dir) {let segments = item.name.split('/');item.pureName = segments[segments.length - 1];item.path = segments.slice(0, segments.length - 1).join('/');item.path = item.path ? item.path + '/' : '';// 将文件内容读取为 blobitem.async('blob').then((blob) => {// blob 转 file,MIME typeitem.raw = new File([blob], item.pureName, { type: 'application/xml'});});}});
});

JSEncrypt

import JSEncrypt from 'jsencrypt';// RSA 公钥
let publicKey = `-----BEGIN PUBLIC KEY-----***-----END PUBLIC KEY-----`;
let jse = new JSEncrypt();
jse.setPublicKey(publicKey);
jse.encrypt(source);  // 加密

Axios

// 提交文件
let formData = new FormData();
formData.append('field', field);
formData.append('file', file);
axios.post('/url', formData, {'Content-type': 'multipart/form-data',
});// 下载文件
axios({method: 'get',url: `/url`,responseType: 'blob',
}).then((res) => {let blob = res.data;let objectUrl = URL.createObjectURL(res.data);let file = new File([res.data], 'filename', {type: 'application/xml', lastModified: Date.now()});
});

其他组件

时间:moment.js

代码编辑器:CodeMirror

组件,库,控件,插件 集合(jQuery/绘图/富文本/PDF/Excel/国际化/Three.js/JSZip/JSEncrypt/Axios/其他)相关推荐

  1. 安卓文本编辑器php cpp,用安卓原生控件封装一个简易的富文本编辑器

    最近接到项目需求:移动端原生写一个富文本编辑器.        ( ⊙ o ⊙ )从没遇到过富文本要用原生写的,然后就查阅各种资料.然后结合自己的思路:其实安卓的富文本编辑器就是一个 "容器 ...

  2. web元件库、axure元件库、元件库、web组件、控件、表单、框架、数据表单、导航栏、边框、图标、列表、日期时间选择器、评分组件、穿梭框、输入框、步骤条、图表组件、数据可视化、后台模板、时间轴

    web元件库.axure元件库.通用元件库.web组件.控件.表单.框架.数据表单.导航栏.边框.图标.列表.日期时间选择器.评分组件.穿梭框.输入框.步骤条.图表组件.数据可视化.后台模板.时间轴. ...

  3. 深入浅出的理解透析小程序插件、组件和控件的区别

    由于在小程序开发中需要引入插件,但同事问起的时候还是被几个概念难住了,虽然能够了解大致的含义,但是对于每一个概念的理解又不是很深入,所以深入学习了解了几个"概念模糊"的知识点,可能 ...

  4. xamarin.android 控件,Android 库控件 - Xamarin | Microsoft Docs

    Xamarin Android 库控件Xamarin.Android Gallery control 03/15/2018 本文内容 Gallery是一种布局小组件,用于显示水平滚动列表中的项,并将当 ...

  5. 组件,控件,用户控件

    在学习这部分时产生了一些疑问,下面是根据我搜罗来的资料得出的一点浅薄的见解. 其实从字面上已经可以理解各自的不同了.但是具体关系其实是从类的继承上来区别的.一般控件派生于:Control类,所以从此类 ...

  6. 我理解的组件和控件的区别

    组件: 组成的件 .    ------- ---汽车上的螺丝嘎达. 控件: 控制的件. -----------方向盘. 再转载下别人的贴: 第一位牛人:控件就是具有用户界面的组件.要说的具体一点,就 ...

  7. C#中组件与控件的主要区别是什么?

    C#中组件与控件的主要区别是什么? 答:组件是指可重复使用并且可以和其他对象进行交互的对象.组件(component)是靠类实现的.控件(Control)是能够提供用户界面接口(UI)功能的组件.换句 ...

  8. 关于银行等带有安全控件插件的输入selenium无法sendkeys的解决方案

    关于银行等带有安全控件插件的输入selenium无法sendkeys的解决方案 参考文章: (1)关于银行等带有安全控件插件的输入selenium无法sendkeys的解决方案 (2)https:// ...

  9. html标签手册 360doc,基于AJAX的文件上传控件NetAdvantage for jQuery

    NetAdvantage for jQuery 是一款全新的轻量级.高性能的jQuery控件,包含了在线的Video播放控件,基于AJAX的文件上传控件,快速且强大的表格控件,以及创建和编辑Word. ...

最新文章

  1. matlab图像处理命令(一)
  2. linux复制压缩文件,Linux如何复制,打包,压缩文件
  3. mongoDB的监控工具
  4. 如何做到让屏幕中的人不翼而飞?这个JavaScript项目告诉你该怎么做!
  5. Ubuntu 12.04 MySQL改utf-8 启动不了
  6. 【正一专栏】警察叔叔,我还是只是一个婴儿
  7. SAP CRM产品主数据里的七种ID
  8. 图像处理基本算法-形态学
  9. 需要清除memcach缓存方能解决的几个报错
  10. MySQL 5.7 自带的四个数据库 介绍
  11. usbserialch340驱动安装失败_【已解决】Mac中安装USB转串口的CH340G驱动
  12. H5-表格的基本样式
  13. 数据库基础知识(索引)
  14. 安全模式下如何重启计算机,Windows10进入安全模式的6种方法?安全模式下如何维护电脑?...
  15. leetcode-954. 二倍数对数组
  16. win10开机桌面图像获取
  17. 使用FormData格式上传图像并预览图片
  18. 企业微信公众号运营技巧有哪些
  19. 基于Opencv和STM32物理鼠标的目标跟踪器
  20. 异常解决:java.lang.IllegalStateException: Failed to introspect Class

热门文章

  1. 常见面试问题-自我介绍、离职原因、期望薪资…(含面试答案)
  2. 中国银行业协会消费金融专业委员会成立
  3. RTMP视频流——推拉流及视频保存
  4. 婚姻关系中的“重大过错”行为
  5. Android学习记录(一)
  6. 【2022/08/11】文件的上传(FileUpload)
  7. 计算机导论/计算机基础实验/网站建设技术:网页基本操作
  8. 客户机操作系统和服务器,对等网和客户机服务器网络有什么区别,什么叫做对等网络?...
  9. 23-Mybatis集成第三方缓存框架EHCache
  10. GitHub上有个高仿微信的开源项目,超厉害的!