纯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>
  • 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
});

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);
}

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;
    });
}

先将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)),
    }),
})

转成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"}));
}

将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)

注意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})

存进mysql blob栏位後,会变成binary object

  • json.dumps(json_data['img']
    • 我们要将List转成json style的string才能存进资料库
  • json.loads(r.decode())
    • 存资料库拿出来的会是binary object,我们要先将其转回字串
    • 然後再将字串 parse回list

<<:  AWS Academy 简介

>>:  JS [笔记] debounce、throttle

使用Fortigate的DoS功能

近期有许多DDoS的资安事件,如下: 国内多家主机托管商遭疑似来自本土之 DVR 僵屍网路 DDoS...

EP21 - 持续部署使用 Octopus Deploy 首部曲,建置 Octopus 基础设施

在第十天的时候, 我们使用 AWS CodeDeploy 部署到 EC2, 当时只有阳春版的部署, ...

【rails】串接Google第三方登入失败,Authentication failure! authenticity_error

此时此刻看着这篇文章的你,是否也遇到下图的错误讯息,反覆检查程序码,也确实参照官方文件1、官方文件2...

那些被忽略但很好用的 Web API / Geolocation

我的字典里没有放弃,因为已锁定你 现在有不少网站都有地图相关的功能,而为了解决地图绘制、路线运算、...

grep - 3 Regex搭配浅谈

grep回顾 grep简介 grep - 2 用更多Option Regexp grep 可以搭配R...