如何在表单中的 input type="file" 中添加图像并在同一表单上提交它们后生成缩略图

IT技术 javascript jquery ajax html
2021-02-27 17:56:43

我有一个允许用户上传图片的表单。用户提交表单后,我想在前端为每张图片生成一个缩略图,然后将其存储在服务器上。

出于安全原因,无法更改文件输入字段的值,那么如何将前端在 js 中生成的一些缩略图图像发送到服务器?

在表单提交之前,前端是否可以从输入文件字段中设置的图像生成缩略图?然后同时提交?

5个回答

我发现这个更简单但功能强大的教程它只是创建一个img元素,并使用 fileReader 对象,将其源属性分配为表单输入的值

function previewFile() {
  var preview = document.querySelector('img');
  var file    = document.querySelector('input[type=file]').files[0];
  var reader  = new FileReader();

  reader.onloadend = function () {
    preview.src = reader.result;
  }

  if (file) {
    reader.readAsDataURL(file);
  } else {
    preview.src = "";
  }
}
<input type="file" onchange="previewFile()"><br>
<img src="" height="200" alt="Image preview...">

它不会制作缩略图。
2021-04-29 17:56:43
为什么这甚至被接受?它不能解决问题。
2021-05-06 17:56:43
在 Firefox 和 chrome 上运行代码片段,运行成功。
2021-05-12 17:56:43

在网上更好地搜索后,我找到了我的问题的答案。

可以将画布File API结合在一起

尝试上传下面演示中的任何图片,并看到新生成的缩略图会出现在表单的右侧。

演示: http : //jsfiddle.net/a_incarnati/fua75hpv/

function handleImage(e){
    var reader = new FileReader();
    reader.onload = function(event){
        var img = new Image();
        img.onload = function(){
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img,0,0);
        }
        img.src = event.target.result;
    }
    reader.readAsDataURL(e.target.files[0]);     
}

DerekR 对这个问题给出了很好的回答:

如何将图像上传到 HTML5 画布

然后像 ctx.drawImage(img,0,0,canvas.width,canvas.height)。
2021-05-14 17:56:43
并将小于 1 的数字乘以 img.width 和 img.height 参数以在分配给 canvas.width 和 canvas.height 时按比例缩小。例如。canvas.width=img.width*0.3。
2021-05-17 17:56:43
您的示例不使用File System API它使用文件 API
2021-05-18 17:56:43

建立在 Allesandro 写的更务实的东西之上。

该函数从 File API 获取一个文件,并尝试将其放入 boundBox 中,同时保留纵横比。没有绘制任何内容,但您会返回一个Promise吐出生成的 dataUrl 的数据。

// Creates a thumbnail fitted insize the boundBox (w x h)
generateThumbnail(file, boundBox){
  if (!boundBox || boundBox.length != 2){
    throw "You need to give the boundBox"
  }
  var scaleRatio = Math.min(...boundBox) / Math.max(file.width, file.height)
  var reader = new FileReader();
  var canvas = document.createElement("canvas")
  var ctx = canvas.getContext('2d');

  return new Promise((resolve, reject) => {
    reader.onload = function(event){
        var img = new Image();
        img.onload = function(){
            var scaleRatio = Math.min(...boundBox) / Math.max(img.width, img.height)
            let w = img.width*scaleRatio
            let h = img.height*scaleRatio
            canvas.width = w;
            canvas.height = h;
            ctx.drawImage(img, 0, 0, w, h);
            return resolve(canvas.toDataURL(file.type))
        }
        img.src = event.target.result;
    }
    reader.readAsDataURL(file);
  })
}

它可以像下面一样使用

generateThumbnail(file, [300, 300]).then(function(dataUrl){
    console.log(dataUrl)
})
@Phil 如果您有更好的解决方案或提供编辑,您随时可以发布更好的答案
2021-04-21 17:56:43
@Phil 比率为 0.166,您将获得 96x32 图像(600*0.166 和 200*0.166)。请记住,比例比例是为了缩小规模。然而你可能是对的,算法可能会过度缩小,但它确保在这种情况下任何边缘都不高于最小边界框(这在许多情况下可能是你想要的)。
2021-04-23 17:56:43
例如,如果图像 = 600x200,则框 = 300x100。ScaleRatio 应该是 0.5,而不是 0.166。你最终会得到一个 100x17 的超小缩略图
2021-04-29 17:56:43
@Phil 本质上它从实际图像中获取最大的边缘,并计算出将其扩展到 boundBox 的最小边缘所需的缩放比例。这样做只是为了使图像始终适合 boundBox。用笔和纸更容易检查
2021-05-15 17:56:43
你能解释为什么你正在使用的比例Math.min(...boundBox)Math.max(...realImageDimensions)这仅适用于完全方形的边界框。你用 eg 测试了[300, 100]吗?
2021-05-16 17:56:43

TL;DR:见 JSFiddle

因为我想通过 API 上传图像并显示图像的预览(两件事实际上非常适合彼此),所以我想出了这个:

(function(angular) {
    angular
        .module('app')
        .directive('inputFilePreview', [function() {

            var canvas, mapToModel, elementScope;

            /**
             * To be fired when the image has been loaded
             */
            var imageOnLoad = function(){
                canvas.width = this.width;
                canvas.height = this.height;
                canvas.getContext("2d").drawImage(this,0,0);
            };

            /**
             * To be fired when the FileReader has loaded
             * @param loadEvent {{}}
             */
            var readerOnLoad = function(loadEvent){
                var img = new Image();
                img.onload = imageOnLoad;
                img.src = loadEvent.target.result;
                if(mapToModel) {
                    setModelValue(elementScope, mapToModel, img.src);
                }
            };

            /**
             * This allows us to set the value of a model in the scope of the element (or global scope if the
             * model is an object)
             * @param scope {{}}
             * @param modelReference {string}
             * @param value {*}
             */
            var setModelValue = function(scope, modelReference, value) {
                // If the model reference refers to the propery of an object (eg. "object.property")
                if(~modelReference.indexOf('.')) {
                    var parts = modelReference.split('.', 2);
                    // Only set the value if that object already exists
                    if(scope.hasOwnProperty(parts[0])) {
                        scope[parts[0]][parts[1]] = value;
                        return;
                    }
                }
                scope[modelReference] = value;
            };

            /**
             * The logic for our directive
             * @param scope {{}}
             * @param element {{}}
             * @param attributes {{}}
             */
            var link = function(scope, element, attributes) {
                elementScope = scope;
                canvas = document.getElementById(attributes.inputFilePreview);
                if(attributes.hasOwnProperty('mapToModel')) {
                    mapToModel = attributes.mapToModel;
                }
                element.on('change', function(changeEvent) {
                    var reader = new FileReader();
                    reader.onload = readerOnLoad;
                    reader.readAsDataURL(changeEvent.target.files[0]);
                });
            };

            return {
                restrict: 'A',
                link: link
            };
        }]);
})(angular);

预览工作所需的两个元素是:

<canvas id="image-preview"></canvas>
<input type="file" data-input-file-preview="image-preview" data-map-to-model="image.file" />

片段如下:

认为可能值得添加一个更现代的答案并引用MDN Web Docs

您可以在输入元素上为“更改”添加一个事件侦听器,然后通过访问文件列表来this.files显示所选图像的缩略图(如 MDN 示例中所示)。这是我最近的一个实现。上传水印是一个<input type="file></input>

uploadWatermark.addEventListener('change', function(){
  const file = this.files[0];
  if (file.type.startsWith('image/')) {
    const img = document.createElement('img');
    const watermarkPreview = document.getElementById("uploaded-watermark");

    img.classList.add("prev-thumb");
    img.file = file;
    watermarkPreview.appendChild(img);

    const reader = new FileReader();
    reader.onload = (function(aImg) { return function(e) { aImg.src =   e.target.result; }})(img);
    reader.readAsDataURL(file);
  }
  
});

如果您上传多个大图像,这可能会变得相当慢。这就是缩略图很有用的原因。
2021-05-06 17:56:43