前端手写瀑布流

瀑布流js实现

瀑布流布局是一种比较流行的网站页面布局,视觉表现为参差不齐的多栏布局,最早采用此布局的网站是Pinterest,逐渐在国内流行开来。

特点

  1. 瀑布流对用户来说有着很强的吸引力,瀑布流会在它的页面底部给用户不断地加载新的暗示信息,通过给出不完整的视觉图片去吸引你的好奇心,让你停不下来地想要向下不断地探索。

  2. 瀑布流的信息较为集中,可以在最小的操作成本下能够获得最多的内容体验,因此瀑布流能更好的适应移动端,由于移动设备屏幕比电脑小,一个屏幕显示的内容不会非常多,因此可能要经常翻页。而在建网站时使用瀑布流布局,用户则只需要进行滚动就能够不断浏览内容。

  3. 另外瀑布流的主要特质就是:定宽而不定高,这样的页面设计区别于传统的矩阵式图片布局模式,巧妙的利用视觉层级,视线的任意流动来缓解视觉的疲劳。

实现

html与css

首先在html文件中准备一个盒子,并添加id,以便后面通过id来操作dom

<!DOCTYPE html>
<html lang="zh">
<head>
    <title>手写瀑布流</title>
    <meta charset="UTF-8">
</head>
<body>
    <div id="mainBorder"></div>
</body>
</html>

添加css样式,设置容器宽度,定位

*{
    margin: 0;
    padding: 0;
}
#mainBorder{
    position: relative;
    width: 100vw;
}

然后开始编写js代码

设置图片宽度,获取容器

const imgWidth = 200 //设置图片宽度
const mainDom = document.getElementById("mainBorder")

提前准备好图片,建议以数字从大到小排列,便于使用for循环插入图片

请添加图片描述

向容器中插入图片

// 添加图片
for (let i = 0; i < 20; i++) {
    const imgUrl = `./images/${i}.jpg` //拼接图片路径
    const img = document.createElement('img') // 创建节点
    img.src = imgUrl
    mainDom.appendChild(img) // 向容器中添加节点
}

在这里,通过for循环产生的数字i刚好就是我们准备好的图片名称,只需要使用模板字符串拼接成完整的图片路径,然后赋值给创建的img标签的src属性即可

请添加图片描述

在瀑布流布局中,我们需要针对每张图片进行绝对定位,可以提前把公共样式提取出来

.waterfallsFlow{
    width: 200px;
    position: absolute;
}

任意一个类名,设置绝对定位和宽度,并把这个类名添加给我们插入的图片

修改插入图片的代码

for (let i = 0; i < 20; i++) {
    const imgUrl = `./images/${i}.jpg`
    const img = document.createElement('img')
    img.src = imgUrl
    img.className = "waterfallsFlow" // 添加类名
    mainDom.appendChild(img)
}

为创建的img元素添加类名,以绑定样式

接下来就是比较核心的两个方法

计算瀑布流相关数值

首先编写第一个函数,根据我们设置的图片宽度与屏幕当前尺寸,计算瀑布流的列数以及每一列的间距

// 计算瀑布流列数与外边距
function calc() {
    const windowWidth = window.innerWidth // 获取屏幕宽度
    const imgRow = parseInt(windowWidth / imgWidth) 
    const imgMargin = (windowWidth - imgRow * imgWidth) / (imgRow + 1)
    return [imgRow, imgMargin]
}

在获取了屏幕宽度后,用屏幕宽度除以图片宽度并取整,即可知道在当前屏幕我们每行可以放多少列图片,此时添加的图片都是紧密相连,然后再将屏幕添加图片后剩余的宽度均匀分配,也就是每张图片的左右间距

为图片设置定位

function setPosition() {
    // es6结构语法
    const [imgRow/*瀑布流列数*/, imgMargin/*每张图片的外边距*/] = calc()
    const imgs = mainDom.children
    // let [rowNum/*当前所在列数*/, imgHeight/*当前图片高度*/] = [1, 0]
    let rowHeightList = new Array(imgRow) // 创建一个用来存储每一列高度的数组
    // 数组初始化
    for (let i = 0; i < rowHeightList.length; i++) {
        rowHeightList[i] = 0
    }
    for (let i = 0; i < imgs.length; i++) {
        const minHeight = Math.min(...rowHeightList)
        const rowNum = rowHeightList.indexOf(minHeight)
        const imgHeight = imgs[i].offsetHeight
        // 由于子元素设置了absolute,故父元素不会被撑开,需要给父元素设置高度
        mainDom.offsetHeight = Math.max(...rowHeightList)
        imgs[i].style.cssText = `
        width:${imgWidth}px;
        top:${minHeight + imgHeightMargin}px;
        left:${imgWidth * rowNum + imgMargin * (rowNum + 1)}px`
        rowHeightList[rowNum] += imgHeight + imgHeightMargin
    }
}

在为每张图片设置定位时,顺序排列可能导致列于列之间高度差距过大,所以需要使用一个数组来保存每一列的高度,每次插入图片时,都往高度最小的那一列进行插入

由于每张图片都是使用的绝对定位,所以无法撑开容器,如果页面中有其他盒子,便会导致布局混乱,所以要将最高列的高度设置为容器的高度

遍历每一个img节点,使用cssText+模板字符串,设置定位

然后只需要在窗口加载和尺寸变化时调用此方法,即可实现瀑布流布局

window.onload = setPosition
window.onresize = setPosition

优化

为了解决改变屏幕尺寸时图片闪烁的问题,可以为图片添加一个过渡

修改css代码

.waterfallsFlow{
    width: 200px;
    position: absolute;
    transition: all .5s;
}

由于window.onresize触发过于频繁,可以使用防抖函数

// 使用闭包完成一个简单的防抖函数
function debounce(func,deplay){
    let timer = null
    return function (){
        if (timer){clearTimeout(timer)}
        timer = setTimeout(()=>{
            func()
        },deplay)
    }
}

然后修改代码

window.onload = setPosition
window.onresize = debounce(setPosition,100)

请添加图片描述

大功告成!!!

完整代码

<!DOCTYPE html>
<html lang="zh">
<head>
    <title>手写瀑布流</title>
    <meta charset="UTF-8">
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        #mainBorder{
            position: relative;
            width: 100vw;
        }
        .waterfallsFlow{
            width: 200px;
            position: absolute;
            transition: all .5s;
        }
    </style>
</head>
<body>
<div id="mainBorder"></div>
<script type="text/javascript">
    const imgWidth = 200 //设置图片宽度
    const mainDom = document.getElementById("mainBorder")
    const imgHeightMargin = 5 // 图片纵向间距
    // 添加图片
    for (let i = 0; i < 20; i++) {
        const imgUrl = `./images/${i}.jpg`
        const img = document.createElement('img')
        img.src = imgUrl
        img.className = "waterfallsFlow"
        mainDom.appendChild(img)
    }

    // 计算瀑布流列数与外边距
    function calc() {
        const windowWidth = window.innerWidth
        const imgRow = parseInt(windowWidth / imgWidth)
        const imgMargin = (windowWidth - imgRow * imgWidth) / (imgRow + 1)
        return [imgRow, imgMargin]
    }

    function setPosition() {
        const [imgRow/*瀑布流列数*/, imgMargin/*每张图片的外边距*/] = calc()
        const imgs = mainDom.children
        // let [rowNum/*当前所在列数*/, imgHeight/*当前图片高度*/] = [1, 0]
        let rowHeightList = new Array(imgRow) // 创建一个用来存储每一列高度的数组
        // 数组初始化
        for (let i = 0; i < rowHeightList.length; i++) {
            rowHeightList[i] = 0
        }
        for (let i = 0; i < imgs.length; i++) {
            const minHeight = Math.min(...rowHeightList)
            const rowNum = rowHeightList.indexOf(minHeight)
            const imgHeight = imgs[i].offsetHeight
            // 由于子元素设置了absolute,故父元素不会被撑开,需要给父元素设置高度
            mainDom.offsetHeight = Math.max(...rowHeightList)
            imgs[i].style.cssText = `
            width:${imgWidth}px;
            top:${minHeight + imgHeightMargin}px;
            left:${imgWidth * rowNum + imgMargin * (rowNum + 1)}px`
            rowHeightList[rowNum] += imgHeight + imgHeightMargin
        }
    }

    // 使用闭包完成防抖函数
    function debounce(func,deplay){
        let timer = null
        return function (){
            if (timer){clearTimeout(timer)}
            timer = setTimeout(()=>{
                func()
            },deplay)
        }
    }

    window.onload = setPosition
    window.onresize = debounce(setPosition,100)
</script>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
好的,以下是一个简单的前端手写轮播图实现思路: 1. HTML 结构中需要一个容器元素,用来包含轮播图。 2. 在该容器元素中,添加多个轮播项,每个轮播项包含一张图片和一段描述文字。 3. 通过 CSS 设置轮播项的样式,包括度、高度、位置等。 4. 在 JavaScript 中获取容器元素和轮播项元素,以及左右切换按钮元素。 5. 通过设置 CSS 样式,将当前显示的轮播项置于最前面。 6. 给左右切换按钮元素添加事件监听器,当点击时,通过 JavaScript 移动轮播项的位置,完成切换。 7. 使用定时器 setInterval() 实现自动轮播,每隔一定时间切换到下一个轮播项。 以下是一个简单的前端手写轮播图代码示例: ```html <div class="carousel-container"> <div class="carousel-item active"> <img src="image1.jpg" alt="image1"> <div class="carousel-caption"> <h3>Image 1</h3> <p>Description 1</p> </div> </div> <div class="carousel-item"> <img src="image2.jpg" alt="image2"> <div class="carousel-caption"> <h3>Image 2</h3> <p>Description 2</p> </div> </div> <div class="carousel-item"> <img src="image3.jpg" alt="image3"> <div class="carousel-caption"> <h3>Image 3</h3> <p>Description 3</p> </div> </div> <div class="carousel-item"> <img src="image4.jpg" alt="image4"> <div class="carousel-caption"> <h3>Image 4</h3> <p>Description 4</p> </div> </div> <div class="carousel-item"> <img src="image5.jpg" alt="image5"> <div class="carousel-caption"> <h3>Image 5</h3> <p>Description 5</p> </div> </div> <div class="carousel-prev"></div> <div class="carousel-next"></div> </div> ``` ```css .carousel-container { position: relative; width: 800px; height: 500px; overflow: hidden; } .carousel-item { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; transition: all 1s ease-in-out; } .carousel-item.active { opacity: 1; } .carousel-item img { width: 100%; height: 100%; object-fit: cover; } .carousel-caption { position: absolute; bottom: 0; left: 0; width: 100%; padding: 20px; background-color: rgba(0, 0, 0, 0.5); color: #fff; font-size: 24px; line-height: 1.5; } .carousel-prev, .carousel-next { position: absolute; top: 50%; transform: translateY(-50%); width: 50px; height: 50px; background-color: rgba(0, 0, 0, 0.5); cursor: pointer; } .carousel-prev { left: 0; } .carousel-next { right: 0; } ``` ```javascript const carouselContainer = document.querySelector('.carousel-container'); const carouselItems = document.querySelectorAll('.carousel-item'); const prevBtn = document.querySelector('.carousel-prev'); const nextBtn = document.querySelector('.carousel-next'); let currentIndex = 0; function showItem(index) { carouselItems.forEach(item => { item.classList.remove('active'); }); carouselItems[index].classList.add('active'); } prevBtn.addEventListener('click', () => { currentIndex--; if (currentIndex < 0) { currentIndex = carouselItems.length - 1; } showItem(currentIndex); }); nextBtn.addEventListener('click', () => { currentIndex++; if (currentIndex > carouselItems.length - 1) { currentIndex = 0; } showItem(currentIndex); }); setInterval(() => { currentIndex++; if (currentIndex > carouselItems.length - 1) { currentIndex = 0; } showItem(currentIndex); }, 5000); ``` 这是一个简单的轮播图实现,具体的实现方式可以根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳当空丶赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值