如何在浏览器中使用Javascript压缩图像?

175

概述:

是否有一种方法可以在上传之前直接通过浏览器压缩图像(多数为JPEG、PNG和GIF)?我相信JavaScript可以做到,但是我找不到实现的方法。


下面是我想要实现的完整场景:

  • 用户登录我的网站,并通过input type="file"元素选择图像,
  • 通过JavaScript获取此图像,并进行某些验证,例如正确的文件格式、最大文件大小等,
  • 如果一切正常,则在页面上显示图像预览,
  • 用户可以进行一些基本操作,例如按照预定义比例旋转图像90°/-90°、裁剪图像,或上传另一个图像并返回第1步,
  • 当用户满意时,编辑后的图像被压缩并"保存"在本地(不是保存到文件,而是在浏览器内存/页面中),
  • 用户填写包含姓名、年龄等数据的表单,
  • 用户点击“完成”按钮,然后将包含数据和压缩图像的表单发送到服务器(不使用AJAX),

直到最后一步,整个过程都应该在客户端完成,并且应该兼容最新版本的Chrome和Firefox、Safari 5+以及IE 8+。如果可能,只使用JavaScript(但我相信这是不可能的)。

我还没有编写任何代码,但我已经考虑过了。可以通过File API从本地读取文件,可以使用Canvas元素预览和编辑图像,但我找不到一种方法来对图像进行压缩

根据 html5please.comcaniuse.com 的说法,由于 IE 浏览器的存在支持这些浏览器相当困难,但可以使用诸如 FlashCanvasFileReader 等 polyfill 来实现。
其实,目标是减少文件大小,因此我认为图像压缩是一个解决方案。但是,我知道上传的图像将在我的网站上显示,并且每次在同一个位置上显示,并且我知道此显示区域的尺寸(例如 200x400)。因此,我可以调整图像大小以适应这些尺寸,从而减小文件大小。我不知道这种技术的压缩比率会是多少。
你怎么看?你有什么建议告诉我吗?你知道是否有任何 JavaScript 方式在浏览器端压缩图像吗?感谢您的回复。

1
这可能会有所帮助:https://www.sitelint.com/blog/how-to-compress-the-image-on-the-client-side-before-uploading/ - Cezary Tomczyk
@AhmedSuror 总有改进的空间。你认为文档的哪个部分可以改进? - undefined
@CezaryTomczyk 我按照他们网站上唯一的一步操作来使用它,但是没有成功!没有足够的文档来说明如何实施它! - undefined
@CezaryTomczyk 不,我在一个ASP.Net Core项目中使用它,并且按照文档中所述包含了脚本,但是无法进一步进行! - undefined
@CezaryTomczyk 谢谢你的帮助 ❤️,我会尝试提出一个问题。 - undefined
显示剩余3条评论
15个回答

245
简而言之:
  • 使用HTML5 FileReader API的.readAsArrayBuffer方法读取文件
  • 使用文件数据创建Blob,并通过window.URL.createObjectURL(blob)获取其url
  • 创建新的Image元素并将其src设置为文件Blob的url
  • 将图像发送到画布,画布大小设置为所需的输出大小
  • 通过canvas.toDataURL("image/jpeg",0.7)(设置自己的输出格式和质量)从画布中获取缩小后的数据
  • 在原始表单上附加新的隐藏输入,并将数据URI图像作为普通文本传输
  • 在后端,读取dataURI,进行Base64解码并保存它
来源:代码

2
@NicholasKyriakides 我可以确认 canvas.toDataURL("image/jpeg",0.7) 能够有效地压缩它,它保存了质量为70的JPEG格式(与默认的100质量相比)。 - user1111929
4
@Nicholas Kyriakides,这不是一个好的区分。大多数编解码器并非无损的,因此它们适合于“降低”定义(即你不能恢复到100)。 - Billybobbonnet
8
缩小指的是将图像按高度和宽度缩小。这实际上是压缩。虽然是有损压缩,但仍然是压缩。它不会缩小像素,只是将一些像素推到相同的颜色,以便使用更少的比特数来压缩这些颜色。JPEG已经内置了对像素的压缩,但在有损模式下,它表示可以将一些颜色称为相同的颜色。这仍然是压缩。就图形而言,缩小通常指实际大小的变化。 - Tatarize
7
我想说的是:文件可以直接传递到 URL.createObjectUrl(),无需将文件转换成 blob;因为文件本身就算作一个 blob。 - hellol11
4
如果需要的话,我已经为psychowood的代码示例添加了一个演示jsfiddle链接:https://jsfiddle.net/Abeeee/0wxeugrt/9/ - user1432181
显示剩余5条评论

43

我在其他答案中发现了两个缺失的点:

  • canvas.toBlob(如果可用)比canvas.toDataURL更高效,而且是异步的。
  • 文件->图像->画布->文件转换会丢失EXIF数据;特别是由现代手机/平板电脑常见设置的有关图像旋转的数据。

以下脚本解决了这两个问题:

// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
if (!HTMLCanvasElement.prototype.toBlob) {
    Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
        value: function(callback, type, quality) {

            var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
                len = binStr.length,
                arr = new Uint8Array(len);

            for (var i = 0; i < len; i++) {
                arr[i] = binStr.charCodeAt(i);
            }

            callback(new Blob([arr], {type: type || 'image/png'}));
        }
    });
}

window.URL = window.URL || window.webkitURL;

// Modified from https://dev59.com/NGsz5IYBdhLWcg3w5MI0#32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback) {
    // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
    if (file.slice) {
        file = file.slice(0, 131072);
    } else if (file.webkitSlice) {
        file = file.webkitSlice(0, 131072);
    }

    var reader = new FileReader();
    reader.onload = function(e) {
        var view = new DataView(e.target.result);
        if (view.getUint16(0, false) != 0xFFD8) {
            callback(-2);
            return;
        }
        var length = view.byteLength, offset = 2;
        while (offset < length) {
            var marker = view.getUint16(offset, false);
            offset += 2;
            if (marker == 0xFFE1) {
                if (view.getUint32(offset += 2, false) != 0x45786966) {
                    callback(-1);
                    return;
                }
                var little = view.getUint16(offset += 6, false) == 0x4949;
                offset += view.getUint32(offset + 4, little);
                var tags = view.getUint16(offset, little);
                offset += 2;
                for (var i = 0; i < tags; i++)
                    if (view.getUint16(offset + (i * 12), little) == 0x0112) {
                        callback(view.getUint16(offset + (i * 12) + 8, little));
                        return;
                    }
            }
            else if ((marker & 0xFF00) != 0xFF00) break;
            else offset += view.getUint16(offset, false);
        }
        callback(-1);
    };
    reader.readAsArrayBuffer(file);
}

// Derived from https://dev59.com/Z2Ij5IYBdhLWcg3wHh3_#40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) {
    var canvas = document.createElement('canvas');
    if (orientation > 4) {
        canvas.width = rawHeight;
        canvas.height = rawWidth;
    } else {
        canvas.width = rawWidth;
        canvas.height = rawHeight;
    }

    if (orientation > 1) {
        console.log("EXIF orientation = " + orientation + ", rotating picture");
    }

    var ctx = canvas.getContext('2d');
    switch (orientation) {
        case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
        case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
        case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
        case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
        case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
        case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
        case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
    }
    ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
    return canvas;
}

function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) {
    if (file.size <= acceptFileSize) {
        callback(file);
        return;
    }
    var img = new Image();
    img.onerror = function() {
        URL.revokeObjectURL(this.src);
        callback(file);
    };
    img.onload = function() {
        URL.revokeObjectURL(this.src);
        getExifOrientation(file, function(orientation) {
            var w = img.width, h = img.height;
            var scale = (orientation > 4 ?
                Math.min(maxHeight / w, maxWidth / h, 1) :
                Math.min(maxWidth / w, maxHeight / h, 1));
            h = Math.round(h * scale);
            w = Math.round(w * scale);

            var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
            canvas.toBlob(function(blob) {
                console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
                callback(blob);
            }, 'image/jpeg', quality);
        });
    };
    img.src = URL.createObjectURL(file);
}

示例用法:

inputfile.onchange = function() {
    // If file size > 500kB, resize such that width <= 1000, quality = 0.9
    reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => {
        let body = new FormData();
        body.set('file', blob, blob.name || "file.jpg");
        fetch('/upload-image', {method: 'POST', body}).then(...);
    });
};

ToBlob 对我很有用,它可以创建文件并在服务器上通过 $_FILES 数组进行接收。谢谢! - Ricardo Ruiz Romero
看起来不错!但是这个能在所有浏览器、网页和移动设备上运行吗?(我们忽略IE) - Garvit Jain

22

@PsychoWoods的答案很好。我想提供我的解决方案。这个JavaScript函数接受一个图像数据URL和一个宽度,将其缩放到新的宽度,并返回一个新的数据URL。

// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
    "use strict";
    var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;

    // Provide default values
    imageType = imageType || "image/jpeg";
    imageArguments = imageArguments || 0.7;

    // Create a temporary image so that we can compute the height of the downscaled image.
    image = new Image();
    image.src = dataUrl;
    oldWidth = image.width;
    oldHeight = image.height;
    newHeight = Math.floor(oldHeight / oldWidth * newWidth)

    // Create a temporary canvas to draw the downscaled image on.
    canvas = document.createElement("canvas");
    canvas.width = newWidth;
    canvas.height = newHeight;

    // Draw the downscaled image on the canvas and return the new data URL.
    ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, newWidth, newHeight);
    newDataUrl = canvas.toDataURL(imageType, imageArguments);
    return newDataUrl;
}

这段代码可用于任何您拥有数据URL并希望获得缩小图像的数据URL的地方。


1
请问您能否提供更多关于这个例子的细节,如何调用函数以及返回结果的方法? - visulo
这是一个例子:http://danielsadventure.info/Html/scaleimage.html 一定要阅读页面源代码以了解其工作原理。 - Vivian River
1
对于其他想阅读@DanielAllenLangdon提供的链接的人,请访问以下网址:https://web.archive.org/web/20171226190510/danielsadventure.info/Html/scaleimage.html - Cedric Arnould
3
提醒一下,有时候image.width/height会返回0,因为图片还没有加载完成。你可能需要将其转换为异步函数,并监听image.onload事件以获取正确的宽度和高度。 - Matt Pope
-1是因为一旦分配了image.src,图像就会以异步方式加载。如果您有一个大于“tiny”的图像,则您的代码将无法运行,并且不会抛出任何错误消息。我必须通过艰苦的方式弄清楚这一点。 - Artur Müller Romanov

18

2
请添加一些关于链接资源的信息。 - SanSolo
1
演示页面链接已损坏。您可以在此处进行测试:https://demo.wangyulue.com/image-conversion/ - gabrielstuff
这非常简单而且有用。各位,甚至不需要浪费时间去阅读其他答案... - Artem Dumanov
2
这是目前为止最好的插件,超级易于使用且功能强大。这可能是最好的答案。感谢分享。 - Adel Mourad
仍然无法理解其中的一些事情,例如准确性和大小之间的关系。 - Adelin
readme 中翻译而来。它说:如果图片大小设为1000Kb,准确率为0.9,则压缩结果在900Kb-1100Kb之间的图像被视为可接受。 - bigiCrab

10

试用这个可自定义的纯JS示例 - 压缩超过90%:

   <div id="root">
        <p>Upload an image and see the result</p>
        <input id="img-input" type="file" accept="image/*" style="display:block" />
    </div>

    <script>
        const MAX_WIDTH = 320;
        const MAX_HEIGHT = 180;
        const MIME_TYPE = "image/jpeg";
        const QUALITY = 0.7;

        const input = document.getElementById("img-input");
        input.onchange = function (ev) {
            const file = ev.target.files[0]; // get the file
            const blobURL = URL.createObjectURL(file);
            const img = new Image();
            img.src = blobURL;
            img.onerror = function () {
                URL.revokeObjectURL(this.src);
                // Handle the failure properly
                console.log("Cannot load image");
            };
            img.onload = function () {
                URL.revokeObjectURL(this.src);
                const [newWidth, newHeight] = calculateSize(img, MAX_WIDTH, MAX_HEIGHT);
                const canvas = document.createElement("canvas");
                canvas.width = newWidth;
                canvas.height = newHeight;
                const ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, 0, newWidth, newHeight);
                canvas.toBlob(
                    (blob) => {
                        // Handle the compressed image. es. upload or save in local state
                        displayInfo('Original file', file);
                        displayInfo('Compressed file', blob);
                    },
                    MIME_TYPE,
                    QUALITY
                );
                document.getElementById("root").append(canvas);
            };
        };

        function calculateSize(img, maxWidth, maxHeight) {
            let width = img.width;
            let height = img.height;

            // calculate the width and height, constraining the proportions
            if (width > height) {
                if (width > maxWidth) {
                    height = Math.round((height * maxWidth) / width);
                    width = maxWidth;
                }
            } else {
                if (height > maxHeight) {
                    width = Math.round((width * maxHeight) / height);
                    height = maxHeight;
                }
            }
            return [width, height];
        }

        // Utility functions for demo purpose

        function displayInfo(label, file) {
            const p = document.createElement('p');
            p.innerText = `${label} - ${readableBytes(file.size)}`;
            document.getElementById('root').append(p);
        }

        function readableBytes(bytes) {
            const i = Math.floor(Math.log(bytes) / Math.log(1024)),
                sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

            return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
        }
    </script>

2
目前为止最佳答案。代码也很干净。 - Zac

8
我发现与被接受的答案相比,有更简单的解决方案。
  • 使用HTML5 FileReader API和.readAsArrayBuffer读取文件
  • 使用文件数据创建Blob,并使用window.URL.createObjectURL(blob)获取其url
  • 创建新的Image元素,并将其src设置为文件blob url
  • 将图像发送到画布,画布大小设置为所需的输出大小
  • 通过canvas.toDataURL("image/jpeg",0.7)(设置自己的输出格式和质量)从画布中获取缩小后的数据
  • 在原始表单上附加新的隐藏输入并基本上将dataURI图像作为普通文本传输
  • 在后端,读取dataURI,解码Base64,然后保存它

针对您的问题:

是否有一种方法可以在上传之前直接通过浏览器压缩图像(主要是jpeg、png和gif)?

我的解决方案:

  1. 使用URL.createObjectURL(inputFileElement.files[0])直接创建文件的Blob。

  2. 与接受的答案相同。

  3. 与接受的答案相同。值得一提的是,画布大小是必要的,使用img.widthimg.height设置canvas.widthcanvas.height。不是img.clientWidth

  4. 通过canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5)获取缩小后的图像。设置'image/jpg'没有效果。image/png也受支持。在函数体内let compressedImageBlob = new File([blob])创建一个新的File对象。

  5. 添加新的隐藏输入或通过javascript发送。服务器无需解码任何内容。

查看https://javascript.info/binary获取所有信息。我在阅读了这一章节之后想出了这个解决方案。


代码:

    <!DOCTYPE html>
    <html>
    <body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
      Select image to upload:
      <input type="file" name="fileToUpload" id="fileToUpload" multiple>
      <input type="submit" value="Upload Image" name="submit">
    </form>
    </body>
    </html>

这段代码看起来比其他答案要简单得多。

更新:

必须将所有内容放在 img.onload 中。否则,canvas 将无法正确获取图像的宽度和高度,因为此时会分配 canvas 的时间。

    function upload(){
        var f = fileToUpload.files[0];
        var fileName = f.name.split('.')[0];
        var img = new Image();
        img.src = URL.createObjectURL(f);
        img.onload = function(){
            var canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            var ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0);
            canvas.toBlob(function(blob){
                    console.info(blob.size);
                    var f2 = new File([blob], fileName + ".jpeg");
                    var xhr = new XMLHttpRequest();
                    var form = new FormData();
                    form.append("fileToUpload", f2);
                    xhr.open("POST", "upload.php");
                    xhr.send(form);
            }, 'image/jpeg', 0.5);
        }
    }

使用image/jpeg参数设置对.png文件进行3.4MB压缩测试。

    |0.9| 777KB |
    |0.8| 383KB |
    |0.7| 301KB |
    |0.6| 251KB |
    |0.5| 219kB |

我喜欢你的解决方案,但是我们如何保留EXIF数据呢? - mangeshbhuskute
我使用了这个函数,但在服务器上得到的是application/octet-stream而不是image/jpeg。为了解决这个问题,我发送了blob本身而不是new File(...) - Nico Serrano

5

我之前在使用@daniel-allen-langdon发布的downscaleImage()函数时遇到了问题,因为image.widthimage.height属性不是立即可用的,因为图像加载是异步的

请参见下面更新的TypeScript示例,它考虑了这一点,使用async函数,并根据最长的维度而不仅仅是宽度来调整图像大小。

function getImage(dataUrl: string): Promise<HTMLImageElement> 
{
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = dataUrl;
        image.onload = () => {
            resolve(image);
        };
        image.onerror = (el: any, err: ErrorEvent) => {
            reject(err.error);
        };
    });
}

export async function downscaleImage(
        dataUrl: string,  
        imageType: string,  // e.g. 'image/jpeg'
        resolution: number,  // max width/height in pixels
        quality: number   // e.g. 0.9 = 90% quality
    ): Promise<string> {

    // Create a temporary image so that we can compute the height of the image.
    const image = await getImage(dataUrl);
    const oldWidth = image.naturalWidth;
    const oldHeight = image.naturalHeight;
    console.log('dims', oldWidth, oldHeight);

    const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
    const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
    console.log('longest dim', longestDimension, currentRes);

    if (currentRes > resolution) {
        console.log('need to resize...');

        // Calculate new dimensions
        const newSize = longestDimension == 'width'
            ? Math.floor(oldHeight / oldWidth * resolution)
            : Math.floor(oldWidth / oldHeight * resolution);
        const newWidth = longestDimension == 'width' ? resolution : newSize;
        const newHeight = longestDimension == 'height' ? resolution : newSize;
        console.log('new width / height', newWidth, newHeight);

        // Create a temporary canvas to draw the downscaled image on.
        const canvas = document.createElement('canvas');
        canvas.width = newWidth;
        canvas.height = newHeight;

        // Draw the downscaled image on the canvas and return the new data URL.
        const ctx = canvas.getContext('2d')!;
        ctx.drawImage(image, 0, 0, newWidth, newHeight);
        const newDataUrl = canvas.toDataURL(imageType, quality);
        return newDataUrl;
    }
    else {
        return dataUrl;
    }

}

我会添加关于“质量”、“分辨率”和“图像类型”(格式)的解释。 - Barabas

5

对于现代浏览器,请使用createImageBitmap()而不是img.onload

async function compressImage(blobImg, percent) {
  let bitmap = await createImageBitmap(blobImg);
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  canvas.width = bitmap.width;
  canvas.height = bitmap.height;
  ctx.drawImage(bitmap, 0, 0);
  let dataUrl = canvas.toDataURL("image/jpeg", percent/100);
  return dataUrl;
}

inputImg.addEventListener('change', async(e) => {
  let img = e.target.files[0];
  console.log('File Name: ', img.name)
  console.log('Original Size: ', img.size.toLocaleString())
  
  let imgCompressed = await compressImage(img, 75) // set to 75%
  let compSize = atob(imgCompressed.split(",")[1]).length;
  console.log('Compressed Size: ', compSize.toLocaleString())
  //console.log(imgCompressed)
})
<input type="file" id="inputImg">


3
我使用了以下软件包: https://www.npmjs.com/package/browser-image-compression
npm install browser-image-compression
or
yarn add browser-image-compression

然后只需按照文档操作:

import imageCompression from 'browser-image-compression';
const options = {
 maxSizeMB: 0.5, // pretty much self-explanatory
 maxWidthOrHeight: 500, // apparently px
}

imageCompression(file, options)
            .then(function(compressedFile) {
                console.log(
                    "compressedFile instanceof Blob",
                    compressedFile instanceof Blob
                ); // true
                console.log(
                    `compressedFile size ${compressedFile.size /
                        1024 /
                        1024} MB`
                ); // smaller than maxSizeMB

                return uploader(compressedFile); // code to actual upload, in my case uploader() is a function to upload to Firebase storage.
            })

如果你对 uploader() 有疑问,这里是它的代码:

import { initializeApp } from "firebase/app";

const firebaseConfig = {
    // your config
};

initializeApp(firebaseConfig);
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
const storage = getStorage();
const sRef = ref(storage);

const uploader = async (file) => {
        /* uploads to root */

        // const imageRef = ref(sRef, file.name);
        // console.log(imageRef);
        // await uploadBytes(imageRef, file).then((snapshot) => {
        //  console.log("Uploaded a blob or file!", snapshot);
        // });

        /* upload to folder 'techs/' */
        const folderRef = ref(sRef, "techs/" + file.name);
        await uploadBytes(folderRef, file);

        // get URL
        const url = await getDownloadURL(ref(storage, folderRef));

        console.log("url: ", url);
        return url;
    };


3

编辑:根据Mr Me在这个答案中的评论,看起来现在可以为JPG/WebP格式使用压缩(请参见https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)。

据我所知,您不能使用canvas压缩图像,而是可以调整其大小。使用canvas.toDataURL将不允许您选择要使用的压缩比率。您可以查看CanImage,它完全符合您的要求:https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

实际上,通常只需调整图像大小即可减小其大小,但如果您想进一步减小大小,则必须使用新引入的方法file.readAsArrayBuffer以获取包含图像数据的缓冲区。

然后,只需使用DataView根据图像格式规范(http://en.wikipedia.org/wiki/JPEGhttp://en.wikipedia.org/wiki/Portable_Network_Graphics)读取其内容。

处理图像数据压缩将是困难的,但尝试一下也值得。另一方面,您可以尝试删除PNG标题或JPEG exif数据以使图像更小,这应该更容易做到。

您将需要在另一个缓冲区上创建另一个DataWiew,并用过滤后的图像内容填充它。然后,您只需使用window.btoa对图像内容进行编码以生成DataURI。

如果您实现了类似的东西,请让我知道,很有趣看代码。


1
也许自你发布这篇文章以来有些变化了,但是你提到的那个 canvas.toDataURL 函数的第二个参数是你想要应用的压缩量。 - hostingutilities.com

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接