分享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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/33684.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据处理技术影响皮质-皮质间诱发电位的量化

摘要 皮质-皮质间诱发电位(CCEPs)是探究颅内人体电生理学中有效连接性的常用工具。与所有人体电生理学数据一样&#xff0c;CCEP数据极易受到噪声的影响。为了解决噪声问题&#xff0c;通常会对CCEP数据进行滤波和重参考&#xff0c;但不同的研究会采用不同的处理策略。本研究…

Kotlin 中的数据类型有隐式转换吗?

在 Kotlin 中&#xff0c;数据类型不可隐式转换。在 Java 中&#xff0c;如果数据是从小到大&#xff0c;是可以隐式转换的&#xff0c;数据类型将自动提升。 下面以 int 类型的数据为例&#xff0c;在 Java 中这样写是可以的&#xff1a; int a 2312; long b a;但是在 Kot…

【产品经理】订单处理10-分配快递策略

本次主要讲解下在订单处理过程中分配快递的策略以及分配快递中需要用到的设置。 一、建立快递档案 在ERP系统中&#xff0c;需要建立快递档案&#xff0c;设置所属快递、快递的服务类型、支持的打印模版以及快递在各个平台的电子面单支持情况。 二、仓库绑定快递 仓库需要设…

基于SpringBoot前后端分离在线骑行网站设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

Python学习笔记19:进阶篇(八)常见标准库使用之glob模块和argparse模块

前言 本文是根据python官方教程中标准库模块的介绍&#xff0c;自己查询资料并整理&#xff0c;编写代码示例做出的学习笔记。 根据模块知识&#xff0c;一次讲解单个或者多个模块的内容。 这里贴一下教程地址&#xff1a;https://docs.python.org/zh-cn/3/tutorial/stdlib.h…

应变计在工程中的角色:精准监测与安全保障的得力助手

在工程领域中&#xff0c;应变计作为一种重要的测量工具&#xff0c;扮演着精准监测与安全保障的得力助手的角色。它能够实时、准确地测量物体在受力作用下的变形情况&#xff0c;为工程师提供关键的数据支持&#xff0c;从而确保工程的稳定性与安全性。 应变计在工程中的应用范…

【办公类-51-01】月评估数字生成01-平均数空值

期末需要制作月评估&#xff0c;每月给孩子的能力水平打分。 以前我是做在EXCEL里&#xff0c;手动打分&#xff0c;然后用公式计算1、2、3出现的个数&#xff0c;然后计算平均数&#xff0c;最后复制到Word里。 因为是手动计算&#xff0c;每次都要算很长时间&#xff0c;确保…

基于 NXP LS1046 +FPGA系列 CPCI 架构轨道交通专用板卡

基于 NXP LS1046 系列 CPCI 架构轨道板卡 该产品是一款 CPCI 无风扇架构的高可靠性板卡&#xff0c;CPU 选用 NXP LS1046A 系统平台&#xff0c;支持嵌入式 Linux 或者标准 Ubuntu Linux 、凝思等操作系统&#xff0c;轨道交通 EMC 及宽温级别设计&#xff0c;板载多路 M12 高速…

蓝牙技术|苹果iOS 18的第三方配件将支持AirPods / AirTag的配对体验

苹果公司在 iOS 18 系统中引入了名为 AccessorySetupKit 的新 API&#xff0c;用户不需要进入蓝牙设置和按下按钮&#xff0c;系统就能识别附近的配件&#xff0c;并提示用户进行配对。首次向配件制造商开放这种配对体验。 iPhone 用户升级 iOS 18、iPad 用户升级到 iPadOS 1…

使用 MinIO S3 和 Tailscale VPN 部署应用程序基础设施

在现代 IT 运营中&#xff0c;面向 IT 运营的人工智能 &#xff08;AI-Ops&#xff09; 正在通过使用高级算法自动执行任务来改变数据管理。MinIO 和 Tailscale 共同为应用层开发提供了安全、可扩展和有效的基础设施。Tailscale 凭借其基于 WireGuard 的覆盖 VPN 网络&#xff…

【小白专用 已验证24.6.18】C# SqlSugar操作MySQL数据库实现增删改查

【小白专用24.6.18】C# SqlSugar&#xff1a;连接数据库实现简单的&#xff0c;增、删、改、查-CSDN博客 SqlSugar .Net ORM 5.X 官网 、文档、教程 - SqlSugar 5x - .NET果糖网 SqlSugar项目创建 通过NuGet包管理器搜索SqlSugar&#xff08;MySql还要安装MySql.Data、Newton…

Redis之商品缓存

文章目录 什么是缓存添加Redis缓存缓存更新策略缓存穿透缓存空对象布隆过滤器 缓存雪崩给不同的key的TTL添加随机值利用Redis集群提高服务的可用性给缓存业务添加降级限流策略给业务添加多级缓存 缓存击穿互斥锁逻辑过期 缓存工具封装方法1 写入redis方法2 设置逻辑过期方法3 解…

驱动开发(六):应用层通过文件系统与内核层交互

驱动开发相关文章: 驱动开发&#xff08;一&#xff09;&#xff1a;驱动代码的基本框架 驱动开发&#xff08;二&#xff09;&#xff1a;创建字符设备驱动 驱动开发&#xff08;三&#xff09;&#xff1a;内核层控制硬件层 驱动开发&#xff08;四&#xff09;&#xff…

借助TheGraph 查询ENS信息

关于ENS (以太坊域名服务) ENS 全称是 Ethereum Name Service,它是一个建立在以太坊区块链上的去中心化域名系统。 ENS 在 Web3 领域发挥着重要作用,主要有以下几个方面: 可读性更好的地址: ENS 允许用户将复杂的以太坊地址(如 0x12345…) 映射为更简单易记的域名。这极大地提…

中国车牌检测数据集VOC+YOLO格式2001张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2001 标注数量(xml文件个数)&#xff1a;2001 标注数量(txt文件个数)&#xff1a;2001 标注…

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统&#xff1f; 从严格意义上说&#xff0c;可将操作系统定义为一种软件&#xff0c;它控制计算机硬件资源&#xff0c;提供程序运行环境。我们通常将这种软件称为内核&#xff08;kerel)&#xff0c;因为它相对较小&#xff0c;而且位于环境的核心。 从广义上…

STM32介绍和资料地址

STM32标准外设软件库 https://www.st.com.cn/zh/embedded-software/stm32-standard-peripheral-libraries.html 支持标准外设库的产品系列&#xff1a;

XMLTomcatHttp协议

XML&Tomcat&Http协议 目录 XML&Tomcat&Http协议 1. xml解析(了解) 1.1 配置文件 1.1.1 配置文件的作用 1.1.2 常见的配置文件类型 1.2 properties文件 1.2.1 文件示例 1.2.2 语法规范 1.3 XML文件 1.3.1 文件示例 1.3.2 概念介绍 1.3.3 XML的基本语…

【Android】记录在自己的AMD处理器无法使用Android studio 虚拟机处理过程

文章目录 问题&#xff1a;无法在AMD平台打开Android studio 虚拟机&#xff0c;已解决平台&#xff1a;AMD 5700g系统&#xff1a;win10专业版1、在 amd平台上使用安卓虚拟机需要安装硬件加速器2、关闭win10上的系统服务 问题&#xff1a;无法在AMD平台打开Android studio 虚拟…

LabVIEW电机故障监测系统

电机作为工业生产中的关键设备&#xff0c;其故障会导致生产停滞和经济损失。因此&#xff0c;开发一个能实时监控电机状态并预测潜在故障的系统具有重要意义。通过高效的数据采集和分析技术&#xff0c;提升故障诊断的准确性和及时性。 系统组成 该系统由以下部分组成&#…