5 Simple Steps to Build a 3D Pencil Drawing on Your Website: Complete Source Code & Guide

Introduction:

Creating a 3D Pencil Drawing Effect on Your Website can captivate your audience and add a unique touch to your design. In this guide, we’ll walk through the process of building a 3D pencil drawing effect using HTML, CSS, and JavaScript. Whether you’re a beginner or an experienced developer, this tutorial will help you add depth and interactivity to your website with an eye-catching 3D effect.

Main focus:

  • Objective: Learn to design a stunning 3D pencil drawing effect for a website.
  • Purpose: Add an engaging 3D pencil effect to attract and retain users.
  • Focus: Easy, step-by-step tutorial covering HTML, CSS, and JavaScript.
  • Skill Level: Suitable for both beginners and experienced developers.
  • Outcome: Create a 3D pencil drawing effect with full source code and clear explanations.

By the end of this guide, you’ll have a dynamic 3D pencil drawing feature that enhances your website’s visual appeal and interactivity, making it more engaging for visitors. Let’s dive in and bring this 3D design to life on your site!

What You’ll Learn in This Tutorial

In this tutorial, we’ll guide you through the process of creating a 3D pencil drawing effect from scratch in three simple steps. You’ll learn how to:

  1. Structure the HTML to create the basic layout, including the background whiteboard and black screen.
  2. Style the pencil with CSS to make the 3D pencil drawing visually appealing.
  3. Implement JavaScript functionality to enable a smooth and interactive drawing effect.

Additionally, we’ll provide a free downloadable source file that includes all the necessary code, so you can easily implement the effect on your website.

Step1: HTML For 3D Pencil Drawing Effect In Website:

HTML Code

HTML
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Pencil Drawing 3D</title>
    <link rel="shortcut icon" href="final upscale.png" type="image/x-icon">
</head>

<body>
    <div id="wrapper">
        <div id="bg"></div>
        <div id="intro"></div>
        <div id="sketchpad"></div>
        <canvas id="sketch"></canvas>
        <div id="shadow"></div>
        <canvas id="pencil3D"></canvas>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.4/TweenMax.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
    <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/356608/OBJLoader.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.4/paper-core.min.js"></script>
    <script src="main.js"></script>
</body>

</html>

HTML Explanation:

  1. <!DOCTYPE html> – Declares the document as HTML5.
  2. <html lang="en"> – Defines the language as English for accessibility and SEO.
  3. <head> – Contains metadata and links required for the HTML document.
  4. <meta charset="UTF-8"> – Sets the character encoding to UTF-8 for universal character support.
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0"> – Ensures the site is responsive on all devices by setting the viewport.
  6. <title>3D Pencil Drawing Effect</title> – Defines the title of the webpage that appears in the browser tab.
  7. <link rel="stylesheet" href="style.css"> – Links the external CSS file style.css for styling.
  8. <body> – The main content of the document begins here.
  9. <div class="container"> – Creates a container div to center the canvas on the page.
  10. <canvas id="pencilCanvas"></canvas> – Defines a canvas with an ID of pencilCanvas where the drawing effect will appear.
  11. <script src="script.js"></script> – Links to the JavaScript file script.js to control animation.
  12. </body> and </html> – Close the body and HTML tags.

Step 2 : CSS For 3D Pencil Drawing Effect In Website:

CSS Code

CSS

html,
body {
    margin: 0;
    padding: 0;
    cursor: none;
}

#bg {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background-color: black;
}

#intro {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background-image: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/356608/pencil-intro.jpg");
    background-repeat: no-repeat;
    background-position: center;
}

#wrapper {
    position: absolute;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

canvas {
    position: absolute;
    top: 0;
    left: 0;
}

#pencil3D {
    pointer-events: none;
}

#sketchpad {
    position: absolute;
    left: 50px;
    top: 50px;
    background-color: #fafafa;
    -webkit-border-radius: 4px;
    -moz-border-radius: 4px;
    border-radius: 4px;
    -webkit-box-shadow: 0px 3px 12px -2px rgba(0, 0, 0, 0.75);
    -moz-box-shadow: 0px 3px 12px -2px rgba(0, 0, 0, 0.75);
    box-shadow: 0px 3px 12px -2px rgba(0, 0, 0, 0.75);
}

CSS Explanation:

  1. body – Styles the main body of the page.
    • display: flex; – Uses flexbox to align items within the body.
    • justify-content: center; – Horizontally centers content.
    • align-items: center; – Vertically centers content.
    • height: 100vh; – Sets the body height to 100% of the viewport.
    • margin: 0; – Removes default body margin for full-width display.
    • background-color: #f4f4f9; – Sets a light gray background color.
  2. .container – Styles the container div.
    • position: relative; – Positions the container to center within the body.
    • width: 600px; and height: 400px; – Sets the dimensions of the canvas.
  3. canvas – Styles the canvas element.
    • width: 100%; and height: 100%; – Makes the canvas fill the container.
    • background-color: #fff; – Sets a white background for the drawing area.
    • box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); – Adds a shadow to give a 3D effect.
    • border-radius: 8px; – Rounds the corners of the canvas.

Step 3 : Javascript For 3D Pencil Drawing Effect In Website:

JavaScript Code

JavaScript


  (function(window){
	
	function Sketch() {
		this.group = new paper.Group();
		this.isDrawing = false;
		
    // paper.js allows us to use the mouse easily to create vector lines.
    // more here http://paperjs.org/tutorials/interaction/creating-mouse-tools/
		this.mouseTool = new paper.Tool();
		this.mouseTool.minDistance = 5;
		this.mouseTool.maxDistance = 30;
		this.mouseTool.on('mousedown', this.onMouseDown.bind(this));
		this.mouseTool.on('mousedrag', this.onMouseDrag.bind(this));
		this.mouseTool.on('mouseup', this.onMouseUp.bind(this));
	}
	
	Sketch.prototype.onMouseDown = function(e) {
		this.isDrawing = true;
		this.currPath = new paper.Path();
		
		this.currPath.fillColor = '#424242';
		
		this.currPath.add(e.point);
	}
	
	Sketch.prototype.onMouseDrag = function(e) {
		if (!this.isDrawing) return;
		
		if (!e.point.isInside(this.sketchingBounds)){
			this.onMouseUp(e);
			return;
		}
		
		var step = e.delta.divide(2);
		step.angle += 10;
		
		var top = e.middlePoint.add(step);
		var bottom = e.middlePoint.subtract(step);
		
		this.currPath.add(top);
		this.currPath.insert(0, bottom);
		this.currPath.smooth(10);
	}
	
	Sketch.prototype.onMouseUp = function(e) {
		if (!this.isDrawing) return;
		
		this.isDrawing = false;
		
		if (e.point.isInside(this.sketchingBounds)) {
			this.currPath.add(e.point);
			this.currPath.closed = true;
		}
		
		this.group.addChild(this.currPath);
	}

	Sketch.prototype.setSketchingBounds = function(x, y, width, height) {
		this.sketchingBounds = new paper.Rectangle(x, y, width, height);
	}
	
	window.Sketch = Sketch;
	
})(window);



/* Set up the Pencil */

var pencil = (function() {
	var pencil,
			isDrawing,
			rangeX = 0,
			rangeY = 0,
			scale = 1.5,
			canvasW,
			canvasH,
			moveRotationRange = {x: 100, y: 30};
	
	function init(_canvasW, _canvasH, onReady) {
		canvasW = _canvasW;
		canvasH = _canvasH;
		isDrawing = false;
    pencil = new THREE.Object3D();
    
    // Load the 3D model and its materials
    var mtlLoader = new THREE.MTLLoader();
		mtlLoader.load( 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/356608/PENCIL.mtl', function( materials ) {
			materials.preload();
			
			var objLoader = new THREE.OBJLoader();
			objLoader.setMaterials( materials );
			objLoader.load( 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/356608/PENCIL.obj', function ( object ) {
				var scale = 1.5;
				object.rotation.x = toRad(90);
				object.scale.set(scale, scale, scale);
				pencil.add(object);
				scene.add( pencil );
			});
			
			stopDrawing();
			onReady();
		});
	}
	
	function startDrawing() {
		isDrawing = true;
		TweenLite.to(pencil.position, 0.2, {z: 0, ease: Expo.easeOut});
	}
	
	function stopDrawing() {
		isDrawing = false;
		TweenLite.to(pencil.position, 0.2, {z: 2, ease: Quad.easeInOut});
	}
	
	function move(x, y) {
		rangeX = (x / wW) - 0.5;
		rangeY = (y / wH) - 0.5;
		
    // Convert the current mouse position to a point in 3D space.  This involves 'unprojecting' the
    // 2D values into 3D space.  More info here: 
    // https://barkofthebyte.azurewebsites.net/post/2014/05/05/three-js-projecting-mouse-clicks-to-a-3d-scene-how-to-do-it-and-how-it-works
		var mouse3D = new THREE.Vector3( (x / wW) * 2 - 1, -(y / wH) * 2 + 1, 0.5 );
		mouse3D.unproject(camera);
		var dir = mouse3D.sub(camera.position).normalize();
		dir.x *= wW / canvasW;
		dir.y *= wH / canvasH;
		var distance = - camera.position.z / dir.z;
		var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );
		
    pencil.position.x = pos.x;
    pencil.position.y = pos.y;
    TweenLite.to(pencil.rotation, 0.2, {y: rangeX * toRad(moveRotationRange.x), x: rangeY * toRad(moveRotationRange.y), z: toRad(rangeY * 200)});
	}
	
  // Utility function for converting degrees to radians
	function toRad(deg) {
		return (Math.PI / 180) * deg;
	}
	
	return {
		init: init,
		startDrawing: startDrawing,
		stopDrawing: stopDrawing,
		move: move,
		isDrawing: function() {
			return isDrawing;
		}
	}
	
})();


// Set up the pencil's shadow.  This gives the illusion that the pencil is floating above the pad.
// The shadow is actually a 2D vector graphic rendered in the same canvas as the sketch (also using paper.js).
// I'm not sure how to get a crisp shadow in THREE.js, which is why I used the 2D canvas to render it.
// The shadow rotates when the pencil is moved across the sketchpad to give the illusion that the 
// light source is at a consistent position (to the top right).
// It also gets darker as the pencil is moved closer to the paper.

(function(window) {
	
	function Shadow() {
		this.rotationRange = 90;
		this.startAngle = -25;
		this.grp = new paper.Group();
		this.grp.applyMatrix = false;
		this.grp.rotation = this.startAngle;
		this.grp.pivot = new paper.Point(0, 0);
		
    // Load the shadow graphic
		paper.project.importSVG('https://s3-us-west-2.amazonaws.com/s.cdpn.io/356608/shadow.svg', function(el) {
			el.position = new paper.Point(0, 85);
			el.opacity = 0.5;
			el.scale(0.6);
			el.applyMatrix = false;
			this.el = el;
			this.grp.addChild(this.el);
			this.goFar();
		}.bind(this));
		
		this.isDragging = false;
	}
	
	Shadow.prototype.move = function(x, y) {
		if (!this.el) return;
		
	  rangeX = (x / wW) - 0.5;
		this.grp.rotation = this.startAngle - (this.rotationRange * rangeX);
		this.grp.position = new paper.Point(x, y);
	}
	
	Shadow.prototype.goNear = function() {
		if (!this.el) return;
		
		this.isDragging = true;
		TweenLite.to(this.el, 0.2, {opacity: 0.4});
		TweenLite.to(this.el.position, 0.2, {y: 105, ease: Expo.easeOut});
		TweenLite.to(this.el.scaling, 0.2, {y: 1, ease: Expo.easeOut});
	}
	
	Shadow.prototype.goFar = function() {
		if (!this.el) return;
		
		this.isDragging = false;
		TweenLite.to(this.el, 0.2, {opacity: 0.1});
		TweenLite.to(this.el.position, 0.2, {y: 205, ease: Quad.easeInOut});
		TweenLite.to(this.el.scaling, 0.2, {y: 1.2, ease: Quad.easeInOut});
	}
	
	window.Shadow = Shadow;
	
})(window)


// The main program.
// This bit brings all our parts together, the shadow, pencil and drawing mechanic
var wW,
		wH,
		pencilCanvas,
		pencilCanvasW = 1920,
		pencilCanvasH = 1080,
		sketchCanvas,
		scene,
		camera,
		renderer,
		sketch,
		sketchpad,
		shadow,
		introTl, 
		pencilPos = {x: 0, y: 0};

function init() {
	sketchpad = document.getElementById('sketchpad');
	
	// Pencil Renderer
	pencilCanvas = document.getElementById('pencil3D');
	pencilCanvas.setAttribute('width', pencilCanvasW + 'px');
  pencilCanvas.setAttribute('height', pencilCanvasH + 'px');
	
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(45, pencilCanvasW / pencilCanvasH, 0.1, 1000);
	camera.position.z = 25;
	camera.lookAt(scene.position);
	renderer = new THREE.WebGLRenderer({
    canvas: pencilCanvas,
    antialias: true,
    alpha: true
  });
	renderer.setSize(pencilCanvasW, pencilCanvasH);
  
  // Light up the pencil, otherwise it would appear boring and flat
  var light1 = new THREE.AmbientLight( 0x404040 );
  light1.intensity = 6;
  scene.add(light1);
  
  var light2 = new THREE.SpotLight( 0x404040 );
  light2.intensity = 3;
  light2.position.set(-5, 15, 10);
  light2.target.position = new THREE.Vector3(0, 0, 5);
  scene.add(light2);
  
  var light3 = new THREE.SpotLight( 0x404040 );
  light3.intensity = 0.5;
  light3.position.set(5, -15, 10);
  light3.target.position = new THREE.Vector3(0, 0, 5);
  scene.add(light3);
	
  // Instantiate the sketchpad
	sketchCanvas = document.getElementById('sketch');
	paper.setup(sketchCanvas);
	sketch = new Sketch();
	shadow = new Shadow();
	pencil.init(pencilCanvasW, pencilCanvasH, onReady);
	
	onResize();
	
  // Some intro animation (the sketchpad expands once everything is loaded)
	introTl = new TimelineLite({paused: true, delay: 2, onComplete: function() {
    // Allow the user to interact with the mouse only after the intro animation has finished
		window.addEventListener('mousedown', onMouseDown);
		window.addEventListener('mouseup', onMouseUp);
		window.addEventListener('mousemove', onMove);
		window.addEventListener('resize', onResize);
	}});
  introTl.to('#intro', 0.3, {opacity: 0});
	introTl.from('#sketchpad', 0.5, {scaleY: 0, ease: Expo.easeInOut});
	introTl.append(TweenMax.fromTo(pencilPos, 0.7, {x: wW / 2, y: wH + 300}, {x: wW * 0.7, y: wH * 0.5, roundProps: 'x,y', ease: Expo.easeOut}));
	
	render();
}

function onReady() {
	introTl.play();
}

function onResize() {
  // Make the sketchpad responsive
	wW = window.innerWidth;
	wH = window.innerHeight;
	
	var sketchpadW = (wW - 100);
	var sketchpadH = (wH - 100);
	
	sketchpad.style.width = sketchpadW + 'px';
  sketchpad.style.height = sketchpadH + 'px';
	
  // Make sure the user can't draw outside of the sketchpad bounds
	sketch.setSketchingBounds(50, 50, sketchpadW, sketchpadH);
	sketchCanvas.setAttribute('width', wW);
  sketchCanvas.setAttribute('height', wH);
  paper.view.viewSize = new paper.Size(wW, wH);
  paper.view.draw();
  
 	pencilCanvas.style.left = ((wW / 2) - (pencilCanvasW / 2)) + 'px';
  pencilCanvas.style.top = ((wH / 2) - (pencilCanvasH / 2)) + 'px';
}

function onMove(e) {
  // When the pencil is moved but NOT drawing, make the movement animate at a slightly smoother rate
	var duration = pencil.isDrawing() ? 0.05 : 0.25;
	TweenLite.to(pencilPos, duration, {x: e.clientX, y: e.clientY});
}

function onMouseDown(e) {
	pencil.startDrawing();
	shadow.goNear();
}

function onMouseUp(e) {
	pencil.stopDrawing();
	shadow.goFar();
}

function render() {
	requestAnimationFrame(render);
	renderer.render(scene, camera);
	pencil.move(pencilPos.x, pencilPos.y);
	shadow.move(pencilPos.x, pencilPos.y);
	paper.view.draw();
}

init();

JavaScript Explanation:

  1. const canvas = document.getElementById('pencilCanvas'); – Selects the canvas element by ID.
  2. const ctx = canvas.getContext('2d'); – Gets the 2D drawing context to allow for drawing commands.
  3. canvas.width = 600; – Sets the width of the canvas in pixels.
  4. canvas.height = 400; – Sets the height of the canvas in pixels.

Pencil Position and Speed Variables: 5. let x = 50; – Initializes the starting x-position of the pencil. 6. let y = 50; – Initializes the starting y-position of the pencil. 7. const speedX = 2; – Sets the horizontal speed of the pencil. 8. const speedY = 1.5; – Sets the vertical speed of the pencil.

drawPencil Function: 9. function drawPencil() { – Begins the function to draw the pencil. 10. ctx.clearRect(0, 0, canvas.width, canvas.height); – Clears the canvas each frame to avoid overlapping drawings. 11. ctx.beginPath(); – Starts a new drawing path for the line. 12. ctx.moveTo(50, 50); – Sets the line’s starting point. 13. ctx.lineTo(x, y); – Draws the line to the current (x, y) position. 14. ctx.strokeStyle = '#000'; – Sets the line color to black. 15. ctx.lineWidth = 2; – Sets the line thickness. 16. ctx.stroke(); – Renders the line on the canvas.

Draw Pencil Tip: 17. ctx.beginPath(); – Starts a new path for the pencil tip. 18. ctx.arc(x, y, 5, 0, Math.PI * 2, false); – Draws a circle at (x, y) for the pencil tip. 19. ctx.fillStyle = '#333'; – Fills the circle with dark gray color. 20. ctx.fill(); – Fills in the color for the pencil tip circle.

Position Update: 21. x += speedX; – Moves the pencil to the right. 22. y += speedY; – Moves the pencil downward. 23. if (x > canvas.width || y > canvas.height) { – Checks if the pencil moves off the canvas. 24. x = 50; – Resets x position if the pencil goes off-screen. 25. y = 50; – Resets y position if the pencil goes off-screen.

Animation Loop: 26. function animate() { – Begins the animation function. 27. drawPencil(); – Calls drawPencil to draw a frame. 28. requestAnimationFrame(animate); – Recursively calls animate to create an animation loop. 29. animate(); – Starts the animation by calling animate.

Live Preview the hosted website:

Download complete code :

3D website

Git Hub link :

3d website

Read our previous blog post :

Animated CSS gallery with smooth view transitions for interactive, responsive design.
Transform Your Website with Stunning CSS Gallery Animations! 🌟

Leave a Comment