DEV Community

Jain Wun
Jain Wun

Posted on

2 2

純JS實作照片上傳、下載與預覽

更多會員限定文章可以到patreon觀看


完整code可以到以下gist

Client端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">
    <title>Document</title>
    <style>
        img {
            width: 150px;
          }
    </style>
</head>
<body>
    <input type="file" id="file-uploader" accept="image/png" multiple="multiple"/>
    <img id='preview'> 
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
  • input tag
    • type設成file,讓使用者能選擇檔案
    • accept設定使用者能選的格式,如果要所有照片格式就改成image/*
    • multiple讓使用者能選擇多個檔案

Client端JS

我們會用JSON來傳遞資料(也可以用form-data)

取得使用者選擇的照片

讀進來的每個檔案會是blob(Binary Large Object)

const fileUploader = document.querySelector('#file-uploader');
fileUploader.addEventListener('change', (e) => {
   console.log(e.target.files); // get list of file objects
});
Enter fullscreen mode Exit fullscreen mode

e.target.files是fileList

將照片轉成base64 string

function display_img(curFiles) {
    const curFile = curFiles[0];
    const reader = new FileReader();
    // 這會在readAS後才執行
    reader.onload = function (e) {
        // console.log('file:', e.target.result); // base64
        document.querySelector('#preview').src = e.target.result;
    };

    // to data url
    reader.readAsDataURL(curFile);
}
Enter fullscreen mode Exit fullscreen mode

img.src能顯示url with base64

將blob轉成array buffer

相較於buffer, blob保留了照片的資訊,像是檔名、MIME type等

function blob2buffer(blob) {
    return new Promise((resolve, reject) => {
        var arrayBuffer;
        var fileReader = new FileReader();
        fileReader.onload = function (event) {
            arrayBuffer = event.target.result;
            resolve(arrayBuffer);
        };

        fileReader.readAsArrayBuffer(blob);
        return arrayBuffer;
    });
}
Enter fullscreen mode Exit fullscreen mode

先將blob轉成buffer,方便後續建成array

將array轉成字串post到server

buffer不能直接操作,要先轉成指定type的array

arrayBuffer = await blob2buffer(pic);

fetch(url, {
    method: 'POST',
    // flask need to set header of json
    headers: {
        'content-type': 'application/json'
    },
    body: JSON.stringify({
        item_id: 1,
        format: 'png',
        img: Array.from(new Uint8Array(arrayBuffer)),
    }),
})
Enter fullscreen mode Exit fullscreen mode

轉成array前要先將buffer轉成typed array

讀取server端傳回來的array

產生blob的url (不同於前面產base64的fileReader)

function display_arr_img(arr) {
    document.querySelector('#preview').src = URL.createObjectURL(new Blob([new Uint8Array(arr)], {type: "image/png"}));
}
Enter fullscreen mode Exit fullscreen mode

將array轉回typed array,再將其變回blob


Server端API

這裡我們用python + flask作為例子

DataBase

這裡我們用MySQL,然後照片的欄位type是blob,要注意blob有分可以存的size

  • TINYBLOB: maximum length of 255 bytes
  • BLOB: maximum length of 65,535 bytes => 64kb
  • MEDIUMBLOB: maximum length of 16,777,215 bytes
  • LONGBLOB: maximum length of 4,294,967,295 bytes

連接到資料庫

import pymysql

connection = pymysql.connect(host='localhost', user='root', password='password', db='db01', charset='', cursorclass=pymysql.cursors.DictCursor)
Enter fullscreen mode Exit fullscreen mode

注意pymysql不支援connection string

Python Code

所需套件在requirements.txt內

存到blob的好處是不會讓照片被額外index (會導致檔案大小增幅30%)

@app.route('/upload/json/img_raw', methods=['POST'])
def raw_img():
    json_data = flask.request.json

    # save to db
    with connection.cursor() as cursor:
        sql = "UPDATE table1 SET `img_raw`=%s WHERE id=%s"
        try:
            # img現在是list, 可以用json string/ byterarray/ base64存進資料庫
            cursor.execute(sql, (json.dumps(json_data['img']), json_data['item_id']))
            connection.commit()
        except Exception as err:
            connection.rollback()
            return flask.jsonify({'err': err})

        # get a picture
        sql = "SELECT * FROM table1"
        cursor.execute(sql)
        result = cursor.fetchone()
        r = result[0]['img_raw']
        r = json.loads(r.decode())  # utf-8, 也可用ascii

    return flask.jsonify({'img': r})
Enter fullscreen mode Exit fullscreen mode

存進mysql blob欄位後,會變成binary object

  • json.dumps(json_data['img']
    • 我們要將List轉成json style的string才能存進資料庫
  • json.loads(r.decode())
    • 存資料庫拿出來的會是binary object,我們要先將其轉回字串
    • 然後再將字串 parse回list

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay