如何在表单的输入类型=“文件”中添加图像并将它们都提交到同一个表单后生成缩略图

Posted

技术标签:

【中文标题】如何在表单的输入类型=“文件”中添加图像并将它们都提交到同一个表单后生成缩略图【英文标题】:How to generate a thumbnail image after adding an image inside an input type="file" in a form and submitting them both on the same form 【发布时间】:2013-05-06 05:38:53 【问题描述】:

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

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

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

【问题讨论】:

您可以在提交表单后将图片上传到临时文件夹。然后附加一个<img> 元素,并将src 设置为临时存储图片的路径。如果用户喜欢,他们可以确认,您可以使用新路径将其上传到真实存储区 我不需要确认,我只需要上传一张图片和它的缩略图到服务器。前端是否可以在表单提交之前从输入文件字段中设置的图像生成缩略图?然后同时提交? 有一个here 使用canvas 标签显示缩略图的例子。否则,您需要一些服务器端处理来生成上传图像的缩略图版本。 【参考方案1】:

我找到了This simpler yet powerful tutorial。它只是创建一个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=""  >

【讨论】:

它不会生成缩略图。 为什么这甚至被接受?它不能解决问题。 在firefox和chrome上运行代码sn-p,运行成功。【参考方案2】:

经过更好的在线搜索,我找到了问题的答案。

可以将 canvasFile 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 已经对这个问题给出了很好的回答:

How to upload image into html5 canvas

【讨论】:

您的示例没有使用File System API。它使用File API。 并乘以小于 1 的数字到 img.width 和 img.height 参数以在分配给 canvas.width 和 canvas.height 时按比例缩小。例如。 canvas.width=img.width*0.3. 然后是 ctx.drawImage(img,0,0,canvas.width,canvas.height)。【参考方案3】:

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

【讨论】:

您能解释一下为什么使用Math.min(...boundBox)Math.max(...realImageDimensions) 的比率吗?这仅适用于完全方形的边界框。你用例如测试过吗? [300, 100] ? @Phil 本质上它从实际图像中获取最大边缘,并计算出将其扩展到 boundBox 最小边缘所需的比例。这只是为了使图像始终适合boundBox。用笔和纸更容易检查 @Phil 如果您有更好的解决方案或提供编辑,您随时可以发布更好的答案 @Pithikos 计算 x 和 y 比率并保留最小的那个怎么样? ==> var scaleRatio = Math.min( boundBox[0]/img.width , boundBox[1]/img.height , 1); 我还在最小值比较中包含了一个“1”,因此结果永远不会大于 1(没有高档,只有低档等待)。 我还会将计算出的宽度和高度四舍五入(或者我担心在某些情况下它可能会超出边界框一个像素):let w = Math.floor(img.width*scaleRatio);let h = Math.floor(img.height*scaleRatio);【参考方案4】:

TL;DR:See the 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" />

片段如下:

(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
        ;
    ])
        .controller('UploadImageController', [
        '$scope',

    function ($scope) 

        $scope.image = 
            title: 'Test title'
        ;

        $scope.send = function (data) 
            $scope.sentData = JSON.stringify(data, null, 2);
            return false;
        ;
    ]);
)(angular);
canvas 
    max-height: 300px;
    max-width: 300px;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form data-ng-app="app" data-ng-controller="UploadImageController">
    <input data-ng-model="image.title" />
    <br />
    <canvas id="image-preview"></canvas>
    <br />
    <input type="file" data-input-file-preview="image-preview" data-map-to-model="image.file" />
    <br />
    <input type="submit" data-ng-click="send(image)" />
    
    <pre>sentData</pre>
</form>

【讨论】:

【参考方案5】:

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

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

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

【讨论】:

如果您上传多个大图像,这可能会变得相当慢。这就是缩略图很有用的原因。

以上是关于如何在表单的输入类型=“文件”中添加图像并将它们都提交到同一个表单后生成缩略图的主要内容,如果未能解决你的问题,请参考以下文章

如何在 vue 中删除图像时使输入类型文件为空?

如何从输入文件中一次读取两行并将它们添加到对象中?

从文件中获取不同类型的项目并将它们添加到数组中

为啥图像名称未显示在表单字段中以进行更新?

Java - 使用用户表单输入将图像上传到网站,将它们保存在 MySQL 数据库中,并在网站上显示图像

如何在多个文件输入中动态填充文件