Kotlin Ktor+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

