分享HTML显示2D/3D粒子时钟
效果截图
实现代码
线上体验:three.js+cannon.js Web 3D
<!DOCTYPE html>
<head>
<title>three.js+cannon.js Web 3D</title><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"><meta name="renderer" content="webkit"><meta name="force-rendering" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=10,chrome=1"><meta data-rh="true" name="keywords" content="three.js,JavaScript,cannon.js"><meta data-rh="true" name="description" content="three.js+cannon.js Web 3D"><meta data-rh="true" property="og:title" content="THREE.JS and CANNON.JS"><meta data-rh="true" property="og:url" content=""><meta data-rh="true" property="og:description" content="three.js+cannon.js Web 3D"><meta data-rh="true" property="og:image" content=""><meta data-rh="true" property="og:type" content="article"><meta data-rh="true" property="og:site_name" content=""><link rel="icon" href=""><style>.fullscreen {margin: 0px;padding: 0px;width: 100vw;height: 100vh;overflow: hidden;}html, body {overflow: hidden;font-family: '宋体', sans-serif;color: #2f2f2f;}#threeImage{display: none;}</style><script type="importmap">{"imports": {"three": "https://cdn.jsdelivr.net/npm/three@0.162.0/+esm","three/addons/": "https://cdn.jsdelivr.net/npm/three@0.162.0/examples/jsm/","lil-gui": "https://threejsfundamentals.org/3rdparty/dat.gui.module.js","@tweenjs/tween.js": "https://cdn.jsdelivr.net/npm/@tweenjs/tween.js@23.1.1/dist/tween.esm.js","cannonjs": "https://cdn.bootcdn.net/ajax/libs/cannon.js/0.6.2/cannon.min.js"}}</script></head><body class="fullscreen"><div id="threeImage" ><img id="testImage" src="" crossorigin="anonymous"/></div><canvas></canvas><script type="module">import * as THREE from 'three';import * as TWEEN from '@tweenjs/tween.js';import { OrbitControls } from 'three/addons/controls/OrbitControls.js';import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';import { GUI } from 'lil-gui';import 'cannonjs';let container;let scene, camera, renderer, controls;let NEAR = 0.1, FAR = 1000;let light, ambient, stats, info, mouseX, mouseY;let SHADOW_MAP_WIDTH = 512;let SHADOW_MAP_HEIGHT = 512;let MARGIN = 0;let SCREEN_WIDTH = window.innerWidth;let SCREEN_HEIGHT = window.innerHeight - 2 * MARGIN;//物理世界let world;// 创建默认材质let defaultMaterial = null//创建材质let bMaterial = nulllet cube, tempCubes = new THREE.Object3D();let color = "#12345678";function setup() {setupScene();setupCamera();setupRenderer();setupLights();setPlane();setControls();initCannon();setupEventListeners();animate();addGUI();}function setupScene() {scene = new THREE.Scene();scene.fog = new THREE.Fog( 0x222222, 1000, FAR );scene.background = new THREE.Color(0xf5e6d3);}function setupCamera() {let res = SCREEN_WIDTH / SCREEN_HEIGHT;camera = new THREE.PerspectiveCamera(45, res, NEAR, FAR);//camera.up.set(0,0,1);//camera.position.set(0,30,20);//camera.position.z = 19;//camera.position.y = -45;camera.position.set(0, 30, 30)//camera.lookAt(0, 0, 0);//camera.lookAt(new THREE.Vector3(0, 0, 0));}let rotWorldMatrix;function rotateAroundWorldAxis(object, axis, radians) {//object:需要旋转的物体,axis:旋转中心轴向量,radians:rotWorldMatrix = new THREE.Matrix4();rotWorldMatrix.makeRotationAxis(axis.normalize(), radians);rotWorldMatrix.multiply(object.matrix); // pre-multiplyobject.matrix = rotWorldMatrix;object.rotation.setFromRotationMatrix(object.matrix);}function setupRenderer() {renderer = new THREE.WebGLRenderer({ clearColor: 0x000000, clearAlpha: 1, antialias: true,// 抗锯齿logarithmicDepthBuffer: true // 使用Three进行加载模型时,总会遇到模型相接处或某些区域出现频闪问题或内容被相邻近元素覆盖掉的情况,对数缓存开启可解决,使用对数缓存});renderer.setSize(window.innerWidth, window.innerHeight);//renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );renderer.setClearColor( scene.fog.color, 1 );renderer.autoClear = false;renderer.shadowMapEnabled = true;renderer.shadowMapSoft = true;//开启阴影效果 设置阴影类型// BasicShadowMap 能够给出没有经过过滤的阴影映射 —— 速度最快,但质量最差。// PCFShadowMap 为默认值,使用Percentage-Closer Filtering (PCF)算法来过滤阴影映射。// PCFSoftShadowMap 和PCFShadowMap一样使用 Percentage-Closer Filtering (PCF)算法过滤阴影映射,但在使用低分辨率阴影图时具有更好的软阴影。// VSMShadowMap 使用Variance Shadow Map (VSM)算法来过滤阴影映射。当使用VSMShadowMap时,所有阴影接收者也将会投射阴影renderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.BasicShadowMaprenderer.shadowMap.autoUpdate = falserenderer.shadowMap.needsUpdate = true// 是否使用传统照明模式,默认为是,关闭传统照明模式即可模仿物理光照,光亮随距离可递减renderer.useLegacyLights = false;// 设置色调映射renderer.toneMapping = THREE.ACESFilmicToneMapping;// 曝光强度renderer.toneMappingExposure = 0.8renderer.outputEncoding = THREE.sRGBEncoding;container = document.createElement( 'div' );document.body.appendChild( container );//document.body.appendChild(renderer.domElement);//renderer.domElement.style.position = "relative";//renderer.domElement.style.top = MARGIN + 'px';container.appendChild( renderer.domElement );}function setPlane(){let planeGeometry = new THREE.PlaneGeometry(60, 20);let planeMaterial = new THREE.MeshBasicMaterial({color: 0xffffff});let plane = new THREE.Mesh(planeGeometry, planeMaterial);// 几何体绕着x轴旋转-90度plane.rotateX(-Math.PI/2);// 设置平面网格为接受阴影的投影面plane.receiveShadow = true;scene.add(plane);}function setupLights() {let ambientLight = new THREE.AmbientLight(0xffffff);ambientLight.castShadow = true;scene.add(ambientLight);// LIGHTSambient = new THREE.AmbientLight( 0xffffff );ambient.castShadow = true;scene.add( ambient );// 添加聚光灯1addSpotlight(50,50,50);// 添加聚光灯2addSpotlight(-50,50,50);// 添加聚光灯3addSpotlight(50,50,-50);// 添加聚光灯4addSpotlight(-50,50,-50);addLight();}function addLight(){light = new THREE.SpotLight( 0xffffff );light.position.set( 30, 30, 30 );light.target.position.set( 0, 0, 0 );light.castShadow = true;light.shadowCameraNear = 10;light.shadowCameraFar = 100;//camera.far;light.shadowCameraFov = 30;light.shadowMapBias = 0.0039;light.shadowMapDarkness = 0.5;light.shadowMapWidth = SHADOW_MAP_WIDTH;light.shadowMapHeight = SHADOW_MAP_HEIGHT;light.shadowCameraVisible = true;scene.add( light );}function setupEventListeners() {window.addEventListener("resize", onWindowResize);}function onDocumentMouseMove( event ) {mouseX = ( event.clientX - windowHalfX );mouseY = ( event.clientY - windowHalfY );}function onWindowResize( event ) {SCREEN_WIDTH = window.innerWidth;SCREEN_HEIGHT = window.innerHeight;renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );//camera.aspect = window.innerWidth / window.innerHeight;//renderer.setSize(window.innerWidth, window.innerHeight);camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;camera.updateProjectionMatrix();//controls.screen.width = SCREEN_WIDTH;//controls.screen.height = SCREEN_HEIGHT;camera.radius = ( SCREEN_WIDTH + SCREEN_HEIGHT ) / 4;}//let clock = new THREE.Clock()function render () {renderer.clear();renderer.render(scene, camera);controls.update();//let delta = clock.getDelta()//world.step(delta)// 更新物理世界 step ( dt , [timeSinceLastCalled] , [maxSubSteps=10] )// dt:固定时间戳(要使用的固定时间步长)// [timeSinceLastCalled]:自上次调用函数以来经过的时间// [maxSubSteps=10]:每个函数调用可执行的最大固定步骤数// * 设置更新物理世界world的步长timestep// * 这里选用60Hz的速度,即1.0 / 60.0world.step(1.0 / 60.0)//world.fixedStep()}function animate() {render();requestAnimationFrame(animate);}function addTexture(url){// 加载纹理const textureLoader = new THREE.TextureLoader()const texture = textureLoader.load(url)//textureLoader.load(url, (texture) => {// texture.mapping = THREE.EquirectangularReflectionMapping// scene.background = texture// scene.environment = texture// // 背景模糊强度// scene.backgroundBlurriness = 0.01//})return texture}function setControls(){controls = new OrbitControls( camera, renderer.domElement );controls.enableDamping = true;controls.rotateSpeed = 1.0;controls.zoomSpeed = 1.2;controls.panSpeed = 0.2;controls.noZoom = false;controls.noPan = false;controls.staticMoving = false;controls.dynamicDampingFactor = 0.3;const radius = 10;controls.minDistance = 0.0;controls.maxDistance = radius * 10;controls.enablePan = truecontrols.dampingFactor = 0.25controls.screenSpacePanning = falsecontrols.enableZoom = truecontrols.zoomScale = 10controls.minZoom = 1controls.maxZoom = 100//controls.minPolarAngle = 1 * -Math.PI / 180//controls.maxPolarAngle = 90 * Math.PI / 180//controls.minAzimuthAngle = 90 * -Math.PI / 180//controls.maxAzimuthAngle = 90 * Math.PI / 180//controls.keys = [ 65, 83, 68 ]; // [ rotateKey, zoomKey, panKey ]//controls.screen.width = SCREEN_WIDTH;//controls.screen.height = SCREEN_HEIGHT;/*// Trackball controlscontrols = new THREE.TrackballControls( camera, renderer.domElement );controls.rotateSpeed = 1.0;controls.zoomSpeed = 1.2;controls.panSpeed = 0.2;controls.noZoom = false;controls.noPan = false;controls.staticMoving = false;controls.dynamicDampingFactor = 0.3;var radius = 100;controls.minDistance = 0.0;controls.maxDistance = radius * 1000;//controls.keys = [ 65, 83, 68 ]; // [ rotateKey, zoomKey, panKey ]controls.screen.width = SCREEN_WIDTH;controls.screen.height = SCREEN_HEIGHT;*/}function addSpotlight (x,y,z){const spotLight2 = new THREE.SpotLight(0xffffff, 1)spotLight2.position.set(x, y, z)spotLight2.target.position.set( 0, 0, 0 )spotLight2.castShadow = truespotLight2.shadow.camera.near = 0.1spotLight2.shadow.camera.far = 30spotLight2.shadow.camera.fov = 30spotLight2.shadow.mapSize.width = 256spotLight2.shadow.mapSize.height = 256// 设置灯光 bias ,解决自阴影问题spotLight2.shadow.bias = -0.0008spotLight2.power = 1scene.add(spotLight2)// 使用辅助器对灯光和阴影进行调整//const cameraHelper = new THREE.SpotLightHelper(spotLight2)//scene.add(cameraHelper)}function initCannon() {// 初始化物理世界world = new CANNON.World()// 设置物理世界重力加速度 单位:m/s² 重力加速度x、y、z分量值,假设y轴竖直向上,这样重力就速度就是y方向负方向。world.gravity.set(0, -9.82, 0)// npm install cannon-es-debugger// 加入 cannon-es-debugger 可以展示模型的物理世界的轮廓// scene: 场景// 物理世界// 第三个参数为可选参数,其中的的onInit方法返回场景中的每个刚体和对应的物理世界的轮廓的three mesh// const cannonDebugger = CannonDebugger(scene, world)// const cannonDebugger = CannonDebugger(scene, world, {// onInit(body: CANNON.Body, mesh: THREE.Mesh) {// // // mesh.visible = true// console.log(body);// },// })// 还要在每帧更新调用中更新 Update the CannonDebugger meshes// cannonDebugger.update() // 创建默认材质defaultMaterial = new CANNON.Material('default')//创建足球材质bMaterial = new CANNON.Material('bMaterial')// 定义两种材质之间的摩擦因数和弹力系数 设置地面材质和小球材质之间的碰撞反弹恢复系数const defaultContactMaterial = new CANNON.ContactMaterial(defaultMaterial, bMaterial, {friction: 5,restitution: 0.5, //反弹恢复系数})// 把关联的材质添加到物理世界中world.addContactMaterial(defaultContactMaterial)// NaiveBroadphase Cannon 默认的算法。检测物体碰撞时,一个基础的方式是检测每个物体是否与其他每个物体发生了碰撞// GridBroadphase 网格检测。轴对齐的均匀网格 Broadphase。将空间划分为网格,网格内进行检测。// SAPBroadphase(Sweep-and-Prune) 扫描-剪枝算法,性能最好。// 默认为 NaiveBroadphase,建议替换为 SAPBroadphase// 碰撞算法world.broadphase = new CANNON.SAPBroadphase(world)//world.broadphase = new CANNON.NaiveBroadphase();// 创建一个物理世界的平面const planeShape = new CANNON.Plane()// 创建一个刚体const planeBody = new CANNON.Body({mass: 0, // 设置质量为0,不受碰撞的影响shape: planeShape,position: new CANNON.Vec3(0, 1, 0)})// 改变平面默认的方向,法线默认沿着z轴,旋转到平面向上朝着y方向// 设置刚体旋转(设置旋转X轴)旋转规律类似threejs 平面planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)// 将刚体添加到物理世界当中world.addBody(planeBody)const textureLoader = new THREE.TextureLoader();let texture = textureLoader.load('https://ts2.cn.mm.bing.net/th?id=OJ.nhM9wE4TnIlRRg');texture.wrapS = THREE.RepeatWrapping;texture.wrapT = THREE.RepeatWrapping;texture.repeat.set(1,1);texture.needsPMREMUpdate = truetexture.minFilter = THREE.LinearFiltertexture.magFilter = THREE.LinearFilterscene.environment = texture;let cubeGeometry = new THREE.BoxGeometry(1,1,1);// http://www.webgl3d.cn/pages/21003f/ PBR材质金属度、粗糙度以及环境贴图const cMaterial = new THREE.MeshPhysicalMaterial({ map: texture,side: THREE.DoubleSide,normalMap: texture,normalScale: new THREE.Vector2(1.1,1.1),specularMap: texture,specular: 0xffffff,shininess: 10,envMap: texture,color: color, metalness: 0.5,//像金属的程度, 非金属材料,如木材或石材,使用0.0,金属使用1.0。roughness: 0.5,//模型表面的光滑或者说粗糙程度,越光滑镜面反射能力越强,越粗糙,表面镜面反射能力越弱,更多地表现为漫反射。 clearcoat: 1,clearcoatRoughness: 0.01,envMapIntensity: 2.5,opacity: 0.5,transparent: true });cube = new THREE.Mesh(cubeGeometry,cMaterial);cube.position.x=0;cube.position.y=2;cube.position.z=0;cube.castShadow = true;scene.add(cube);}const calcMeshCenter = (group)=>{/*** 包围盒全自动计算:模型整体居中*/let box3 = new THREE.Box3()// 计算层级模型group的包围盒// 模型group是加载一个三维模型返回的对象,包含多个网格模型box3.expandByObject(group)// 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置let center = new THREE.Vector3()box3.getCenter(center)// console.log('查看几何体中心坐标', center);// 重新设置模型的位置,使之居中。group.position.x = group.position.x - center.xgroup.position.y = group.position.y - center.ygroup.position.z = group.position.z - center.z}const gui = new GUI({width: 280,title: 'Setting',autoPlace: true})var params = new function() {this.color = 0x123456;this.position = -35;this.visible = true;};function addGUI(){gui.addColor(params, "color").onChange(e => {cube.material.color.set(e);});gui.add(params, "position", -172, 28).onChange(e => {tempCubes.position.x = e});}const canvas = document.querySelector('canvas');const ctx = canvas.getContext('2d',{//频繁读取画布信息willReadFrequently: true});function initCanvasSize(){//canvas绘制的图形是位图,即栅格图像或点阵图像,当将它渲染到高清屏时,会被放大,每个像素点会用devicePixelRatio的平方个物理像素点来渲染,因此图片会变得模糊。//将画布尺寸设置为画板尺寸的window.devicePixelRatio倍canvas.width = window.innerWidth * window.devicePixelRatio;canvas.height = "150"//window.innerHeight * window.devicePixelRatio;}initCanvasSize();//获取[min,max]范围内的随机数function getRandom(min, max){return Math.floor(Math.random()*(max+1-min)+min);}//通过构造函数生成圆形颗粒class Particle{constructor(){//生成圆圈的半径const r = Math.min(canvas.width,canvas.height)/2;//画布的中心点const cx = canvas.width/2;const cy = canvas.height/2;//大圆上的随机角度并换算成弧度const rad = getRandom(0,360)*(Math.PI/180); //粒子的坐标this.x = cx + r*Math.cos(rad);this.y = cy + r*Math.sin(rad);//粒子的尺寸this.size = getRandom(1*window.devicePixelRatio,5*window.devicePixelRatio);}randomHexColor() { //随机生成十六进制颜色const hex = Math.floor(Math.random() * 16777216).toString(16); //生成ffffff以内16进制数while (hex.length < 6) { //while循环判断hex位数,少于6位前面加0凑够6位hex = '0' + hex;}return '#' + hex; //返回‘#'开头16进制颜色}draw(){ctx.beginPath();ctx.fillStyle = color;//ctx.fillStyle = this.randomHexColor();ctx.arc(this.x,this.y,this.size,0,2*Math.PI);ctx.fill();}moveTo(targetX,targetY){//设定一个缓动时间 500 ms毫秒//起始位置const startX = this.x;const startY = this.y;const duration = 500;//横向速度 目标位置 减去 当前位置 再除以运动的时间const xSpeed = (targetX - startX)/duration;//纵向速度 目标位置 减去 当前位置 再除以运动的时间const ySpeed = (targetY - startY)/duration;//起始时间const startTime = Date.now();//增加动画效果const _move = ()=>{//运动时间const t = Date.now() - startTime;//x方向运动距离 起始位置 加 速度乘以时间const x = startX + xSpeed * t;//y方向运动距离 起始位置 加 速度乘以时间const y = startY + ySpeed * t;//x,y缓动this.x = x;this.y = y;//超过设定时间取消运动if(t>=duration){// 赋值为目标位置this.x = targetXthis.y = targetY}//重新注册rafrequestAnimationFrame(_move)}//执行移动_move();}}//点数据组const partciles = [];//需要绘制的文本let text = null;//填充点数据for(let i = 0;i<1;i++){//画单个点const p = new Particle();//启动绘制//p.draw();//添加进数组partciles.push(p);}//清空画布function clear(){//清空画布ctx.clearRect(0,0,canvas.width,canvas.height);tempCtx.clearRect(0,0,tempCanvas.width,tempCanvas.height);// 将缓存 canvas 复制到旧的 canvas//ctx.drawImage(tempCanvas,0,0,canvas.width,canvas.height);}// 新建一个 canvas 作为缓存 canvasconst tempCanvas = document.createElement('canvas');// 设置缓冲画布的宽高tempCanvas.width = canvas.width; tempCanvas.height = canvas.height;const tempCtx = tempCanvas.getContext('2d');//绘制function draw(){//在每次绘制文本之前清空画布clear();// 开始绘制//let img = document.createElement('img');//let data = canvas.toDataURL();//img.src = data;// 缓存 canvas 绘制完成//tempCtx.drawImage(img,0,0);update();//遍历数组中的所有点partciles.forEach((p)=>{//绘制点p.draw();//p.moveTo(0,0);})//下一次绘制时注册RAF,重新绘制requestAnimationFrame(draw);}//生成文字function getText(){//得到当前时间 并做截取return new Date().toTimeString().substring(0,8);}//绘制文字function update(){//获得文本const newText = getText();if(newText == text){//相同就不绘制return;}text = newText;//绘制 文本const { width, height } = tempCanvas;if(drImage){// let temp = localStorage.getItem("threeImage");// CSSImageValue 或 HTMLCanvasElement 或 HTMLImageElement 或 HTMLVideoElement 或 ImageBitmap 或 OffscreenCanvas 或 SVGImageElement 或 VideoFrame//tempCtx.drawImage(document.getElementById("testImage"),0,0,tempCanvas.width,tempCanvas.height);tempCtx.fillStyle = "#000";tempCtx.textBaseline = "middle";tempCtx.font = `${100*window.devicePixelRatio}px '华文细黑', sans-serif`;//计算出绘制出的文本的宽度const textWidth = tempCtx.measureText(text).width;tempCtx.fillText(text,(width - textWidth)/3,height/2);//imageBox.appendChild(tempCanvas);}//拿到画布上黑色像素点的坐标const points = getPoints();//console.log(points);//如果数组长度小于文本坐标的长度 删除多余部分即可if(points.length < partciles.length){partciles.splice(points.length);}scene.remove(tempCubes); // 更新前删除旧数码显示tempCubes = null;tempCubes = new THREE.Object3D();for(let i=0;i<points.length;i++){let p = partciles[i];//粒子不足创建即可if(!p){p = new Particle();partciles.push(p);}const [x,y] = points[i];p.moveTo(x,y);let tempCube = cube.clone();tempCube.position.set((x/10),(y/10),(y/10));tempCubes.add(tempCube);}if(tempCubes){//calcMeshCenter(tempCubes);rotateAroundWorldAxis(tempCubes,new THREE.Vector3(1,0,0), Math.PI);tempCubes.position.set(-36,10,0);tempCubes.rotateX(-Math.PI/2);tempCubes.scale.set(0.5, 0.5, 0.5);//flipMesh(tempCubes);let position = new THREE.Vector3();let quaternion = new THREE.Quaternion();let scale = new THREE.Vector3();tempCubes.matrixWorld.decompose( position, quaternion, scale );tempCubes.updateMatrixWorld( true );scene.add(tempCubes);}}function flipMesh(object3D) {object3D.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1));reverseWindingOrder(object3D);}function reverseWindingOrder(object3D) {// TODO: Something is missing, the objects are flipped alright but the light reflection on them is somehow brokenif (object3D.type === "Mesh") {var geometry = object3D.geometry;for (var i = 0, l = geometry.faces.length; i < l; i++) {var face = geometry.faces[i];var temp = face.a;face.a = face.c;face.c = temp;}var faceVertexUvs = geometry.faceVertexUvs[0];for (i = 0, l = faceVertexUvs.length; i < l; i++) {var vector2 = faceVertexUvs[i][0];faceVertexUvs[i][0] = faceVertexUvs[i][2];faceVertexUvs[i][2] = vector2;}geometry.computeFaceNormals();geometry.computeVertexNormals();}if (object3D.children) {for (var j = 0, jl = object3D.children.length; j < jl; j++) {reverseWindingOrder(object3D.children[j]);}}}function getPoints(){// 得到画布上制定范围内的像素点信息const { width, height, data} = tempCtx.getImageData(0,0,tempCanvas.width,tempCanvas.height);//console.log(data);const points = [];//像素点取稀释点const gap = 4;for(let i = 0; i< width;i+=gap){for(let j = 0; j< height;j+=gap){//通过行号、列号 计算像素点的下标const index = (i+j*width)*4;const r = data[index];const g = data[index+1];const b = data[index+2];const a = data[index+3];//判断是否透明if(r===0&&g===0&&b===0&&a===255){//console.log(r,g,b,a);points.push([i,j])}}}return points;}let imageBox = document.getElementById('threeImage')const loadImage = (url) => {return new Promise((resolve, reject) => {const img = new Image()img.onload = () => {resolve(img);}img.onerror = (err) => {reject(err)}img.src = urlimg.alt = "threeImage";})}function startDownload(url) {document.getElementById("testImage").src = url;document.getElementById("testImage").width = window.innerWidthdocument.getElementById("testImage").height = window.innerHeightloadImage(url).then((resImage)=>{imageReceived(resImage)}).catch((err)=>{throw new Error(err); // reject promise});//let imageURL = url;//let imageDescription = "three image";//downloadedImg = new Image();//downloadedImg.crossOrigin = "Anonymous";//downloadedImg.addEventListener("load", imageReceived, false);//downloadedImg.alt = imageDescription;//downloadedImg.src = imageURL;}let drImage = nullfunction imageReceived(downloadedImg) {const c = document.createElement("canvas");let context = c.getContext("2d");c.width = window.innerWidth || downloadedImg.width;c.height = window.innerHeight || downloadedImg.height;c.innerText = downloadedImg.alt;//downloadedImg.setAttribute('crossOrigin', 'anonymous');//context.drawImage(downloadedImg, 0, 0, c.width, c.height);//imageBox.appendChild(c);//确保图片被加载完成 使用drawImage绘制到画布上面drImage = downloadedImg; //画布已被跨域数据污染的操作try {localStorage.setItem("threeImage", c.toDataURL("image/png"));draw();// 复杂问题拆解成简单化问题// 画单个点,画很多个点,画很多个运动的点// 提升代码掌控能力,开发思维} catch (err) {console.error(`Error: ${err}`);}}//需要服务器端支持 CORS policy: No 'Access-Control-Allow-Origin' startDownload('https://ts2.cn.mm.bing.net/th?id=OJ.nhM9wE4TnIlRRg');setup();</script></body></html>
相关文章:

分享HTML显示2D/3D粒子时钟
效果截图 实现代码 线上体验:three.jscannon.js Web 3D <!DOCTYPE html> <head> <title>three.jscannon.js Web 3D</title><meta charset"utf-8"><meta name"viewport" content"widthdevice-width,ini…...

Java——IDEA使用
一、IDEA介绍 IntelliJ IDEA 是 JetBrains 公司开发的一款功能强大的集成开发环境(IDE),主要用于 Java 编程语言,但也支持多种其他语言和框架。由于其强大的功能和灵活性,IntelliJ IDEA 被广泛应用于软件开发领域&…...
高性能STL库 EASTL 、高性能JSON库
GitHub - electronicarts/EASTL: EASTL stands for Electronic Arts Standard Template Library. It is an extensive and robust implementation that has an emphasis on high performance. 兄弟们,对STL要求性能高的可以试试这个EASTL库!!…...
多通道采集器采样接口设计[进行中...]
1.技术问题 这是一个非常小的设计,完全不值得把它展示出来。但是因为这个接口设计关系到一些细微的配置和技术限制,仍然有一些细节需要处理,并且很容易出错,我们先把技术问题罗列一下: 多个传感器对应的多个逻辑通道…...
rapidjson使用中crash问题分析
问题 在使用rapidjson时,使用Document的Parse方法解析json字符串,程序crash。 分析 可以参考https://github.com/Tencent/rapidjson/issues/1269,由于rapidjson的内存分配器默认认为内存分配成功,没有对分配后做判空判断&#…...
TCP协议中的三次握手和四次挥手机制
TCP协议中的三次握手和四次挥手机制 TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议,它的三次握手和四次挥手机制是建立和断开连接的关键步骤。 三次握手: 第一次…...

Linux双网卡默认路由的metric设置不正确,导致SSH连接失败问题定位
测试环境 VMware虚拟机 RockyLinux 9 x86_64 双网卡:eth0(访问外网): 10.206.216.92/24; eth1(访问内网) 192.168.1.4/24 问题描述 虚拟机重启后,SSH连接失败,提示"Connection time out",重启之前SSH连接还是正常的…...
Batch入门学习:从零开始掌握批处理脚本
目录 1. Batch脚本简介 1.1 什么是Batch脚本? 1.2 Batch脚本的历史 1.3 Batch脚本的应用场景 2. Batch脚本基本语法 2.1 注释 2.2 变量 2.3 常用命令 2.3.1 ECHO 2.3.2 PAUSE 2.3.3 CLS 2.3.4 GOTO 2.3.5 IF 2.3.6 FOR 2.4 参数传递 2.5 输入输出重…...

diffusion model(十八):diffusion model中negative prompt的工作机制
info个人博客主页http://myhz0606.com/article/ncsn 前置阅读: DDPM: http://myhz0606.com/article/ddpm classifier-guided:http://myhz0606.com/article/guided classifier-free guided:http://myhz0606.com/article/classi…...

Python | Leetcode Python题解之第200题岛屿数量
题目: 题解: class Solution:def dfs(self, grid, r, c):grid[r][c] 0nr, nc len(grid), len(grid[0])for x, y in [(r - 1, c), (r 1, c), (r, c - 1), (r, c 1)]:if 0 < x < nr and 0 < y < nc and grid[x][y] "1":self.d…...

利用圆上两点和圆半径求解圆心坐标
已知圆上两点P1,P2,坐标依次为 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2),圆的半径为 r r r,求圆心的坐标。 假定P1,P2为任意两点,则两点连成线段的中点坐标是 x m i …...

从ChatGPT代码执行逃逸到LLMs应用安全思考
摘要 11月7日OpenAI发布会后,GPT-4的最新更新为用户带来了更加便捷的功能,包括Python代码解释器、网络内容浏览和图像生成能力。这些创新不仅开辟了人工智能应用的新境界,也展示了GPT-4在处理复杂任务方面的惊人能力。然而,与所有…...
Python入门-基础知识-变量
1.标识符与关键字 Python语言规定,标识符由字母、数字和下画线组成,且不允许以数字开头。合法的标识符可以 是student_1、 addNumber、num等,而3number、2_student等是不合法的标识符。在使用标识符时应注意以下几点。 (1)命名时应遵循见名知…...

设计模式原则——接口隔离原则
设计模式原则 设计模式示例代码库地址: https://gitee.com/Jasonpupil/designPatterns 接口隔离原则 要求程序员尽量将臃肿庞大的接口拆分为更小的和更具体的接口,让接口中只包含客户感兴趣的方法接口隔离原则的目标是降低类或模块之间的耦合度&…...

MySQL数据库——在Centos7环境安装
MySQL在Centos7环境安装 1.切换root用户 安装与卸载中,用户全部切换成为root,安装好后,普通用户也能使用 2.卸载不要的环境 要将自己环境中有关mysql的全都删除,避免安装过程中被影响 ps axj | grep mariadb 先检查是否有mari…...

怎样规避液氮容器内部结霜的问题
液氮容器内部结霜问题一直是我们在使用液氮储存罐时遇到的一个棘手难题。液氮的极低温度使得容器内部很容易产生结霜现象,这不仅影响了容器的正常使用,还可能对内部样品或设备造成损坏。因此,如何有效规避液氮容器内部结霜问题成为了每个使用…...

冶金工业5G智能工厂工业物联数字孪生平台,推进制造业数字化转型
冶金工业5G智能工厂工业物联数字孪生平台,推进制造业数字化转型。传统生产方式难以满足现代冶金工业的发展需求,数字化转型成为必然趋势。通过引入5G、工业物联网和数字孪生等先进技术,冶金工业可以实现生产过程智能化、高效化和绿色化&#…...
一文入门机器学习参数调整实操
作者前言: 通过向身边的同事大佬请教之后,大佬指点我把本文的宗旨从“参数调优”改成了“参数调整”。实在惭愧,暂时还没到能“调优”的水平,本文只能通过实操演示“哪些操作会对数据训练产生影响”,后续加深学习之后,…...

基于51单片机的银行排队呼叫系统设计
一.硬件方案 本系统是以排队抽号顺序为核心,客户利用客户端抽号,工作人员利用叫号端叫号;通过显示器及时显示当前所叫号数,客户及时了解排队信息,通过合理的程序结构来执行排队抽号。电路主要由51单片机最小系统LCD12…...
JXCategoryView的使用总结
一、初始化 -(JXCategoryTitleView *)categoryView{if (!_categoryView) {_categoryView [[JXCategoryTitleView alloc] init];_categoryView.delegate self;_categoryView.titleDataSource self;_categoryView.averageCellSpacingEnabled NO; //是否平均分配项目之间的间…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...