[번역] HTML, CSS, JS, NodeJS로 온라인 이미지-PDF 변환기를 만드는 방법

Sonny·2023년 9월 21일
20

Article

목록 보기
15/22
post-thumbnail

원문 : https://www.freecodecamp.org/news/build-an-online-image-to-pdf-converter-with-html-css-js-nodejs/

온라인 이미지-PDF 변환기는 이미지를 PDF로 변환할 수 있도록 도와주는 웹 사이트입니다. 이 도구는 이미지를 효율적으로 저장할 수 있는 방법을 제공하므로 유용합니다.

이 튜토리얼에서는 HTML, CSS, 자바스크립트 및 NodeJS를 사용하여 온라인 이미지-PDF 변환기를 만드는 방법을 알아봅니다.

목차

전제 조건

이 프로젝트를 시작하기 전에 아래의 언어/라이브러리/프레임워크가 설치되어 있거나 알고 있어야 합니다.

  • HTML, CSS, 자바스크립트: 이 튜토리얼을 따라 하려면 HTML, CSS 및 자바스크립트 사용에 대한 기본 지식이 있어야 합니다. 이러한 파일을 생성하고 서로 연결하는 방법을 알아야 하며 기본 HTML 요소, CSS 선택자, 자바스크립트 개념, DOM에 대해서도 알아야 합니다.
  • NodeJS와 npm: 프로젝트에 필요한 패키지를 설치하는 데 사용할 것이므로 npm과 Node.js가 설치되어 있어야 합니다. 특히 Node.js 내장 모듈을 임포트하고 사용하는 방법에 대한 기본 지식이 있어야 합니다.
  • Nodemon: Nodemon은 프로젝트를 더 빠르게 개발하는 데 도움이 되는 중요한 노드 패키지입니다. 프로젝트에 변경사항이 생겼을 때 서버를 재시작하는 데 도움이 됩니다.
  • Express.js 및 express-generator: Express.js는 웹 서버를 구축하는 데 사용할 노드 프레임워크입니다. Express-generator는 Express.js를 효율적으로 실행하는 데 필요한 파일과 폴더를 생성하는 데 도움이 되는 라이브러리입니다. 기본적인 Express 애플리케이션을 만드는 방법을 알고 있어야 합니다.
  • Express-session: 애플리케이션 세션을 관리하는 데 도움이 되는 Express 미들웨어 라이브러리입니다. 이 라이브러리의 설정 방법에 대해 알아야 합니다.
  • Jade/Pug: 업로드한 이미지의 주소를 HTML 파일로 렌더링하는 데 도움이 되는 자바스크립트 템플릿 엔진입니다. 이 라이브러리의 기본 개념을 알고 있어야 합니다.
  • PDFKit: 이미지-PDF 데 사용할 자바스크립트 라이브러리입니다.
  • Multer: 파일 업로드를 처리하는 노드 라이브러리입니다.
  • Sortable.js: 이미지를 PDF로 변환하기 전에 이미지를 재정렬하기 위해 프론트엔드에서 사용할 자바스크립트 드래그 앤 드롭 라이브러리입니다.

마지막으로, 폴더와 적절한 확장자를 가진 파일을 만드는 방법을 알아야 합니다. 에디터로 이러한 파일을 편집하는 방법을 알고 있어야 합니다.

프로젝트 설정

먼저 폴더를 생성하고 CLI를 통해 해당 폴더로 이동해야 합니다.

mkdir img2pdf
cd img2pdf

다음으로, 필요한 모든 라이브러리를 설치할 수 있도록 npm 패키지로 초기화합니다.

npm init -y

모든 것이 순조롭게 진행되면 폴더에 package.json 파일이 생성됩니다.

package.json은 다음과 같이 표시되어야 합니다.

{
  "name": "img2pdf",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

다음으로 express-generator를 설치합니다.

npx express-generator
npm install

기본 템플릿 엔진은 jade를 사용할 것입니다.

위의 명령어는 Express.js가 효율적으로 실행되는 데 필요한 모든 필수 폴더와 라이브러리를 설정합니다.

설치가 끝나면 프로젝트 폴더는 다음과 같이 표시됩니다.

/img2pdf
  /bin
    www
  /node_modules
    /public
	  /images
	  /javascripts
	  /stylesheets
  /routes
  /views
  app.js
  package.json
  package-lock.json
  • bin/www는 애플리케이션의 진입점입니다.
  • node_modules 폴더에는 프로젝트에 필요한 패키지가 저장됩니다.
  • public 폴더에는 서버가 반환하고 저장할 정적 파일이 저장됩니다.
  • routes 폴더에는 애플리케이션의 모든 경로가 저장됩니다.
  • views 폴더에는 서버 사이드 렌더링에 사용되는 Jade 파일이 저장됩니다.
  • app.js는 루트 자바스크립트 파일입니다.

package.json 파일을 확인하면 Express.js, Jade 및 기타 라이브러리가 설치되어 있는 것을 확인할 수 있습니다.

다음으로 Nodemon 패키지를 전역에 설치합니다.

npm install -g nodemon

마지막으로 PDFkit, Multer, Sortablejs, Express-session을 설치합니다.

npm i multer pdfkit sortablejs express-session

다음으로 package.json에 devstart 명령어를 추가합니다. 이렇게 하면 자바스크립트 파일을 변경할 때 Nodemon이 애플리케이션을 다시 시작할 수 있습니다.

"devstart": "nodemon ./bin/www"

설치가 끝나면 package.json 파일은 다음과 같이 표시되어야 합니다.

{
  "name": "img2pdf",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "devstart": "nodemon ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "~4.16.1",
	"express-session": "^1.17.3",
    "http-errors": "~1.6.3",
    "jade": "~1.11.0",
    "morgan": "~1.9.1",
    "multer": "^1.4.5-lts.1",
    "pdfkit": "^0.31.0",
    "sortablejs": "^1.15.0"
  }
}

다음으로 프로젝트 폴더로 이동하여 애플리케이션 서버를 실행합니다.

cd img2pdf
set DEBUG=img2pdf:* & npm run devstart

성공 메시지가 표시되면 서버가 이미 실행 중이라는 의미입니다.

웹 브라우저를 열고 검색창에 http://localhost:3000/ 을 입력합니다.

모든 것이 순조롭게 진행되면 아래 이미지와 같은 결과가 나올 것입니다.

따라야 할 단계

코드를 작성하기 전에 이 프로젝트를 빌드하기 위해 따라야 할 단계를 살펴봅시다.

  1. 먼저 루트 URL /에 도달하면 index HTML 파일을 반환하는 경로를 정의합니다.
  2. index HTML 파일 내에서 이미지 파일(png, jpg)만 허용하는 폼을 만든 다음, 정의된 경로를 통해 서버로 전송합니다.
  3. 서버가 Multer를 통해 이미지를 받으면 폴더에 해당 이미지를 저장하고 세션 스토어에 주소를 저장합니다. 이후, 업로드된 이미지의 주소가 포함된 Jade 파일을 렌더링하는 루트 URL 경로로 요청을 리디렉션합니다.
  4. Jade 파일 내에서 사용자가 PDF로 변환하기 전에 이미지를 재정렬할 수 있도록 Sortablejs를 활성화합니다. 또한 'PDF로 변환' 버튼이 있어 정렬된 이미지의 주소를 서버의 /pdf 경로로 전송할 수 있습니다.
  5. /pdf 경로에서 이미지를 받으면 PDFkit을 사용하여 이미지를 PDF로 변환합니다. 그런 다음, 변환된 PDF의 주소를 전송합니다.
  6. 사용자가 PDF 링크를 클릭하면 파일이 사용자의 기기로 다운로드됩니다.

루트 URL 경로를 만드는 방법

먼저 루트 URL(/)에 접근하면 index.html 파일을 전송하는 경로를 만들겠습니다.

다음은 이 작업의 간단한 순서도입니다.

먼저 routes/index.js 파일을 열고 HTML 파일을 반환하는 경로를 만듭니다.

routes/index.js 파일의 모든 코드를 다음과 같이 바꿉니다.

var express = require('express');
var router = express.Router();

var path = require('path');

// public/html 폴더에 저장된 index.html 파일을 반환하는 '/' GET 경로를 만듭니다.
router.get('/', function(req, res, next) {
  res.sendFile(path.join(__dirname, '..','/public/html/index.html'));
});

module.exports = router;

위의 코드에서 express 라이브러리를 불러오고 express.Router() 함수를 활성화했습니다. 파일 경로를 설명하는 데 사용되는 path 모듈도 불러왔습니다.

그런 다음, 루트 URL / 경로의 모든 GET 요청을 처리하는 route 메서드를 정의했습니다. route 메서드가 요청을 수신할 때마다 res.sendFile() 메서드를 사용하여 index.html 파일을 사용자에게 다시 보냅니다.

res.sendFile(...) 메서드 내에서 사용되는 __dirname 변수와 path.join() 메서드는 index.html 파일의 주소를 정확하게 지정하는 데 도움이 됩니다.

이어서 public 폴더 내에 html 폴더를 만들고 index.html 페이지를 추가합니다.

public 폴더는 다음과 같이 표시되어야 합니다.

/public
  /html
  /images
  /javascripts
  /stylesheets

그런 다음, public/html 폴더에 index.html 파일을 만들고 아래 코드를 추가합니다.

<DOCTYPE HTML>
<html>
  <head>
    <title>IMG-to-PDF Converter</title>
	<meta charset="UTF-8">
	<meta name="author" content="YOUR NAME">
	<meta name="description" content="Easily convert any set of images to PDFs">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
  </head>
  <body>
	<h1>Image to PDF converter</h1>
  </body>
</html>

서버가 아직 실행 중이라면 웹 브라우저에서 https://localhost:3000/으로 이동하세요.

결과는 다음과 같아야 합니다.

서버를 실행하지 않았다면 프로젝트의 디렉토리(cd img2pdf)로 이동하여 다음 명령어를 실행합니다.

set DEBUG=img2pdf:* & npm run devstart

서버에 이미지를 업로드하는 방법

이 섹션에서는 서버에 이미지를 업로드하는 방법을 설명하겠습니다.

다음은 이 작업에 대한 간단한 순서도입니다.

다음과 같이 진행됩니다.

  • 사용자가 서버로 이미지를 전송합니다.
  • 서버는 이미지를 수신하여 이름을 변경하고 폴더에 저장합니다.
  • 서버는 이미지 파일명을 추출하여 세션에 저장하고 요청을 루트 URL로 리디렉션합니다.
  • 루트 URL 경로에서 요청을 수신하고 이미지 파일명이 포함된 Jade/pug 파일을 렌더링합니다.
  • 종료됩니다.

서버가 계속 실행되는 동안, public/html/index.html 파일의 내용을 다음과 같이 바꿉니다.

<DOCTYPE HTML>
<html>
  <head>
	<title>IMG-to-PDF Converter</title>
	<meta charset="UTF-8">
	<meta name="author" content="Gideon Akinsanmi">
	<meta name="description" content="Easily convert any set of images to PDFs">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel='stylesheet' href='/stylesheets/style.css' />
	<link rel='stylesheet' href='/stylesheets/index.css' />
  </head>
  <body>
	<main>
	  <header>
		<h1><a href='/'>IMG2PDF</a></h1>
	  </header>
	  <article>
		<p class='title'>Easily convert your PNG and JPG images </p>
		<form method='post' action='/upload' enctype='multipart/form-data'>
		  <input id='file-upload' type='file' name='images' accept='.png, .jpg' multiple/>
		  <p><label for='file-upload'>Select files</label></p>
		  <p id='selected-files'><code> </code> </p>
		  <p><input type='submit' value='upload' /></p>
		</form>
	  </article>
	  <footer>
		<p><code>copyright &copy; IMG2PDF 2023</code></p>
	  </footer>
	  <script>
		let fileUpload = document.getElementById('file-upload');
		let selectedFiles = document.querySelector('#selected-files code');
		let submitButton = document.querySelector('input[type=submit]');

		let filenames = ''
		fileUpload.onchange = function (){
			filenames = ''
			for(let file of this.files){
				filenames += file.name
				filenames += ','
			}
			selectedFiles.parentElement.style.display = 'block'
			selectedFiles.textContent = filenames
			submitButton.style.display = 'inline-block';
		}
	  </script>
	</main>
  </body>
</html>

위 코드에서는 일부 CSS 파일(styles.cssindex.css)을 문서에 불러왔습니다. 또한 POST 요청을 /upload 경로로 전송하는 form 요소도 만들었습니다. form 요소의 enctypemultipart/form-data입니다(이 속성이 없으면 서버에서 이미지를 수신할 수 없습니다).

form 요소 안에는 기기에서 파일을 가져오는 데 도움이 되는 input 요소가 있습니다. input 요소에는 images 값이 포함된 name 속성이 있습니다(이 속성은 multer에서 이미지를 식별하는 데 사용됩니다). 이 요소는 .png 또는 .jpg 확장자와 같은 여러 이미지 파일을 허용하도록 구성되었습니다.

제출 버튼 역할을 하는 input 요소도 있습니다. 이 요소는 파일 업로드 요청을 트리거하는 데 사용됩니다.

그런 다음, HTML 문서에 상호 작용을 추가하는 일부 자바스크립트 코드가 포함된 script 요소가 있습니다.

public/stylesheets 폴더에 style.css 파일을 생성하고 이 CSS 코드를 추가합니다.

* {margin: 0;padding: 0;box-sizing: border-box;font-family:Poppins;transition: all 0.5s ease;}
main {display: flex;flex-direction:column;height:100vh;}

h1 a {color: #ff6600;text-decoration:none;}
h1 {background-color: white;font-size:25pt;padding:5px;text-align: center;}

p {font-size:20pt;text-align: center;margin-bottom: 25px;}

header, footer {border: 2px solid #ececec;}

article {padding:20px;flex-grow: 1;background-color: #fff7f0;}
footer p {margin-bottom: 0;font-size: 16pt;}

@media screen and (max-width: 380px) {
	footer p {font-size: 12pt;}
}

@media screen and (max-width: 300px) {
	h1 {font-size: 17pt;}
}

위의 CSS 코드는 웹사이트의 전체 레이아웃을 정의합니다.

이후, public/stylesheets 폴더에 index.css 파일을 만들고 아래 코드를 추가합니다.

p.title {font-size:30pt;}
p#selected-files {display:none;white-space: nowrap;overflow:hidden;text-overflow: ellipsis;}

label {display: inline-block;font-size:25pt;cursor:pointer;padding: 5px 45px;border-radius:25px;color:white;background-color: #ff9955;}
label:hover {background-color: #ff6600;}

input[type=file] {display: none;}
input[type=submit] {display: none;font-size:16pt;cursor:pointer;padding: 5px 25px;border-radius:25px;color:white;background-color:black;}

@media screen and (max-width: 380px) {
	p.title {font-size: 25pt;}
}

@media screen and (max-width: 300px) {
	p.title {font-size: 22pt;}
	label {font-size: 20pt;}
}

@media screen and (max-width: 250px) {
	label {padding: 5px 35px;}
}

위의 CSS 코드는 index.html 요소의 특정 스타일을 정의합니다.

다음으로, routes/index.js 파일을 열어 이미지 파일을 수신하고, 폴더에 저장하고, 세션 스토어에 파일명 저장하고, 요청을 루트 URL로 리디렉션하는 /upload 경로를 생성합니다.

먼저 app.js 파일을 수정하고 express-session을 활성화해야 합니다.

app.js 파일에 다음 코드를 추가하세요.

// express-session 모듈을 불러옵니다.
var session = require('express-session');

// express.js 미들웨어를 활성화합니다.
app.use( session({secret: 'YOUR_SECRET'}) )

app.js 파일은 다음과 같이 표시되어야 합니다.

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var logger = require('morgan');
var session = require('express-session');

var indexRouter = require('./routes/index');


var app = express();

// view engine 설정
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
// express-session 활성화
app.use( session({secret: 'YOUR_SECRET'}) )

app.use('/', indexRouter);


// 404를 캐치하여 에러 핸들러에 전달
app.use(function(req, res, next) {
  next(createError(404));
});

// 에러 핸들러
app.use(function(err, req, res, next) {
  // 개발 모드의 오류만 제공하도록 로컬 설정
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // 오류가 발생하면 오류 페이지를 렌더링
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

routes/index.js 파일을 열어 이미지 파일명을 수신할 경로를 생성하고 해당 이미지 파일명을 세션에 저장한 뒤 루트 URL로 리디렉션합니다.

// import the multer library
var multer = require('multer');
var path = require('path');

// multer 파일 스토리지 구성
let storage = multer.diskStorage({
	// public/images폴더에 이미지를 저장합니다.
	destination: function(req, file, cb){
		cb(null, 'public/images')
	},
	// 이미지 이름 변경
	filename: function(req, file, cb){
		cb(null, file.fieldname + '-' + Date.now() + '.' + file.mimetype.split('/')[1] )
	}
})

// 파일 필터링 구성
let fileFilter = (req, file, callback) => {
	let ext = path.extname(file.originalname);
	// 파일 확장자가 '.png' 또는 '.jpg'가 아닌 경우 오류 페이지를 반환하고, 그렇지 않으면 true를 반환합니다.
	if (ext !== '.png' && ext !== '.jpg'){
		return callback(new Error('Only png and jpg files are accepted'))
	} else {
		return callback(null, true)
	}
}

// 스토리지 및 파일 필터링 구성을 넣어 Multer를 초기화합니다.
var upload = multer({ storage, fileFilter: fileFilter});

router.post('/upload', upload.array('images'), function (req, res){
	let files = req.files;
	let imgNames = [];

	// 파일명 추출
	for( i of files){
		let index = Object.keys(i).findIndex( function (e){return e === 'filename'})
		imgNames.push( Object.values(i)[index] )
	}
	// 세션에 이미지 파일 이름 저장
	req.session.imagefiles = imgNames

    // 요청을 루트 URL 경로로 리디렉션
	res.redirect('/')
})

위의 코드에서 Multer 라이브러리를 불러왔습니다. 그런 다음 multer.diskStorage() 메서드를 사용하여 이미지 파일을 저장할 위치와 이름을 어떻게 변경해야 하는지 설명했습니다.

또한 서버에 png와 jpg 파일만 저장되도록 fileFilter 함수를 만들었습니다. 사용자가 PNG 또는 JPG가 아닌 다른 파일을 보내면 "Only png and jpg files are accepted"라는 메시지와 함께 오류 페이지가 표시됩니다.

다음으로, /upload 경로로 POST 요청을 수신하는 route 메서드를 만들었습니다.

이 route 메서드에는 이름이 이미지인 파일만 저장하도록 multer에 지시하는 upload.array('images') 메서드를 포함시켰습니다(HTML input 요소 <input id='file-upload' type='file' name='images' accept='.png, .jpg' multiple/>에 의해 포함되었습니다).

그런 다음, req.files 속성에서 파일명을 추출하여 세션 스토어에 저장했습니다. 마지막으로 요청을 루트 URL 경로로 리디렉션합니다.

routes/index.js파일에서 세션에 이미지 파일명을 저장하는 경우, index.jade 파일을 렌더링하도록 루트 URL 경로를 편집합니다.

router.get('/', function(req, res, next) {
	// 세션에 imagefiles가 없는 경우, 일반 HTML 페이지를 반환합니다.
	if (req.session.imagefiles === undefined){
		res.sendFile(path.join(__dirname, '..','/public/html/index.html'));
	} else {
	// 세션에 저장된 imagefiles가 있는 경우, index.jade 파일에 렌더링합니다.
		res.render('index', {images: req.session.imagefiles} )
	}
});

위의 코드를 보면, URL 경로가 요청을 받으면 먼저 세션 스토어에 imagefiles 속성이 있는지 확인합니다. 없는 경우, index.html 파일을 전송합니다. 있는 경우, 이미지 파일명이 포함된 index.jade 파일이 전송됩니다.

마지막으로, views/index.jade 파일로 이동하여 업로드된 이미지를 HTML로 렌더링하도록 편집합니다.

doctype html
html
  head
    title IMG-to-PDF Converter
    meta(charset='UTF-8')
  body
    h1 Images
    each image in images
      img(src=`/images/${image}` width='200' height='200')

위의 코드는 Jade 구문으로 작성되었으며, 서버에서 렌더링할 때 HTML 문서 형태로 표시됩니다.

참고 사항: Jade/Pug에서 들여쓰기는 스페이스 키 또는 탭 키 중 하나를 사용해야 하며, 둘이 같이 사용되지 않아야 합니다.

rs 혹은 Nodemon으로 서버를 실행하거나 재시작할 때, http://localhost:3000/ 으로 이동하여 업로드 프로세스를 다시 시작합니다(파일 선택부터 업로드 버튼 클릭까지).

로드한 이미지가 포함된 HTML 페이지가 표시됩니다.

결과는 다음과 같습니다.

이미지를 정렬하고 PDF로 변환하는 방법

이 섹션에서는 사용자가 이미지를 재정렬하고 PDF로 변환할 수 있도록 허용하는 방법에 대해 설명하겠습니다.

다음은 이 작업에 대한 간단한 순서도입니다.

다음과 같이 진행됩니다.

  • 사용자가 "convert to PDF"를 클릭합니다.
  • 브라우저가 정렬된 이미지 파일명이 포함된 요청을 서버로 전송합니다.
  • 서버는 요청을 수신하고 이미지를 PDF로 변환한 후, 브라우저로 주소를 다시 보냅니다.
  • 브라우저는 주소를 수신하여 사용자에게 표시합니다.
  • 사용자가 링크를 클릭하고 PDF를 다운로드합니다.
  • 종료됩니다.

views/index.jade 파일에서 Sortablejs를 사용하여 이미지를 정렬할 것입니다.

index.jade 파일의 내용을 다음과 같이 바꿉니다.

doctype html
html
  head
    title IMG-to-PDF Converter
    meta(charset='UTF-8')
    link(rel='stylesheet' href='/stylesheets/style.css')
    link(rel='stylesheet' href='/stylesheets/index-view.css')
    script(type='module' src='/javascripts/sort.js' defer)
  body
    main
      header
        h1
          a(href='/') IMG2PDF
      article
        p
          a(href='/new') New +
        div
          each image in images
           img(src=`/images/${image}` data-name=image width='200' height='200')
        p
          a(class='convert')
            span(class='text') Convert to PDF &rarr;
            span(class='loader')
        p
          a(class='download' download) Download &darr;
      footer
        p
          code copyright &copy; IMG2PDF 2023

위의 코드는 HTML 문서로 렌더링되는 Jade 파일입니다. 여기에는 CSS에 연결되는 linkstyle 요소와 자바스크립트 파일과 같은 문서 메타데이터와 이미지 요소, 하이퍼링크 및 HTML 요소가 포함되어 있습니다.

이어서 public/stylesheets/index-view.css 파일을 만들고 아래 코드를 추가합니다.

p a {font-size:20pt;text-decoration: none;color: white;display: inline-block;padding: 5px 20px;margin-bottom:15px;background-color: #ff6600;cursor: pointer;}

div {margin:auto;margin-bottom: 15px;padding:10px;display: flex;flex-direction:row;flex-wrap:wrap;width:80%;background-color: rgba(255,255,255,0.9);}

div img {width: 200px;height:200px;object-fit: contain;padding: 10px;background-color: #ffe6d5;margin-right:10px;margin-bottom: 10px;cursor:pointer;}

p a.download {background-color: black;display:none;}

span.loader {display:none;border: 5px solid black;border-top: 5px solid white;border-radius:50%;width: 25px;height: 25px;animation: spin 0.5s linear infinite;}


@keyframes spin{
	0% {transform: rotate(0deg);}
	100% {transform: rotate(360deg);}
}

@media screen and (max-width: 620px) {
	div img {width: 45%;height: 150px;}
}

@media screen and (max-width: 415px) {
	p a {font-size: 16pt;}
	div img {height: 120px;}
}

@media screen and (max-width: 330px) {
	p a {font-size: 14pt;}
	div {width: 100%;}
	div img {width:100%;margin-right: 0px;margin-bottom: 5px;}
}

@media screen and (max-width: 260px) {
	p a {font-size: 13pt;padding: 5px 10px;}
}

그런 다음, node_modules/sortablejs/modular 폴더로 이동하여 sortable.core.esm.js 파일을 public/javascripts 폴더에 복사합니다.

이어서 public/javascripts/sort.js 파일을 만듭니다.

Sortablejs를 활성화하고 변환을 위해 정렬된 파일명을 서버로 전송하는 코드를 추가합니다.

import Sortable from '/javascripts/sortable.core.esm.js';

// 이미지 태그의 컨테이너 요소에 sortablejs 사용
let list = document.querySelector('div');
let sort = Sortable.create(list);

let convertButton = document.querySelector('a.convert');

// convert 버튼을 클릭한 경우
convertButton.onclick = function(){
	let images = document.querySelectorAll('img');
	let loader = document.querySelector('span.loader');
	let convertText = document.querySelector('span.text');
	let downloadButton = document.querySelector('a.download');

	let filenames = [];
	// 이미지명을 배열로 추출
	for(let image of images){
		filenames.push(image.dataset.name)
	}
	// 로딩 애니메이션 활성화
	loader.style.display = 'inline-block';
	convertText.style.display = 'none'

	// 이미지 파일명을 '/pdf' 경로로 전송하고 PDF 파일 링크를 수신하는 POST 요청을 작성합니다.
	fetch('/pdf', {
		method: 'POST',
		headers: {
			"Content-Type": "application/json"
		},
		body: JSON.stringify(filenames)
	})
	.then( (resp)=> {
		return resp.text()
	})
	.then( (data) => {
    // 로딩애니메이션 비활성화
		loader.style.display = 'none';

    // convert 및 download 버튼 표시
		convertText.style.display = 'inline-block'
		downloadButton.style.display = 'inline-block'

    // download 버튼에 주소 첨부
		downloadButton.href = data
	})
	.catch( (error) => {
		console.error(error.message)
	})
}

위의 코드에서 Sortablejs 코어 자바스크립트 파일을 가져와서 이미지 요소의 부모 요소에서 활성화했습니다.

Convert to PDF 버튼을 클릭하면 이미지 요소에서 이미지 파일명을 추출하여 서버에 POST 요청으로 전송합니다.

브라우저가 서버 응답을 수신하면 다운로드 버튼에 링크를 첨부하여 클릭시, 사용자의 기기에 PDF 문서가 다운로드되도록 했습니다.

다음으로, routes/index.js 파일에서 정렬된 파일명을 수신하여 PDF로 변환하는 /pdf 경로를 생성합니다.

먼저 public 폴더에 pdf 폴더를 생성하고 routes/index.js 파일에 아래 코드를 추가합니다.

var path = require('path');
var fs = require('fs');

// PDFkit를 불러옵니다.
var PDFDocument = require('pdfkit');

router.post('/pdf', function(req, res, next) {
	let body = req.body

	// 새 pdf 생성
	let doc = new PDFDocument({size: 'A4', autoFirstPage: false});
	let pdfName = 'pdf-' + Date.now() + '.pdf';

	// pdf를 public/pdf 폴더에 저장
	doc.pipe( fs.createWriteStream( path.join(__dirname, '..',`/public/pdf/${pdfName}` ) ) );

	// pdf 페이지를 만들고 이미지를 추가
	for(let name of body){
		doc.addPage()
		doc.image(path.join(__dirname, '..',`/public/images/${name}`),20, 20, {width: 555.28, align: 'center', valign: 'center'} )
	}
	// 프로세스 종료
	doc.end();

  // 브라우저로 주소를 다시 전송
	res.send(`/pdf/${pdfName}`)
})

위의 코드에서 pdfkit을 불러오고 /pdf URL 경로의 POST 요청에 응답하는 route 메서드를 만들었습니다. 이 route 메서드 내에서 pdfkit을 사용하여 모든 이미지를 PDF로 변환했습니다. 마지막으로 PDF 문서의 주소를 문서로 다시 보냈습니다.

서버를 다시 시작하면 웹 브라우저에서 http://localhost:3000/ 으로 이동하여 파일 업로드 프로세스를 다시 시작합니다. 이미지를 PDF로 변환할 수 있습니다.

전체 과정을 설명하는 유튜브 동영상입니다.

이미지 업로드를 취소하고 다시 시작하는 방법

이 섹션에서는 사용자가 기존의 업로드된 프로젝트를 취소하고 새 프로젝트를 만들 수 있는 기능을 만들겠습니다.

views/index.jade/new 경로를 가리키는 New + 버튼이 있다는 것을 기억하세요.

...
article
  p
    a(href='/new') New +
...

이제 routes/index.js 파일에 경로를 작성하겠습니다.

다음은 이 작업에 대한 간단한 순서도입니다.

다음과 같이 진행됩니다.

  • 사용자가 'New +' 버튼을 클릭합니다.
  • 브라우저가 서버에 GET 요청을 보냅니다.
  • 서버가 이미지를 삭제하고 세션 스토어에서 해당 파일명을 지웁니다.
  • 서버가 요청을 루트 URL로 리디렉션합니다.
  • 종료됩니다.

routes/index.js 파일에 다음 코드를 추가하세요.

var { unlink } = require('fs/promises');

router.get('/new', function(req, res, next) {
	// 세션에 저장된 파일을 삭제합니다.
	let filenames = req.session.imagefiles;

	let deleteFiles = async (paths) => {
		let deleting = paths.map( (file) => unlink(path.join(__dirname, '..', `/public/images/${file}`) ) )
		await Promise.all(deleting)
	}
	deleteFiles(filenames)

	// 세션에서 데이터를 제거합니다.
	req.session.imagefiles = undefined

	// 루트 URL로 리디렉션합니다.
	res.redirect('/')
})

위의 코드에서 /new 경로가 GET 요청을 받으면 폴더와 세션 스토어에 저장된 이미지를 삭제합니다. 그런 다음 요청을 루트 URL로 리디렉션합니다.

http://localhost:3000/로 이동하여 이미지를 업로드하고 기존 파일 업로드 프로세스를 취소하면 폴더에서 이미지가 삭제되고 홈페이지로 리디렉션되는 것을 볼 수 있습니다.

결론

위의 코드를 사용하여 온라인 이미지-PDF 변환기를 만들 수 있었습니다. 축하합니다!

아시다시피 소프트웨어 개발은 지속적인 과정입니다. 따라서 프로젝트에 몇 가지 추가 기능(예: 보안, 입력 유효성 검사 등)을 구현해 볼 수 있습니다.

전체 소스 코드는 제 깃허브 저장소에서 확인할 수 있습니다.

궁금한 점이 있으시면 freeCodeCamp 프로필에서 제 연락처 정보를 확인하실 수 있습니다. 최대한 빨리 답변해 드리겠습니다!

이만 마무리하겠습니다.

profile
FrontEnd Developer

1개의 댓글

comment-user-thumbnail
2023년 9월 23일

당신 최고야.

답글 달기