Three.js 彩色画板

文化   2024-09-28 12:02   河南  

使用 Three.js 的 WebGL 小实验。Three.js 彩色画板。

实现代码

HTML:

<ul class="colours">  <li></li>  <li></li>  <li></li>  <li></li>  <li></li></ul><button class="pencil__refresh btn">  Clear</button><button class="pencil__submit btn">Save</button>
CSS:
#mfPreviewBar {display: none !important;}

html,body { position: fixed; overflow: hidden;touch-action: none;}

body {background-color: #f7f4f0;cursor: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/605067/cursor.png'), auto;font-family: 'Galano Grotesque Semi Bold';margin: 0;overflow: hidden;}

button {font-family: 'Galano Grotesque Semi Bold';}

.colours {bottom: 30px;display: none;left: 50%;list-style-type: none;padding-left: 0;position: absolute;transform: translateX(-50%);z-index: 10;
@media (min-width: 1024px) {display: flex;}
li {border-radius: 50%;display: inline-block;height: 14px;margin: 0 12px;width: 14px;
&:nth-child(1){background-color: #100c08;}&:nth-child(2){background-color: #759BA9;}&:nth-child(3){background-color: #77dd77;}&:nth-child(4){background-color: #ff6961;}&:nth-child(5){background-color: #ffd1dc;}}}

.pencil__refresh {background-color: #FCF4EA;border: none;border-radius: 50%;bottom: 18px;cursor: pointer;height: 26px;padding: 4px 1px 0px;position: absolute;left: 50%;text-align: center;transform: translateX(-50%);width: auto;z-index: 3;
@media (min-width: 1024px) {bottom: 27px;left: 30px;transform: none;}}

.pencil__submit {bottom: 27px;display: none;position: absolute;right: 30px;z-index: 4;
@media (min-width: 1024px) { display: block;}}

.btn {background-color: transparent;border: none;cursor: pointer;font-size: 13px;letter-spacing: .05em;outline: none;padding: 5px 10px 1px 10px;text-transform: uppercase;user-select: none;-moz-user-select: none;}

JAVASCRIPT:

// -- Drawingconst colors  = ["#100c08","#759BA9","#77dd77","#ff6961","#ffd1dc"]

let currentColorIndex = 0

let colorButtons = document.querySelectorAll('.colours li'),colorButtonsL = colorButtons.length

for (var x = 0; x < colorButtonsL; x++) {((x) => {colorButtons[x].addEventListener('click', () => {setActiveColour(x)}) })(x)}

function setActiveColour(x) {currentColorIndex = x}

// -- Drawingconst drawingCanvas = document.createElement('canvas')drawingCanvas.width = window.innerWidthdrawingCanvas.height = window.innerHeight

drawingCanvas.style.position = 'fixed'drawingCanvas.style.left = 0drawingCanvas.style.top = 0drawingCanvas.style.zIndex = 1

document.body.appendChild(drawingCanvas)

var drawingCtx = drawingCanvas.getContext('2d')

let isDrawing,newlyUp = false,lastPoint

let pencilPathDefaults = {minThickness: .2,maxThickness: 2,}

let pencilPathCurrent = {thickness: .2}

let pencilPathTarget = {thickness: .2}

document.addEventListener('touchstart', handleMouseDown)document.addEventListener('mousedown', handleMouseDown)

function handleMouseDown(e) {if (e.target.localName !== 'canvas') return isDrawing = truepencilTargetPos.height = 0pencilTargetPos.heightRotate = .2

pencilPathTarget.thickness = pencilPathDefaults.maxThickness
let xPos = (e.touches ? e.touches[0].clientX : e.clientX),yPos = (e.touches ? e.touches[0].clientY : e.clientY)

let currentPoint = { x: xPos, y: yPos }

drawingCtx.beginPath()drawingCtx.fillStyle = colors[currentColorIndex];drawingCtx.globalAlpha = .9drawingCtx.arc(currentPoint.x + 5, currentPoint.y + 5, pencilPathDefaults.thickness,false, Math.PI * 2, false)drawingCtx.fill()

lastPoint = currentPoint}

document.addEventListener('touchend', handleMouseUp)document.addEventListener('mouseup', handleMouseUp)

function handleMouseUp() {newlyUp = true isDrawing = falsepencilTargetPos.height = pencilDefaultPos.heightpencilTargetPos.heightRotate = 0pencilPathTarget.thickness = pencilPathDefaults.minThickness
setTimeout(fullyUp, 50)}

function fullyUp() {newlyUp = false}

// -- Pencil// Renderervar renderer = new THREE.WebGLRenderer({alpha: true,antialias: true,autoClear: true})

renderer.setClearColor(0x000000, 0)renderer.setPixelRatio(window.devicePixelRatio)renderer.setSize(window.innerWidth, window.innerHeight)renderer.domElement.style.zIndex = 5renderer.domElement.style.position = 'relative'renderer.domElement.style.pointerEvents = 'none'document.body.appendChild(renderer.domElement)

// Ensure Full Screen on Resizefunction fullScreen () {camera.aspect = window.innerWidth / window.innerHeightcamera.updateProjectionMatrix()

renderer.setSize(window.innerWidth, window.innerHeight)}

window.addEventListener('resize', fullScreen, false)

// Scenevar scene = new THREE.Scene()

// Lighting

let hemiLight,dirLight

function createLights () {hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 )hemiLight.color.setHSL( 0.6, 1, 0.2 )hemiLight.groundColor.setHSL( 0.095, 1, 0.75 )hemiLight.position.set( 0, 0, 50 )scene.add( hemiLight )hemiLightHelper = new THREE.HemisphereLightHelper( hemiLight, 10 )// scene.add( hemiLightHelper )
directionalLight = new THREE.DirectionalLight( 0xffffff, 1 )directionalLight.color.setHSL( 1, 1, 1 )directionalLight.position.set( -.5, 1, 10 )scene.add( directionalLight )directionalLight.castShadow = truedirectionalLight.shadow.mapSize.width = 2048directionalLight.shadow.mapSize.height = 2048directionalLightHelper = new THREE.DirectionalLightHelper( directionalLight, 10 )// scene.add( directionalLightHelper )
spotLight = new THREE.SpotLight( 0xffffff, 0.4 )spotLight.color.setHSL( 1, 1, 1 )spotLight.position.set( -7, 1, 2 )scene.add( spotLight )spotLight.castShadow = falsespotLight.shadow.mapSize.width = 2048spotLight.shadow.mapSize.height = 2048spotLightHelper = new THREE.SpotLightHelper( spotLight, 10 )// scene.add( spotLightHelper )
topLight = new THREE.SpotLight( 0xffffff, 0.1 )topLight.color.setHSL( 1, 1, 1 )topLight.position.set( 1, -7, 2 )scene.add( topLight )topLight.castShadow = falsetopLight.shadow.mapSize.width = 2048topLight.shadow.mapSize.height = 2048topLightHelper = new THREE.SpotLightHelper( topLight, 10 )// scene.add( topLightHelper )}

createLights()

function createGround () {let groundGeo = new THREE.PlaneBufferGeometry( 10000, 10000 )let groundMat = new THREE.ShadowMaterial()groundMat.opacity = 0.1

this.ground = new THREE.Mesh( groundGeo, groundMat )this.ground.position.z = 0this.ground.position.y = 0this.ground.receiveShadow = true

this.scene.add( this.ground )}

createGround()

// Shadowsrenderer.shadowMap.enabled = truerenderer.shadowMapSoft = truerenderer.shadowMap.type = THREE.PCFSoftShadowMap

// Camera and positionvar camera = new THREE.PerspectiveCamera( 8, window.innerWidth / window.innerHeight, 1, 10000 )

camera.position.y = 0camera.position.z = 12

// Custom (OGJ) Objectsconst codepenAssetUrl = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/605067/'

let onProgress = function ( xhr ) {if ( xhr.lengthComputable ) {let percentComplete = xhr.loaded / xhr.total * 100// console.log( Math.round(percentComplete, 2) + '% downloaded' )}}

let onError = function ( xhr ) {console.log('ERROR')}

var mtlLoader = new THREE.MTLLoader()mtlLoader.setPath(codepenAssetUrl)

let pencil,pencilDefaultPos = {height: .04,xRotate: 0,yRotate: 0,zRotate: 0},pencilPos = {height: pencilDefaultPos.height,xRotate: 0,yRotate: 0,zRotate: 0},pencilTargetPos = {height: pencilDefaultPos.height,xRotate: 0,yRotate: 0,zRotate: 0,heightRotate: 0}

mtlLoader.load('pencil-multi.mtl', function (materials) {materials.preload()

var objLoader = new THREE.OBJLoader()objLoader.setMaterials(materials)objLoader.setPath(codepenAssetUrl)objLoader.load('pencil-multi.obj', function (obj) {
obj.traverse( function ( child ) {if ( child instanceof THREE.Mesh ) {child.castShadow = true

let area = new THREE.Box3()area.setFromObject( child )

let yOffset = area.min.yobj.position.y = 0}})

obj.castShadow = true

pencil = obj

rotateAroundWorldAxis(pencil, new THREE.Vector3(1, 0, 0), THREE.Math.degToRad(90))rotateAroundWorldAxis(pencil, new THREE.Vector3(1, 0, 0), THREE.Math.degToRad(-10))rotateAroundWorldAxis(pencil, new THREE.Vector3(0, 1, 0), THREE.Math.degToRad(20))

pencilDefaultPos.xRotate = pencil.rotation.xpencilDefaultPos.yRotate = pencil.rotation.ypencilDefaultPos.zRotate = pencil.rotation.z

scene.add(pencil)

animate()document.addEventListener('touchmove', stopScroll)document.addEventListener('touchmove', mouseMove)document.addEventListener('mousemove', mouseMove)

}, onProgress, onError, null, false)}, onProgress, onError, null, false)

function animate () { // Pencil position from paperpencilPos.height += (pencilTargetPos.height - pencilPos.height) * .2

// Move pencil point position if requiredpencil.position.copy(mousePos)pencil.position.x += pencilPos.heightpencil.position.y += pencilPos.heightpencil.position.z += pencilPos.height
// Pencil thicknesspencilPathCurrent.thickness += (pencilPathTarget.thickness - pencilPathCurrent.thickness) * .2
// Tilt pencil// xif ( isNotClose(pencilPos.xRotate, pencilTargetPos.xRotate) ) {let newRotate = (pencilTargetPos.xRotate - pencilPos.xRotate) * .2pencilPos.xRotate += Math.round(newRotate * 1000) / 1000pencil.rotation.x = pencilPos.xRotate + pencilDefaultPos.xRotate}
// yif ( isNotClose(pencilPos.yRotate, pencilTargetPos.yRotate) ) {let newRotate = (pencilTargetPos.yRotate - pencilPos.yRotate) * .2pencilPos.yRotate += Math.round(newRotate * 1000) / 1000pencil.rotation.y = pencilPos.yRotate + pencilDefaultPos.yRotate}
// zlet targetZAngle = pencilTargetPos.zRotate + pencilTargetPos.heightRotate
if ( isNotClose(pencilPos.zRotate, targetZAngle) ) {let newRotate = (targetZAngle - pencilPos.zRotate) * .2pencilPos.zRotate += Math.round(newRotate * 1000) / 1000pencil.rotation.z = pencilPos.zRotate + pencilDefaultPos.zRotate}
window.requestAnimationFrame( animate )renderer.render( scene, camera )}

function clearCanvas () {drawingCtx.clearRect(0, 0, drawingCtx.canvas.width, drawingCtx.canvas.height) }

let mouse = {x: 0,y: 0},mousePos = new THREE.Vector3(0, 0, 0),mouseDirection = {x: 1,y: 1}

function stopScroll (e) {e.preventDefault()}

function mouseMove (e) {let xPos = (e.touches ? e.touches[0].clientX : e.clientX),yPos = (e.touches ? e.touches[0].clientY : e.clientY)
// Update the mouse variablee.preventDefault()mouse.x = (xPos / window.innerWidth) * 2 - 1mouse.y = - (yPos / window.innerHeight) * 2 + 1
if (!lastPoint) {lastPoint = { x: xPos, y: yPos }}

// Make the sphere follow the mouse var vector = new THREE.Vector3(mouse.x + .01, mouse.y - .01, 0.5)vector.unproject(camera)

var dir = vector.sub(camera.position).normalize()var distance = - camera.position.z / dir.zmousePos = camera.position.clone().add( dir.multiplyScalar( distance ) )
// Mouse Trackingvar currentPoint = { x: xPos, y: yPos }
var angle = angleBetween(lastPoint, currentPoint)let xDist = distanceBetweenSingle(lastPoint.x, currentPoint.x)let yDist = distanceBetweenSingle(lastPoint.y, currentPoint.y)

mouseDirection.x = (currentPoint.x > lastPoint.x ? 1 : -1)mouseDirection.y = (currentPoint.y > lastPoint.y ? -1 : 1)
let newXAngle = (yDist / 100)let newZAngle = (xDist / 100) * -1
let maxAngle = .25
pencilTargetPos.xRotate = (newXAngle > -maxAngle && newXAngle < maxAngle ? newXAngle : (newXAngle < -maxAngle ? -maxAngle : maxAngle))pencilTargetPos.zRotate = (newZAngle > -maxAngle && newZAngle < maxAngle ? newZAngle : (newZAngle < -maxAngle ? -maxAngle : maxAngle))
if (isDrawing || newlyUp) {let dist = distanceBetween(lastPoint, currentPoint)

for (var i = 0; i < dist; i += .3) {x = lastPoint.x + (Math.sin(angle) * i)y = lastPoint.y + (Math.cos(angle) * i)
drawingCtx.beginPath()drawingCtx.strokeStyle = 'blue';drawingCtx.globalAlpha = getRandomInt(.15, .25)drawingCtx.arc(x + 5, y + 5, pencilPathCurrent.thickness, false, Math.PI * 2, false)drawingCtx.fill()}}
lastPoint = currentPoint}

// Refreshlet refreshBtn = document.querySelector('.pencil__refresh')refreshBtn.addEventListener('click', handleRefresh)

function handleRefresh(e) {e.preventDefault()
clearCanvas() }

// Savelet submitBtn = document.querySelector('.pencil__submit')submitBtn.addEventListener('click', handleSubmit)

function handleSubmit(e) {e.preventDefault()
savePNG()}

function savePNG() {const freshCanvas = document.createElement('canvas')freshCanvas.width = drawingCanvas.widthfreshCanvas.height = drawingCanvas.height

let freshCtx = freshCanvas.getContext('2d')
freshCtx.fillStyle = "#f7f4f0"freshCtx.fillRect(0, 0, freshCanvas.width, freshCanvas.height)freshCtx.drawImage(drawingCanvas, 0, 0)
let imageDataURL = freshCanvas.toDataURL()let image = new Image()
image.src = imageDataURL
var w = window.open("") w.document.write(image.outerHTML)}

// Utilitiesfunction rotateAroundWorldAxis (obj, axis, radians) {let rotWorldMatrix = new THREE.Matrix4() rotWorldMatrix.makeRotationAxis(axis.normalize(), radians) rotWorldMatrix.multiply(obj.matrix) obj.matrix = rotWorldMatrix obj.setRotationFromMatrix(obj.matrix)}

function isNotClose (current, target) {return (current < target - .004 || current > target + .004)}

function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min}

function distanceBetween(point1, point2) { return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2))}

function distanceBetweenSingle(point1, point2) { return point1 - point2}

function angleBetween(point1, point2) { return Math.atan2( point2.x - point1.x, point2.y - point1.y )}

function direction(point1, point2) { return Math.atan2( point1, point2)}


源码:

https://codepen.io/bradarnett/pen/XyZKaG


体验:

https://codepen.io/bradarnett/full/XyZKaG



感谢您的阅读      

在看点赞 好文不断  


初识Threejs
初识 Three.js 的奇妙世界,走进三维空间,与小编一起拥抱前端技术,涉及WebGL、WebGPU、Threejs、Shader、GIS、VR/AR、数字孪生、3D图形学等。
 最新文章