admin管理员组

文章数量:1302406

I am trying to preview multiple images in the browser before they are uploaded to the server using ReactJS and the FileReader() API. The problem that I have However is every time I select some images for preview, only the last image is displayed.

My code looks like this:

class App extends Component {
    constructor(props){
        super(props);
        this.state = {
            id: "upload-photo",
            imageURI: null
        }
    }

    buildImgTag(){
        let imgTag = null;
        if (this.state.imageURI !== null) {
            imgTag = (
                <div className="photo-container">
                    <img className="photo-uploaded" src={this.state.imageURI} alt="Photo uploaded"/>
                </div>
            );
            return imgTag;
        }
    }

    readURI(e){
        if (e.target.files) {
            let filesAmount = e.target.files.length;
            let i;
            for (i = 0; i < filesAmount; i++) {
                let reader = new FileReader();
                reader.onload = function(ev) {
                    this.setState (
                        {
                            imageURI: ev.target.result
                        }
                    )
                }.bind(this);
                reader.readAsDataURL(e.target.files[i]);
            }
        }
    }

    handleChange(e){
        this.readURI(e);
        if (this.props.onChange !== undefined) {
            this.props.onChange(e);
        }
    }

    render() {
        const imgTag = this.buildImgTag();

        return (
            <div className="container">
                <div className="row justify-content-center">

                    <div className="col-md-6">
                        <div className="card">
                            <div className="card-header" style={{backgroundColor: 'rgb(232, 245, 253)', borderTopLeftRadius: '4px', borderTopRightRadius: '4px', display: 'flex', maxHeight: '50vh', minHeight: '25vh', overflow: 'hidden'}}>
                                <div className="avatar">
                                    <img src=":8080/images/avatar-default.png" alt="User Avatar" className="user-avatar"/>
                                </div>
                                <div id="textEditor">
                                    <form method="post" action="" encType="multipart/form-data">
                                        <textarea name="" id="richTextArea" placeholder="What's happening?"></textarea>
                                        {imgTag}
                                        <div id="theRibbon">
                                            <div>
                                                <input
                                                    id={this.state.id}
                                                    type="file"
                                                    name=""
                                                    accept="image/gif,image/jpeg,image/jpg,image/png,video/mp4,video/x-m4v"
                                                    title="Add photos or video"
                                                    onChange={this.handleChange.bind(this)}
                                                    multiple
                                                />
                                                <label htmlFor={this.state.id}>
                                                    <figure>
                                                        <svg xmlns="" width="30" height="27"
                                                             viewBox="0 0 20 17" className="upload-icon">
                                                            <path
                                                                d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"/>
                                                        </svg>
                                                    </figure>
                                                    <span className="tooltiptext">Add photos or video</span>
                                                </label>
                                            </div>
                                            <button type="submit" className="tweet">Tweet</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

I expect all images selected to be displayed / previewed in the browser but only the last image is previewed. What am I doing wrong?

I am trying to preview multiple images in the browser before they are uploaded to the server using ReactJS and the FileReader() API. The problem that I have However is every time I select some images for preview, only the last image is displayed.

My code looks like this:

class App extends Component {
    constructor(props){
        super(props);
        this.state = {
            id: "upload-photo",
            imageURI: null
        }
    }

    buildImgTag(){
        let imgTag = null;
        if (this.state.imageURI !== null) {
            imgTag = (
                <div className="photo-container">
                    <img className="photo-uploaded" src={this.state.imageURI} alt="Photo uploaded"/>
                </div>
            );
            return imgTag;
        }
    }

    readURI(e){
        if (e.target.files) {
            let filesAmount = e.target.files.length;
            let i;
            for (i = 0; i < filesAmount; i++) {
                let reader = new FileReader();
                reader.onload = function(ev) {
                    this.setState (
                        {
                            imageURI: ev.target.result
                        }
                    )
                }.bind(this);
                reader.readAsDataURL(e.target.files[i]);
            }
        }
    }

    handleChange(e){
        this.readURI(e);
        if (this.props.onChange !== undefined) {
            this.props.onChange(e);
        }
    }

    render() {
        const imgTag = this.buildImgTag();

        return (
            <div className="container">
                <div className="row justify-content-center">

                    <div className="col-md-6">
                        <div className="card">
                            <div className="card-header" style={{backgroundColor: 'rgb(232, 245, 253)', borderTopLeftRadius: '4px', borderTopRightRadius: '4px', display: 'flex', maxHeight: '50vh', minHeight: '25vh', overflow: 'hidden'}}>
                                <div className="avatar">
                                    <img src="http://laratweet.local:8080/images/avatar-default.png" alt="User Avatar" className="user-avatar"/>
                                </div>
                                <div id="textEditor">
                                    <form method="post" action="" encType="multipart/form-data">
                                        <textarea name="" id="richTextArea" placeholder="What's happening?"></textarea>
                                        {imgTag}
                                        <div id="theRibbon">
                                            <div>
                                                <input
                                                    id={this.state.id}
                                                    type="file"
                                                    name=""
                                                    accept="image/gif,image/jpeg,image/jpg,image/png,video/mp4,video/x-m4v"
                                                    title="Add photos or video"
                                                    onChange={this.handleChange.bind(this)}
                                                    multiple
                                                />
                                                <label htmlFor={this.state.id}>
                                                    <figure>
                                                        <svg xmlns="http://www.w3/2000/svg" width="30" height="27"
                                                             viewBox="0 0 20 17" className="upload-icon">
                                                            <path
                                                                d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"/>
                                                        </svg>
                                                    </figure>
                                                    <span className="tooltiptext">Add photos or video</span>
                                                </label>
                                            </div>
                                            <button type="submit" className="tweet">Tweet</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

I expect all images selected to be displayed / previewed in the browser but only the last image is previewed. What am I doing wrong?

Share edited Apr 5, 2019 at 2:31 Dacre Denny 30.4k5 gold badges51 silver badges66 bronze badges asked Apr 5, 2019 at 0:59 Israel ObanijesuIsrael Obanijesu 6961 gold badge14 silver badges28 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 5

The main problem here is that your App ponents state can currently only track one imageURI. Consider revising the state model so that an array of imageURI's can be stored and rendered:

this.state = {
    id: "upload-photo",
    imageArray: [] /* Replace imageURI with an array for multiple images */
}

Next, you'll need to update readURI() so that it stores multiple images in the ponent state. One approach would be to use Promise.all() to load an array of images asynchronously:

readURI(e){
    if (e.target.files) {

        /* Get files in array form */
        const files = Array.from(e.target.files);

        /* Map each file to a promise that resolves to an array of image URI's */ 
        Promise.all(files.map(file => {
            return (new Promise((resolve,reject) => {
                const reader = new FileReader();
                reader.addEventListener('load', (ev) => {
                    resolve(ev.target.result);
                });
                reader.addEventListener('error', reject);
                reader.readAsDataURL(file);
            }));
        }))
        .then(images => {

            /* Once all promises are resolved, update state with image URI array */
            this.setState({ imageArray : images })

        }, error => {        
            console.error(error);
        });
    }
}

Lastly, you'll just need to update buildImgTag() so that multiple images are rendered. One approach to that might be:

buildImgTag(){

    return <div className="photo-container">
    { 
      this.state.imageArray.map(imageURI => 
      (<img className="photo-uploaded" src={imageURI} alt="Photo uploaded"/>)) 
    }
    </div>
}

Also, here is a jsFiddle showing the file reading logic in action. Hope that helps

For those ing looking for the same answer, there is a cleaner option which doesn't involve faffing around with anything other than javascript:

HTML:

<!DOCTYPE html>
<html>
    <head>
        <title>File API - FileReader as Data URL</title>
    </head>
    <body>
        <header>
            <h1>File API - FileReader</h1>
        </header>
        <article>
            <label for="files">Select multiple files: </label>
            <input id="files" type="file" multiple/>
            <output id="result" />
        </article>
    </body>
</html>

CSS:

body
{
    font-family: 'Segoe UI';
    font-size: 12pt;
}

header h1
{
    font-size:12pt;
    color: #fff;
    background-color: #1BA1E2;
    padding: 20px;
}
article
{
    width: 80%;
    margin:auto;
    margin-top:10px;
}
.thumbnail
{
    height: 100px;
    margin: 10px;    
}

JAVASCRIPT

window.onload = function()
{

    //Check File API support
    if ( window.File && window.FileList && window.FileReader )
    {
        var filesInput = document.getElementById("files");

        filesInput.addEventListener 
        ( 
            "change", function ( event )
            {
                var files = event.target.files; //FileList object
                var output = document.getElementById ( "result" );

                for ( var i = 0; i< files.length; i++ )
                {
                    var file = files [ i ];

                    //Only pics
                    if ( !file.type.match ( 'image' ) )
                    continue;

                    var picReader = new FileReader();

                    picReader.addEventListener 
                    ( 
                        "load", function ( event )
                        {                    
                            var picFile = event.target;

                            var div = document.createElement ( "div" );

                            div.innerHTML = "<img class='thumbnail' src='" + picFile.result + "'" + "title='" + picFile.name + "'/>";

                            output.insertBefore ( div, null );
                        }
                    );

                   //Read the image
                   picReader.readAsDataURL ( file );
               }                               
           }
        );
    }
    else
    {
        console.log ( "Your browser does not support File API" );
    }
}

http://jsfiddle/0GiS0/Yvgc2/

Tentei durante um dia todo a resposta do amigo @Dacre Denny e acabei utilizando a primeira linha de sua resolução, ambas as respostas são nativas do JavaScript mas parece que o FileReader() deve servir para aplicações mais plexas o por exemplo salvar as imagens em base64 em um banco de dados, não tenho certeza...

Acabei resolvendo sem converter para Base64 de uma forma muito "mais simples" Primeiro declarei meus States

const [files, setFiles] = useState([]);
const [previews, setPreviews] = useState([]);
const [files, setFiles] = useState([]);

...

const handleUpload = (event) => {
const fileList = Array.from(event.target.files);

setFiles(fileList); //Passei o valor de fileList para o State Files

const mappedFiles = fileList.map((file) => ({
  ...file,
  preview: URL.createObjectURL(file),
}));

setPreviews(mappedFiles); };

...Em meu Styled-Component de Upload:

<UploadPhoto type="file" id="files" accept="image/*" multiple="true" onChange={(e) => handleUpload(e)} />

E finalmente para exibição dos arquivos fiz um array.Map

if (previews.length > 0) {
  return (
    <FormGroup> //styled-ponent (div) para organizar os itens do form
      <UploadContainer> //styled-ponent (div) display: flex para suportar as imagens
        {previews.map((file) => <ImgUpload preview={file.preview} />)} //styled-ponent 
        //(div) que se repete de acordo  o número de imagens
      </UploadContainer>
    </FormGroup>
  );
}

A melhor forma para exibição das imagens em minha opinião é colocando o background-image de alguma div juntamente as propriedades background-repeat: no-repeat, background-size: cover e background-position: 50% 50%

export const ImgUpload = styled.div`
flex-direction: row;
text-align: center;
margin: 10px;
background-image: ${(props) => (props.preview ? `url(${props.preview})` : null)} ;
min-height: 100px;
min-width: 100px;
border-radius: 5px;
background-repeat: no-repeat;
background-size: cover;
background-position: 50% 50%;
margin-right: 5px;
`;

本文标签: javascriptReactJS Preview multiple images before uploadStack Overflow