You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Ktor+Freemarker中JS仅加载首个书籍封面的解决方法

解决Freemarker循环中JS仅加载首个书籍封面的问题

问题背景

用Kotlin+Ktor调用API获取书籍JSON数据,在Freemarker的.ftl模板中循环渲染书籍卡片,尝试通过内联JavaScript加载书籍封面,但只有列表第一个元素的封面能成功加载,其余元素封面显示占位图。

问题原因

循环渲染时,每个书籍卡片的图片和隐藏输入框都使用了相同的id="img-book"和id="img-url",但HTML规范中id属性必须唯一。document.getElementById()方法只会返回DOM中第一个匹配该id的元素,导致后续卡片的JS代码始终修改第一个卡片的图片,而非当前卡片的图片。

解决方案

方案1:直接在模板中设置图片src(推荐,无需JS)

既然book.imageUrl已经是可用数据,完全不需要通过JavaScript间接设置,直接在<img>标签中绑定src属性即可,这是最简洁高效的方式:

修改后的卡片代码:

<div class="card" style="width: 18rem;">
    <img class="card-img-top" src="${book.imageUrl! 'https://via.placeholder.com/128x209.png'}">
    <div class="card-body">
        <h5 class="card-title">${book.title}</h5>
        <p class="card-text">${book.author}</p>
        <a href="/books/${book.id}" class="btn btn-outline-primary">Go to Book</a>
    </div>
</div>

注:${book.imageUrl! '占位图地址'}是Freemarker的默认值语法,当book.imageUrl为空或不存在时,会使用占位图地址。

方案2:使用类名+父元素定位(保留JS的场景)

如果必须通过JavaScript加载(比如需要额外处理图片加载逻辑),可以把id改为class,并通过当前脚本的父元素定位到当前卡片内的元素:

修改后的卡片代码:

<div class="card" style="width: 18rem;">
    <img class="card-img-top img-book" src="https://via.placeholder.com/128x209.png">
    <div class="card-body">
        <h5 class="card-title">${book.title}</h5>
        <p class="card-text">${book.author}</p>
        <input type="hidden" class="img-url" name="imageUrl" value="${book.imageUrl}">
        <script type="text/javascript">
            (function() {
                // 获取当前脚本所在的card-body容器
                const container = document.currentScript.parentElement;
                // 在容器内查找当前卡片的url输入框和图片元素
                const imgUrl = container.querySelector('.img-url').value;
                const image = container.querySelector('.img-book');
                image.src = imgUrl || 'https://via.placeholder.com/128x209.png';
            })();
        </script>
        <a href="/books/${book.id}" class="btn btn-outline-primary">Go to Book</a>
    </div>
</div>

这里改用class标识元素,通过document.currentScript.parentElement获取当前脚本所在的卡片容器,再用querySelector在容器内查找元素,确保每个JS代码只操作当前卡片的元素。

方案3:生成唯一ID

给每个元素添加包含书籍ID的唯一id,保证每个元素的id不重复:

修改后的卡片代码:

<div class="card" style="width: 18rem;">
    <img id="img-book-${book.id}" class="card-img-top" src="https://via.placeholder.com/128x209.png">
    <div class="card-body">
        <h5 class="card-title">${book.title}</h5>
        <p class="card-text">${book.author}</p>
        <input type="hidden" id="img-url-${book.id}" name="imageUrl" value="${book.imageUrl}">
        <script type="text/javascript">
            (function() {
                const bookId = "${book.id}";
                const imgUrl = document.getElementById(`img-url-${bookId}`).value;
                const image = document.getElementById(`img-book-${bookId}`);
                image.src = imgUrl || 'https://via.placeholder.com/128x209.png';
            })();
        </script>
        <a href="/books/${book.id}" class="btn btn-outline-primary">Go to Book</a>
    </div>
</div>

通过${book.id}动态生成唯一id,确保每个JS代码能精准找到当前卡片的对应元素。

内容的提问来源于stack exchange,提问作者nicowi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 12:45:37