Метод classList.add не работает, и компонент перерисовывается при нажатии на ввод файла в Svelte

Я хочу, чтобы окно загрузки изображений открывалось и закрывалось. В моем коде функция открытия работает, но по какой-то причине не работает функция закрытия. Кроме того, когда я использую console.info в функциях, отвечающих за открытие и закрытие, console.info в функции закрытия выполняется дважды. Я также пробовал использовать другой подход variable = !variable, но при этом подходе возникает проблема: при нажатии кнопки ввода файла страница перерисовывается, что сбрасывает переменную состояния в значение false, в результате чего окно снова закрывается. Кто-нибудь знает, как это исправить?

<script>
    let article = {}
    
    let base64Image
    
    let articleViewer
    
    let imageUploadBox
    
    function showImageUpload(){
        if (imageUploadBox.classList.contains("hidden")){
            imageUploadBox.classList.remove("hidden")
            console.info(imageUploadBox.classList)
        }
    }
    
    function closeImageUpload(){
        if (!imageUploadBox.classList.contains("hidden")){
            imageUploadBox.classList.add("hidden")
            console.info(imageUploadBox.classList)
        }
    }
    
    function processImage(e){
        const image = e.target.files[0]
        const imageReader = new FileReader()
        imageReader.onload = (e) => {
            base64Image = e.target.result
        }
        imageReader.readAsDataURL(image)
    }
    
    function insertImage(image){
        let selection = window.getSelection()
        let range = selection.getRangeAt(0)
        
        if (articleViewer.contains(range.commonAncestorContainer)){
            let imgTag = document.createElement("img")
            imgTag.src = image
            imgTag.className = "w-[50%] m-auto rounded-lg"
            
            range.deleteContents()
            range.insertNode(imgTag)
            
            range.setStartAfter(imgTag)
            range.collapse(true)
            selection.removeAllRanges()
            selection.addRange(range)
            
            base64Image = ""
        }
        else {
            alert("Images can only be inserted in the article content")
        }
    }
    
    function updateArticle(){}
</script>

<div class = "flex flex-col gap-4 p-8 h-screen">
    <input type = "text" class = "bg-emerald-500 text-white p-4 rounded-lg focus:border-none focus:outline-none" bind:value = {article["title"]} />
    <div class = "relative">
        <button on:click = {() => showImageUpload()} >
            <svg viewBox = "0 0 48 48" xmlns = "http://www.w3.org/2000/svg" height = "40px" width = "40px"><path fill = "white" d = "M13.3 34.15h21.45q.5 0 .7-.4.2-.4-.1-.8l-5.85-7.8q-.25-.3-.6-.3t-.6.3l-6 7.75-4.05-5.55q-.25-.3-.6-.3t-.6.3l-4.3 5.6q-.25.4-.075.8t.625.4ZM9 42q-1.2 0-2.1-.9Q6 40.2 6 39V9q0-1.2.9-2.1Q7.8 6 9 6h30q1.2 0 2.1.9.9.9.9 2.1v30q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h30V9H9v30ZM9 9v30V9Z"/></svg>
            <!--{#if imageUploadBox}-->
                <div class = "hidden bg-emerald-500 p-8 rounded-lg flex justify-center items-center flex-col gap-8 absolute top-[110%] relative" bind:this = {imageUploadBox}>
                    <input type = "file" accept = "images/*" on:change = {processImage} class = "rounded-lg text-emerald-100 text-xs font-semibold bg-emerald-950 p-4" />
                    {#if base64Image}
                        <img src = {base64Image} class = "rounded-lg w-[50%]" />
                        <button on:click = {insertImage(base64Image)} class = "bg-emerald-950 text-emerald-100 p-4 text-xs font-semibold rounded-lg" >Add</button>
                    {/if}
                    <button on:click = {closeImageUpload} class = "p-4 rounded-full absolute bottom-[100%] right-[100%] bg-emerald-950" >
                        <svg viewBox = "0 0 48 48" xmlns = "http://www.w3.org/2000/svg" height = "1.5em" width = "1.5em"><path fill = "white" d = "M24 26.1 13.5 36.6q-.45.45-1.05.45-.6 0-1.05-.45-.45-.45-.45-1.05 0-.6.45-1.05L21.9 24 11.4 13.5q-.45-.45-.45-1.05 0-.6.45-1.05.45-.45 1.05-.45.6 0 1.05.45L24 21.9l10.5-10.5q.45-.45 1.05-.45.6 0 1.05.45.45.45.45 1.05 0 .6-.45 1.05L26.1 24l10.5 10.5q.45.45.45 1.05 0 .6-.45 1.05-.45.45-1.05.45-.6 0-1.05-.45Z"/></svg>
                    </button>
                </div>
            <!--{/if}-->
        </button>
    </div>
    <div id = "articleViewer" class = "bg-emerald-500 text-white p-4 rounded-lg focus:border-none focus:outline-none" bind:innerHTML = {article["content"]} contenteditable = "true" bind:this = {articleViewer}>hello</div>
</div>

<style>
    input:active {
        border: 0;
        outline: 0;
    }
    
    .hidden {
        display: none;
    }
</style>

Другой подход, вызывающий повторную отрисовку: повторная отрисовка происходит, когда я нажимаю <input type = "file" />:

<script>
    let article = {}
    
    let base64Image
    
    let articleViewer
    
    let imageUploadBox = false
    
    function showImageUpload(){
        imageUploadBox = !imageUploadBox
    }
    
    function closeImageUpload(){
        imageUploadBox = !imageUploadBox
    }
    
    function processImage(e){
        const image = e.target.files[0]
        const imageReader = new FileReader()
        imageReader.onload = (e) => {
            base64Image = e.target.result
        }
        imageReader.readAsDataURL(image)
    }
    
    function insertImage(image){
        let selection = window.getSelection()
        let range = selection.getRangeAt(0)
        
        if (articleViewer.contains(range.commonAncestorContainer)){
            let imgTag = document.createElement("img")
            imgTag.src = image
            imgTag.className = "w-[50%] m-auto rounded-lg"
            
            range.deleteContents()
            range.insertNode(imgTag)
            
            range.setStartAfter(imgTag)
            range.collapse(true)
            selection.removeAllRanges()
            selection.addRange(range)
            
            base64Image = ""
        }
        else {
            alert("Images can only be inserted in the article content")
        }
    }
    
    function updateArticle(){}
</script>

<div class = "flex flex-col gap-4 p-8 h-screen">
    <input type = "text" class = "bg-emerald-500 text-white p-4 rounded-lg focus:border-none focus:outline-none" bind:value = {article["title"]} />
    <div class = "relative">
        <button type = "button" on:click = {() => showImageUpload()} >
            <svg viewBox = "0 0 48 48" xmlns = "http://www.w3.org/2000/svg" height = "40px" width = "40px"><path fill = "white" d = "M13.3 34.15h21.45q.5 0 .7-.4.2-.4-.1-.8l-5.85-7.8q-.25-.3-.6-.3t-.6.3l-6 7.75-4.05-5.55q-.25-.3-.6-.3t-.6.3l-4.3 5.6q-.25.4-.075.8t.625.4ZM9 42q-1.2 0-2.1-.9Q6 40.2 6 39V9q0-1.2.9-2.1Q7.8 6 9 6h30q1.2 0 2.1.9.9.9.9 2.1v30q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h30V9H9v30ZM9 9v30V9Z"/></svg>
            {#if imageUploadBox}
                <div class = "bg-emerald-500 p-8 rounded-lg flex justify-center items-center flex-col gap-8 absolute top-[110%] relative">
                    <input type = "file" accept = "images/*" on:change = {processImage} class = "rounded-lg text-emerald-100 text-xs font-semibold bg-emerald-950 p-4" />
                    {#if base64Image}
                        <img src = {base64Image} class = "rounded-lg w-[50%]" />
                        <button type = "button" on:click = {() => insertImage(base64Image)} class = "bg-emerald-950 text-emerald-100 p-4 text-xs font-semibold rounded-lg" >Add</button>
                    {/if}
                    <button type = "button" on:click = {closeImageUpload} class = "p-4 rounded-full absolute bottom-[100%] right-[100%] bg-emerald-950" >
                        <svg viewBox = "0 0 48 48" xmlns = "http://www.w3.org/2000/svg" height = "1.5em" width = "1.5em"><path fill = "white" d = "M24 26.1 13.5 36.6q-.45.45-1.05.45-.6 0-1.05-.45-.45-.45-.45-1.05 0-.6.45-1.05L21.9 24 11.4 13.5q-.45-.45-.45-1.05 0-.6.45-1.05.45-.45 1.05-.45.6 0 1.05.45L24 21.9l10.5-10.5q.45-.45 1.05-.45.6 0 1.05.45.45.45.45 1.05 0 .6-.45 1.05L26.1 24l10.5 10.5q.45.45.45 1.05 0 .6-.45 1.05-.45.45-1.05.45-.6 0-1.05-.45Z"/></svg>
                    </button>
                </div>
            {/if}
        </button>
    </div>
    <div id = "articleViewer" class = "bg-emerald-500 text-white p-4 rounded-lg focus:border-none focus:outline-none" bind:innerHTML = {article["content"]} contenteditable = "true" bind:this = {articleViewer}>hello</div>
</div>

<style>
    input:active {
        border: 0;
        outline: 0;
    }
    
    .hidden {
        display: none;
    }
</style>

🤔 А знаете ли вы, что...
JavaScript - это скриптовый язык программирования, разработанный Netscape Communications Corporation.


1
52
1

Ответ:

Решено

Если я не ошибаюсь в вашем вопросе, вы можете инициализировать локальную переменную, чтобы отслеживать состояние открытия/закрытия. Избавьтесь от closeImageUpload и showImageUpload и используйте привязку классов Svelte следующим образом:

<div
    class:hidden = {!isOpen}
    bind:this = {imageUploadBox}
    class = "bg-emerald-500 p-8 rounded-lg flex justify-center items-center flex-col gap-8 absolute top-[110%] relative">
...
</div>

Наконец, я не проверял это, но основная причина вашей проблемы, вероятно, связана с наличием двух вложенных кнопок, которые могут мешать. В Svelte 5 этот синтаксис изменился, но в Svelte 3 и 4 вам следует использовать stopPropagationмодификатор события вот так:

<button on:click|stopPropagation = {() => (isOpen = false)} class = "p-4 rounded-full absolute bottom-[100%] right-[100%] bg-emerald-950" >
  <svg>...</svg>
</button>

а для внешней кнопки просто установите isOpen на true.

<button on:click = {() => (isOpen = true)}>

Ссылка на ответ