ajax+php上传图片,等比压缩,canvas压缩减少上传带宽,优化上传速度

in web技术 with 0 comment 访问: 529 次

后端处理上传文件并等比压缩

./upload.php

<?php

class UploadImageServer
{
    /**
     * 默认上传根目录
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var string
     */
    private $uploadPath = './upload';

    /**
     * 默认最大宽度,计算缩略比使用
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var int
     */
    private $maxWidth = 1920;

    /**
     * 当前上传文件的实际宽度,方便瀑布流的图片预加载
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var int
     */
    private $imgWidth = 0;

    /**
     * 当前上传文件的实际高度,方便瀑布流的图片预加载
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var int
     */
    private $imgHeight = 0;

    /**
     * 当前上传文件的后缀名(类型)
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var string
     */
    private $imageExtension = 'jpeg';

    /**
     * 默认缩略比
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var int
     */
    private $percent = 1;

    /**
     * 错误信息
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var string
     */
    private $errorMsg = '';

    /**
     * 限制上传文件的最大Size
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var int
     */
    public $limitSize = 10485760;

    /**
     * 限制上传文件的后缀类型
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var array
     */
    public $limitExtension = ['gif','jpg','jpeg','bmp','png', 'PNG', 'JPG', 'JPEG', 'GIF', 'BMP'];

    /**
     * 限制上传文件的宽度
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var int
     */
    public $limitWeight = 1000;

    /**
     * 限制上传文件的高度
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @var int
     */
    public $limitHeight = 500;

    /**
     * 上传操作
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     * @return array
     */
    public function uploadImg()
    {
        if (array_key_exists('post_image', $_FILES)) {
            $file = $_FILES['post_image'];

            // 检查上传文件格式
            if ($this->checkUploadImg($file)) {

                // 本地存储路径
                $destFileName = $this->uploadPath . '/image/' . $this->code('image-' . time() . '-') . '.' . $this->imageExtension;

                // 本地等比例压缩
                if ($this->saveImg($file['tmp_name'], $destFileName)) {

                    /*
                    // 上传图片到文件系统(七牛、cdn等),可选
                    try {
                    } catch (\Exception $e) {
                        return ['code' => '-1', 'message' => '上传文件系统出错!'];
                    }

                    // 删除本地的图,仅保留文件系统上的一份,可选
                    @unlink($destFileName);
                    */

                    return [
                        'code' => '0',
                        'data' => [
                            'img_url' => $destFileName,
                            'img_width' => $this->imgWidth,
                            'img_height' => $this->imgHeight
                        ]
                    ];

                } else {
                    return ['code' => '-1', 'message' => $this->errorMsg];
                }
            } else {
                return ['code' => '-1', 'message' => $this->errorMsg];
            }
        } else {
            return ['code' => '-1', 'message' => '上传文件不存在!'];
        }
    }

    /**
     * 随机6位数字
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     *
     * @param string $prefix
     *
     * @return string
     */
    private function code($prefix = '')
    {
        $code = str_pad(mt_rand(0, 999999), 6, '0', STR_PAD_BOTH);
        return $prefix . $code;
    }

    /**
     * 检查文件类型大小和合法性
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     *
     * @param $file
     *
     * @return bool
     */
    private function checkUploadImg($file)
    {
        $image = getimagesize($file['tmp_name']);
        $imageExtension = ltrim(strrchr($image['mime'], '/'), '/');

        //文件上传失败
        if ($file['error'] !== 0) {
            $this->errorMsg= '文件上传失败!';
            return false;
        }

        // 是否是真实图片文件
        if (!$image) {
            $this->errorMsg = '非法图像文件';
            return false;
        }

        // 检查文件类型
        if (!in_array($imageExtension, $this->limitExtension)) {
            $this->errorMsg = '上传文件类型不允许!';
            return false;
        }

        // 检查文件大小
        if ($file['size'] > $this->limitSize) {
            $this->errorMsg = '上传文件大小超出限制!';
            return false;
        }

        // 检查是否合法上传
        if (!is_uploaded_file($file['tmp_name'])) {
            $this->errorMsg = '非法上传文件!';
            return false;
        }

        // 图像宽高限制
        if ($image[0] < $this->limitWeight || $image[1] < $this->limitHeight) {
            $this->errorMsg = '请选择宽度大于1000px、高度大于500px的图片';
            return false;
        }

        $this->imageExtension = $imageExtension;
        return true;
    }

    /**
     * 等比压缩并保存
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     *
     * @param $file
     * @param $name
     *
     * @return mixed
     */
    private function saveImg($file, $name)
    {
        list($width, $height, $type, $attr) = getimagesize($file);
        $imageInfo = [
            'width' => $width,
            'height' => $height,
            'type' => image_type_to_extension($type, false),
            'attr' => $attr,
        ];
        $this->percent = $this->setPercent($this->maxWidth, $imageInfo['width']);
        $create = 'imagecreatefrom' . $imageInfo['type'];
        $image = $create($file);
        $newWidth = $imageInfo['width'] * $this->percent;
        $newHeight = $imageInfo['height'] * $this->percent;
        $newThump = imagecreatetruecolor($newWidth, $newHeight);
        imagecopyresampled($newThump, $image, 0, 0, 0, 0, $newWidth, $newHeight, $imageInfo['width'], $imageInfo['height']);

        // 同类型压缩
        $save = "image" . $imageInfo['type'];

        // 固定类型jpeg压缩(空间占用小)
        // $save = 'imagejpeg';

        if ($save($newThump, $name)) {
            @chmod($name, 0777);
            imagedestroy($newThump);
            imagedestroy($image);

            // 保存图片的实际高度宽度,方便前端瀑布流展示
            $this->imgWidth = $newWidth;
            $this->imgHeight = $newHeight;

            return true;
        } else {
            $this->errorMsg= '文件等比压缩失败!';
            
            return false;
        }
    }

    /**
     * 设置缩放比例
     *
     * @Author huaixiu.zhen
     * http://litblc.com
     *
     * @param $maxWidth
     * @param $width
     *
     * @return float|int
     */
    private function setPercent($maxWidth, $width)
    {
        if ($width > $maxWidth) {
            $percent = $maxWidth / $width;
        } else {
            $percent = 1;
        }

        return $percent;
    }
}

后端调用示例 ./index.php

<?php

require_once './upload.php';

$uploadImgServer = new UploadImageServer();

$result = $uploadImgServer->uploadImg();
print_r(json_encode($result, JSON_UNESCAPED_UNICODE));

至此后端已经压缩完毕,但是如果上传的图片大多是几M的大图,难免浪费上传带宽,而且会导致速度非常慢,影响用户体验,于是可以使用canvas在上传之前压缩一遍,解决速度慢的问题。

前端使用canvas压缩再上传

./index.html

<html>
<head>
    <script src="https://libs.baidu.com/jquery/1.9.0/jquery.min.js"></script>
</head>
<input type="file" class="js-upload-local">

<script>
    // 上传本地图片
    $('.js-upload-local').change(function(){
        var files = $(this)[0].files;
        if (files.length) {
            var file = files[0];
            if (/(gif|jpeg|\png|jpg|bmp)$/.test(file.type)) {
                compress(file, 1920, updateImg);
            }
        }
    });

    // 压缩图片 & 上传
    function compress(data, maxWidth, callbackFunc) {
        var reader = new FileReader();
        reader.readAsDataURL(data)
        reader.onload = function(e) {
            //新建一个img标签(还没嵌入DOM节点)
            var image = new Image()
            image.src = e.target.result;
            image.onload = function() {

                var percent = 1,
                    canvas = document.createElement('canvas'),
                    context = canvas.getContext('2d'),
                    imageWidth,    //压缩后图片的大小
                    imageHeight,
                    result = '';

                // 计算压缩比
                if(image.width > maxWidth) {
                    percent = maxWidth / image.width;
                }

                // 对图片进行等比压缩
                imageWidth = image.width * percent;
                imageHeight = image.height * percent;
                canvas.width = imageWidth;
                canvas.height = imageHeight;
                context.drawImage(image, 0, 0, imageWidth, imageHeight);
                result = canvas.toDataURL('image/jpeg');

                callbackFunc(dataURLtoBlob(result), data.name);
            }
        }
    }

    /**
     * base64转二进制
     * @param dataurl
     * @returns {Blob}
     */
    function dataURLtoBlob(dataurl) {
        var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
            bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
        while (n--){
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new Blob([u8arr], {type:mime});
    }

    /**
     * 上传到后端
     * @param res
     * @param filename
     */
    function updateImg(res, filename) {
        $('.js-upload-local').val();
        var imageFile = new FormData();
        imageFile.append("post_image", res, filename);
        $.ajax({
            type: "post",
            url: './index.php',
            data: imageFile,
            dataType: 'json',
            processData: false,  //tell jQuery not to process the data
            contentType: false,  //tell jQuery not to set contentType
            beforeSend: function (XMLHttpRequest) {
                // 加载loading信息
            },
            success: function (res) {
                console.log(res)
            },
            complete: function () {
                // 取消loading效果
            },
            error: function (res) {
                console.log(res)
            }
        });
    }
</script>
</html>

后记

如果不需要前端canvas压缩,或者兼容性不允许,可以直接使用后端的代码。

扩展学习(blob,二进制,base64)

<!DOCTYPE HTML>
<html>
<meta charset="utf-8">
<head>
</head>
<body>
// multiple属性可以让用户能选择多个文件

<input id="myfiles" multiple type="file">

</body>

<script>

var pullfiles = function(){ 
    // 获取到input元素
    var fileInput = document.querySelector("#myfiles");

    // 所有type属性(attribute)为file的 <input> 元素都有一个files属性(property),用来存储用户所选择
    var files = fileInput.files;



    // 普通方式上传二进制文件
    if (files[0]) {
        uploadImg(files[0])
    }



    // 将file生成blob链接,可用于本地预览,缩略图
    if (files[0]) {
        var blobUrl = URL.createObjectURL(files[0]);
        console.log('blob链接:', blobUrl)
    }



    // 生成data:base64的字符串
    if (files[0]) {
        var reader = new FileReader();
        reader.readAsDataURL(files[0])

        reader.onload = function (e) {
            // 这里操作生成base64字符串
            var base64str = e.target.result;
            // 上传二进制文件(由base64转换成的二进制)
            uploadImg(dataURLtoBlob(base64str))
        }
    }

}


// 设置change事件处理函数
document.querySelector("#myfiles").onchange=pullfiles;



/**
 * base64转二进制
 * @param dataurl
 * @returns {Blob}
 */
function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while (n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

/**
 * 上传文件
 */
function uploadImg(files) {
    var formData = new FormData();
    formData.append('username', 'abc123');
    formData.append('post_image', files);
    fetch('http://localhost:81/test/mozilla-test/upload.php', {
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .catch(error => console.error('Error:', error))
    .then(response => console.log('Success:', response));
}

</script>

</html>
赞赏支持
Responses