console命令有关
console.table()
使用console.table()
可以将一些网络请求获取的数据以表格的形式展现出来方便调试。比console.log()
更直观。
console.group(),groupEnd(),groupCollapsed()
可以使用group()
和groupEnd()
快速打印一个分组信息。使用group()打印的分组信息默认展开。 而使用groupCollapsed()默认将分组折叠显示
let label = 'Package'
console.group(label)
console.log("one")
console.log("two")
console.log("three")
console.groupEnd(label)
console.dir()
可以使用console.dir()
快速打印一个对象的属性信息
console.time()和timeEnd()
可以使用console.time()
和timeEnd()
进行计时统计,如下代码,time()
告诉浏览器启动计时,timeEnd()
告诉浏览器停止计时,代码主体死循环两秒钟。
console.time('loop') //loop相当于一个标签或变量,做打印提示信息用
const start = Date.now()
while (Date.now() - start < 2000) { }
console.timeEnd('loop')
console.count()和console.countReset()
可以使用console.count()
快速统计指定标签调用的次数,如果为指定标签,则默认为default
标签。
const start = Date.now()
while (Date.now() - start < 20) {
console.count('loop')
}
console.countReset('loop') //清空该标签的统计次数,不清空则下次统计从上次标签断点处开始计算
console.trace()
console.trace()
打印堆栈跟踪,当开发中存在一个函数被多次调用的时候,控制台未报错,但是达不到预期效果的时候,可以查看它的调用情况,就可以使用该打印信息。
如下代码中,console.trace()
位于b函数内部,所以先打印b函数,而b函数又是被a函数调用,所以接着打印a,最后a函数是被全局调用的,所以最后打印全局匿名。
function b() {
console.trace()
}
function a() {
b()
}
a()
给输出语句添加样式
如下代码中定义了一个样式变量,想要应用到输出语句上,首先需要在打印的内容消息前添加%c
代表后面的内容需要被css样式形式,而修饰的样式在第二个参数中存放。
const style = `
padding:5px;
background-color:skyblue;
border:1px solid #333;
font-size:2em;
`
console.log(`%c今天是个好日子`, style)
下面这个是在控制台打印输出,非页面显示
控制台Performance性能优化面板认识
这里是一个测试案例性能分析地址
默认情况下,需要点击红色区域进行录制,录制的结果被分析显示在下方面板中显示。
首先为开启性能优化的时候,性能分析报告的首部会出现红色线条,这就代表代码存在性能问题,且红色线是由多个小块组成,每一个块代表一个js从开始执行到完成渲染的时间。
在往下看会出现该区域,黄色代表js执行,紫色代表渲染执行,绿色代表浏览器绘制执行。会发现紫色占用时间大,代表时间可能出现在渲染过程中。 同时该区域表达的情况也可以从底部的圆形图查看显示具体的信息。
该区域是屏幕快照,需要打开才会显示,该区域代表某一刻执行的显示状态。
在往下就是一些子菜单,其中主要(main)是最常用的渲染主线程,可以查看渲染过程中出现的问题
在渲染主线程中,通过事件循环不断执行任务,如图每一个小方块就是一个任务。如果每一个任务出现了性能问题耗时过长,则在右侧顶部会出现一个红色的三角提示信息。 鼠标悬停的时候可以查看任务耗时。
选中时间线缩小,而不是像上图所示查看完整的信息。在下图中,纵向是一个推栈调用信息,意思就是任务中触发了动画帧已触发,在这里又触发了函数调用,函数调用又会触发app.update()去执行。
当点击了app.update()
区域的时候,可以查看底部调用树
中的报告情况。这里就是一个推栈展开调用情况,可以查看每一个区域所耗的时长,并且还附加了该区域的代码定位。
点击旁边的文件定位跳转,会发现在源代码中已经标注了问题代码部分所耗的时长。会发现引起回流的主要出现在offsetTop
属性的读取上。当修改为一个变量接收offsetTop
后,只会在页面初始化的时候加载一次引起一次回流。后期触发offsetTop
发生改变,否则不会回流。
单行与多行文本省略号
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
默认行为相关
下面是基本代码合在一起,默认整个页面是可以滚动的,但是当用户点击显示按钮的时候让视频区域显示出来的时候,希望可以观看视频,而滚动条不要滚动。但是发现在不添加{ passive: false }
的情况下,滚动条在视频显示的时候依旧可以滚动,这不符合预期。
这个地方涉及到性能问题,滚动事件处理起来很耗时间,额外处理默认行为就更加困难了,如果本身不打算处理默认行为,那么passive:true
即不写为该默认值。而这个时候再次去使用默认行为控制台就会报错提示。如果设置passive:false
即告诉浏览器我可能会做一些处理默认行为的事情通知你一下做好准备。这样子就不会出现问题,并且程序可以按照预期的执行。
<style>
body {
height: 3000px;
}
.box {
display: none;
margin: 0 auto;
width: 500px;
height: 500px;
border: 1px solid #333;
background-color: skyblue;
}
</style>
</head>
<body>
<button class="btn">显示</button>
<button class="btn2">隐藏</button>
<div class="box">我是视频区域</div>
<script>
let box = document.querySelector('.box')
let btn = document.querySelector(".btn")
let btn2 = document.querySelector(".btn2")
btn.onclick = function () {
box.style.display = 'block'
window.addEventListener("wheel", wheelHandler, { passive: false })
}
function wheelHandler(e) {
// 视频区域显示就禁用滚动事件
e.preventDefault();
}
btn2.onclick = function () {
box.style.display = 'none'
// 移除滚轮的事件监听
window.removeEventListener("wheel", wheelHandler)
}
</script>
未添加passive选项的时候如图
计时器问题
当打开一个页面,在该页面中使用到计时器的时候,如果这个时候切换到其他页面中,则浏览器会认为该标签页被隐藏了,为了提示效率,不需要按照计时器指定的时间去执行代码,将时间调成浏览器给定的时间。
setInterval(() => {
console.log("500毫秒执行一次");
}, 500)
setInterval(() => {
console.log("2秒钟执行一次")
}, 2000)
如下图,第一张图未切换标签页的时候,规律性的打印输出,但是第二章图切换的时候,会发现打印输出结果各不相同
如果想保持规律性打印输出,可以在页面切换的时候就停止定时器,需要使用到visibilitychange
事件,出于兼容性原因,请确保使用 document.addEventListener
而不是 window.addEventListener
来注册回调。document.visibilityState 当前页面为visibility,切换隐藏时为hidden
。修改代码如下后,切换标签页的时候也能保持打印输出符合预期。
let time1 = setInterval(() => {
console.log("500毫秒执行一次");
}, 500)
let time2 = setInterval(() => {
console.log("2秒钟执行一次")
}, 2000)
document.addEventListener('visibilitychange', () => {
// document.visibilityState 当前页面为visibility,切换隐藏时为hidden
if (document.visibilityState === 'hidden') {
clearInterval(time1);
clearInterval(time2);
} else {
time1 = setInterval(() => {
console.log("500毫秒执行一次");
}, 500)
time2 = setInterval(() => {
console.log("2秒钟执行一次")
}, 2000)
}
})
鼠标事件
document.addEventListener('click', function (e) {
...........
})
如下代码功能一样,都是鼠标距离可视视口区域左侧或顶部的位置
e.x, e.clientX, e.y, e.clientY
鼠标到页面左侧或顶部的距离,包含了滚动条滚出去的页面
e.pageX, e.pageY
鼠标到屏幕左侧的距离
e.screenX
当前事件和上一个事件之间移动距离的水平差值
e.movementX
鼠标到目标元素e.target
左侧的距离
e.offsetX
Flip动画
传统的transition
和animation
是针对css属性改变的时候去做某些事情,如果改变的是DOM结构
F:first,记录每个元素的起始位置
L:last,记录每个元素结束的位置
I:invert,记录反转元素到起始的距离(起始位置-结束位置,排除为0的情况)
P:play,播放动画回到结束位置
重点是需要知道下面这句话
Element 接口的 animate()
方法是创建一个新的 Animation 的便捷方法,将它应用于元素,然后运行动画。它将返回一个新建的 Animation 对象实例
代码如下
<button>切换</button>
<div class="box">
<div class="item">我是第1个</div>
<div class="item">我是第2个</div>
<div class="item">我是第3个</div>
<div class="item">我是第4个</div>
<div class="item">我是第5个</div>
</div>
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 300px;
height: 300px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.item {
padding: 4px;
border: 1px solid red
}
</style>
<script>
let btn = document.querySelector('button')
let box = document.querySelector('.box')
btn.addEventListener('click', () => {
// 记录位置
record(box)
// 改变元素结构
change()
// flip动画实现
move(box)
})
function record(container) {
for (let i = 0; i < container.children.length; i++) {
let dom = container.children[i]
let rect = dom.getBoundingClientRect() // 获取修改前DOM的大小和相对于视口的位置
// 添加自定义属性绑定起始位置
dom.startX = rect.x
dom.startY = rect.y
}
}
function change() {
const childrens = [...box.children] //转换为真数组
for (let i = 0; i < childrens.length; i++) {
let children = childrens[i] //每一个元素
let j = Math.floor(Math.random() * childrens.length) //随机改变元素位置,固定随机数范围
if (i != j) { //不是同一个元素的时候改变位置
let nextChildren = children.nextElementSibling
// 改变DOM结构
box.insertBefore(children, childrens[j]) //移动的同时会删除上一个children,保持只有一个该元素
box.insertBefore(childrens[j], nextChildren)
}
}
}
function move(container) {
for (let i = 0; i < container.children.length; i++) {
let dom = container.children[i] //修改后的元素
let rect = dom.getBoundingClientRect() //获取修改后的DOM的大小和相对于视口的位置
let cruX = rect.x, curY = rect.y
dom.animate([
// 起始css属性
{ transform: `translate(${dom.startX - cruX}px,${dom.startY - curY}px)` },
//结束css属性
{ transform: `translate(0px,0px)` }
], {
duration: 500
})
}
}
</script>
过渡结束事件多次触发
当一个DOM元素添加了多个过渡效果时候,如果希望在结束过渡效果的时候只会执行一次,可以给事件监听绑定第三个参数once
dom.addEventListener("transitioned", function () {
}, {
once: true
})
高亮关键字
用户输入关键字查询后,显示查询的结果中,将关键字高亮显示。总的思路就是先筛选数据后进行高亮显示
<input type="text">
<div>
</div>
<script>
let input = document.querySelector('input')
let box = document.querySelector('div')
let arr = ["赵云", "韩信", "墨子", "张飞", "张铁", "老夫子"]
// 初始化展示
box.innerHTML = arr.map((item) => {
return `<p>${item}</p>`
}).join('')
input.addEventListener('change', () => {
let key = input.value.trim() //获取内容
let reg
if (key) {
reg = new RegExp(key, 'g') //定义正则,全局匹配,输入的关键字
}
box.innerHTML = arr.filter((item) => {
if (item.includes(input.value)) {
return item
}
}).map(item => {
let name = item
if (reg) {
name = name.replace(reg, function () {
return `<strong style="color: green">${key}</strong>`
})
}
return `<p>${name}</p>`
}).join('')
})
</script>
图片延迟加载
图片加载是一个性能优化的手段,可以大大提高页面的加载响应速度。像电商网站中必备图片优化处理。如大屏展示页面中,当未完全加载到图片之前,先显示loading图。对于首屏的轮播图等地方,优先等待首屏的其他数据加载完成后才会加载真实图片。页面初次加载的时候并非将所有的图片加载回来显示,不在当前可视窗口区域的图片暂时不进行加载,当用户滚动滚轮触发必要条件的时候,才会去加载对应的图片显示。
对图片进行延迟加载一方面可以提高页面的加载速度,另一方面也可以减少用户的网络流量。
首屏加载
<style>
.imageLazyBox {
width: 367px;
height: 500px;
background: url(./3.gif)no-repeat center center #eee;
}
.imageLazyBox img {
width: 100%;
height: 100%;
/* 某些浏览器,如果图片src没有值,则会默认显示一些样式,这不好看。
因此建议将图片元素隐藏 有两种方案
1:display:none 但是会引起回流重绘
2:opacity透明度处理*/
opacity: 0;
transition: opacity 0.3s;
}
</style>
</head>
<body>
<!-- 将图片放置于容器中,给容器设置默认显示占位图(如果不设置容器,那么图片加载完成的时候会突然出现)
初始的时候,src属性不立刻赋值,而是通过自定义属性保存图片地址-->
<div class="imageLazyBox">
<img src="" image-lazy="https://cdn2.thecatapi.com/images/5ef.jpg">
</div>
<script>
function lazyAPI() {
let imageLazyBox = document.querySelector(".imageLazyBox")
let img = imageLazyBox.querySelector('img')
let imageLazy = img.getAttribute('image-lazy')
img.onload = () => {
img.style.opacity = 1
}
img.src = imageLazy
img.removeAttribute("image-lazy")
}
// 等待其他资源加载完成后执行图片获取操作
//1 window.onload = lazyAPI
// 2 定时器
setTimeout(() => {
lazyAPI()
}, 1000)
非可视区域图片延迟加载
将上面的窗口高度调高,且修改代码。
function offset(element) {
let l = element.offsetLeft
let t = element.offsetTop
let p = element.offsetParent
// console.log(l, t, p, element);
while (p && p.tagName != 'body') {
if (!/MSIE 8/.test(navigator.userAgent)) {
//处理浏览器兼容
l += p.clientLeft
t += p.clientTop
}
l += p.offsetLeft
t += p.offsetTop
p = p.offsetParent //结束条件
}
return {
top: t,
left: l
}
}
function lazyAPI(imageLazyBox) {
let img = imageLazyBox.querySelector('img')
let imageLazy = img.getAttribute('image-lazy')
img.onload = () => {
img.style.opacity = 1
}
img.src = imageLazy
img.removeAttribute("image-lazy")
}
let imageLazyBox = document.querySelector(".imageLazyBox")
window.onscroll = () => {
//距离父元素的距离+自身高度
let A = offset(imageLazyBox).top + imageLazyBox.offsetHeight
// 滚动出去的距离+屏幕的高度
let B = document.documentElement.scrollTop + document.documentElement.clientHeight
if (A <= B) {
lazyAPI(imageLazyBox)
}
}
</script>
这个时候滚动页面的时候会出现一个问题,即第一次图片出现的时候,已经执行了一次lazyAPI
函数,并将自定义属性移除了,但是之后的每次滚动都会进入该函数中,但是却没有自定义属性,所以就会报错。所以需要进行判断。方法有如下几种
//方法确定,滚动事件每次都会执行函数体,浪费性能
function lazyAPI(imageLazyBox) {
......
let imageLazy = img.getAttribute('image-lazy')
if (!imageLazy) return
}
//使用变量控制,缺点是每次都会进入滚动事件的回调,但不会执行过多的代码
let flag = true
function lazyAPI(imageLazyBox) {
//结束位置
flag = false
}
window.onscroll = () => {
if (!flag) return //函数首部判断
....
}
//方法三:一旦图片进入完成加载,就取消事件监听
function lazyAPI(imageLazyBox) {
//函数末尾执行
flag = false
if (!flag) {
window.removeEventListener('scroll', fun)
}
}
//将回调函数封装成函数,方便取消事件监听
window.addEventListener('scroll', fun)
function fun() {
//距离父元素的距离+自身高度(想图片出现一半就自身高度一半,想图片一出现就取消自身高度)
let A = offset(imageLazyBox).top + imageLazyBox.offsetHeight
// 滚动出去的距离+屏幕的高度
let B = document.documentElement.scrollTop + document.documentElement.clientHeight
if (A <= B) {
lazyAPI(imageLazyBox)
}
}
但是这样子还是有一个缺点,就是滚动事件触发的太频繁了,可以设置节流,固定时间内只允许触发一次。
window.addEventListener('scroll', throttle(fun))
// 节流
function throttle(fun, delay = 500) {
let previous = 0
let timer = null
return function (...arg) {
let nowTime = new Date()
let remaining = delay - (nowTime - previous)
if (remaining <= 0) {
clearTimeout(timer)
timer = null
previous = nowTime
fun.call(this, ...arg)
} else if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
previous = new Date()
fun.call(this, ...arg)
}, remaining)
}
}
}
借助getBoundingClientRect()实现
接下来使用第二种方法,左图是上面代码的做法
Element.getBoundingClientRect()
方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。
- 容器在视口中
- 容器不在视口中
function fun() {
console.log("进入了");
if (!flag) return
//若 -imageLazyBox.offsetHeight 则为一出现就获取
//若 -imageLazyBox.offsetHeight/2 则为到一半出现就获取
//图片完全出现获取url
let A = imageLazyBox.getBoundingClientRect().bottom
let B = document.documentElement.clientHeight //获取视口高度
if (A <= B) {
lazyAPI(imageLazyBox)
}
}
借助 IntersectionObserver构造函数实现
IntersectionObserver
接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。
基本格式:创建一个新的 IntersectionObserver 对象,当其监听到 一个或多个 目标元素的可见部分(的比例)超过了一个或多个阈值(threshold)时(默认阈值为0),会执行指定的回调函数。调用该对象身上的observe(DOM)
设置监听对象,调用unobserve(DOM)
设置解绑监听。
这里需要注意,执行的触发时机是可以更改的,默认情况下是下代码中的注释部分
/*
1:监听某些DOM元素与浏览器可视窗口的交叉位置的状态信息
2:创建一个 IntersectionObserver 对象,当监听的DOM元素和浏览器
可视窗口的交叉位置状态发送变化,便回执行回调函数。其中changes参数是一个数组集合。
集合中的每一个元素对应一个DOM元素和可视窗口的交叉位置信息。
*:boundingClientRect 等价于 Element.getBoundingClientRect()
*:target为监听的目标元素
*:isIntersecting为目标元素是否可视窗口的交叉了,为布尔值
3:执行时机
*:页面初始化的时候会执行一次监听。
*:容器初次进入可视窗口的时候会执行一次 (触发不频繁)
*:容器完全离开可视窗口的时候会执行一次
4:配置参数
*:root:所监听对象的具体祖先元素。如果未传入值或值为null,则默认使用顶级文档的视窗(一般为html)。
*:rootMargin:计算交叉时添加到根(root)边界盒bounding box的矩形偏移量,可以有效的缩小或扩大根的判定范围从而满足计 算需要。所有的偏移量均可用像素(px)或百分比(%)来表达, 默认值为"0px 0px 0px 0px"。
*:threshold:一个包含阈值的列表, 按升序排列, 列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会触发callback。默认值为0。
*/
let ob = new IntersectionObserver(changes => {
console.log(changes);
})
//监听元素
ob.observe(imageLazyBox)
//解绑监听
ob.unobserve(imageLazyBox)
修改触发时机,设置配置参数threshold
,基本格式:new IntersectionObserver(回调函数,配置对象)
。
其中threshold
传入一个数组,数组的值如下:0代表容器一进入可视区域就触发,0.5代表容器进入可视区域一半的时候触发,1代表容器完全进入可视窗口的时候触发一次。离开的时候也是这样子。
let ob = new IntersectionObserver(changes => {
console.log(changes[0]);
}, {
threshold: [0, 0.5, 1]
})
借助IntersectionObserver
设置图片延迟加载
let flag = true
function lazyAPI(imageLazyBox) {
let img = imageLazyBox.querySelector('img')
let imageLazy = img.getAttribute('image-lazy')
// if (!imageLazy) return
img.onload = () => {
img.style.opacity = 1
}
img.src = imageLazy
img.removeAttribute("image-lazy")
flag = false
// if (!flag) {
// window.removeEventListener('scroll', throttle(fun))
// }
}
let imageLazyBox = document.querySelector(".imageLazyBox")
let ob = new IntersectionObserver(changes => {
let item = changes[0] //只有一个监听元素
if (item.isIntersecting) { //完全进入后会执行一次
lazyAPI(item.target)
ob.unobserve(item.target) //移除监听
}
}, {
threshold: [1]
})
ob.observe(imageLazyBox)
使用原生img属性loading
loading=lazy
:延迟加载图像,直到它和视口接近到一个计算得到的距离(由浏览器定义)。目的是在需要图像之前,避免加载图像所需要的网络和存储带宽。这通常会提高大多数典型用场景中内容的性能。
<img src="https://cdn2.thecatapi.com/images/5ef.jpg" loading="lazy">
瀑布流效果
案例分析:存在一个布局容器,容器中分为固定列数,但是每一列宽度一致,即图片宽度一致,但是允许图片的高度不同。初始情况下,获取图片数据,将顺序图片填满第一行,由于图片高度不统一可能会造成两边高中间矮的效果,如果下次填充还是按照顺序从左到右依次填充,假设在最坏情况下,图片每次填充一行都是两边高中间矮,那么就会造成视觉效果很难看。所以需要采用瀑布流布局效果,将每次需要填充的图片数据先按照高度排序,然后每次将最高的图片插入到已有容器列中最矮的一列,将最矮的图片插入到已有列数中最高的一列(也可以不对图片进行高度排序,直接顺序将每次插入的图片插入到列最矮的地方)。这里需要处理页面可视区域的布局情况和非可视区域的布局情况,对于非可视区域需要进行图片懒加载效果,同时添加滚动条刷新不断获取数据的操作。
封装如图所示结构
data.json中封装模拟数据,格式如下,其中所有的宽度一致,高度改变
[
{
"id": 1,
"pic": "images/8.jpg",
"width": 300,
"height": 433,
"title": "泰勒·斯威夫特(Taylor Swift) , 1989年12月13日出生于美国宾州,美国歌手、演员。2006年出道,同年发行专辑《泰勒·斯威夫特》,该专辑获得美国唱片业协会的白金唱片认证",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
....
]
<div class="container">
<div class="column">
<!-- <div class="card">
<a href="#">
<div class="lazyImageBox" style="height: 280px;">
<img src="" data-image="./3.gif">
</div>
<p>我来了</p>
</a>
</div> -->
</div>
<div class="column">
</div>
<div class="column">
</div>
</div>
html,
body {
background: #d6d7db;
}
* {
box-sizing: border-box;
}
.container {
margin: 20px auto;
width: 760px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.container .column {
width: 240px;
}
.card {
margin-bottom: 10px;
padding: 5px;
background: #fff;
box-shadow: 3px 3px 10px 0 #222;
}
.card a {
display: block;
text-decoration: none;
}
.card a .lazyImageBox {
/* height,图片不显示的时候需要根据图片的高度设置,撑开容器占位 */
background: url(./3.gif) no-repeat center center #f4f4f4;
background-size: cover;
overflow: hidden;
}
.card a .lazyImageBox img {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity .3s;
}
.card a p {
margin-top: 5px;
color: #222;
font-size: 12px;
line-height: 20px;
}
使用getBoundingClientRect方法实现
let container = document.querySelector('.container')
let columns = document.querySelectorAll('.column')
// 获取所有的容器
let lazyImgBoxs = null
// 转换类数组/伪数组为真数组
columns = Array.from(columns)
// 节流
function throttle(fun, delay = 500) {
let previous = 0;
let timer = null;
return function (...args) {
let nowTime = new Date()
let remaining = delay - (nowTime - previous)
if (remaining <= 0) {
clearTimeout(timer)
timer = null
previous = nowTime
fun.call(this, ...args)
} else if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
previous = new Date()
fun.call(this, ...args)
}, remaining)
}
}
}
// 初始化获取数据
/* 需要注意:如果使用浏览器默认的打开浏览,是基于file协议,
不能发送ajax请求,需要安装live serve插件使用该插件打开 */
const queryData = () => {
let data = []
//通过xhr获取数据
let xhr = new XMLHttpRequest;
//该文件在index.html文件中引入,所以地址是基于html页面引入json文件的,false代表同步获取数据
xhr.open('GET', './data.json', false)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText)
}
}
xhr.send()
return data
}
let flag = true
// 初始化页面
const initHTML = data => {
// 当给图片绑定高度的时候会发现,图片清晰度丢失了,这是因为指定容器的宽为230,但是最终返回的图片宽度均为300
// 所以需要按照比例进行缩放
data = data.map(item => {
// AW/AH = BW/BH
let AW = 230, BW = item.width, BH = item.height
let AH = AW / (BW / BH)
item.width = AW
item.height = AH
return item
})
for (let i = 0; i < data.length; i += 3) {
// 每三行为一组
let group = data.slice(i, i + 3)
if (flag) {
flag = false
} else {
// 根据每一组中的图片高度进行小-大排序 升序
group.sort((a, b) => a.height - b.height)
}
// 对每一列进行排序 由大-小排序 降序
columns.sort((a, b) => {
return b.offsetHeight - a.offsetHeight
})
// 根据每行图片数量插入页面中
group.forEach((item, index) => {
let card = document.createElement('div')
card.className = 'card'
card.innerHTML = `<a href="#">
<div class="lazyImageBox" style="height: ${item.height}px;">
<img src="" data-image="${item.pic}">
</div>
<p>${item.title}</p>
</a>`
columns[index].appendChild(card)
})
}
lazyImgBoxs = Array.from(document.querySelectorAll('.lazyImageBox'))
}
// 单张延迟加载函数
const lazyImg = lazyImgBox => {
let img = lazyImgBox.querySelector('img')
let dataImagUrl = img.getAttribute('data-image')
img.onload = () => {
img.style.opacity = 1
}
img.src = dataImagUrl
lazyImgBox.isLoad = true //给每一个图片绑定一个完成标识
}
// 遍历所有的图片实现单张图片加载
const lazyImgs = () => {
// 必须先让lazyImgBoxs获取到所有图片容器
lazyImgBoxs.forEach(item => {
if (item.isLoad) return
// 判断执行时机
// 获取视口高度
let B = document.documentElement.clientHeight
let A = item.getBoundingClientRect().bottom
if (A <= B) {
lazyImg(item)
}
})
}
let data = queryData() //初始化获取数据
initHTML(data) //初始化渲染页面,让加载容器显示
setTimeout(lazyImgs, 250)
window.addEventListener('scroll', throttle(() => {
lazyImgs()
//通过监听滚动条是否到底部实现下拉刷新获取更多数据
let HTML = document.documentElement
// +50是控制误差范围
if ((HTML.clientHeight + HTML.scrollTop + 50) >= HTML.scrollHeight) {
// HTML.scrollHeight是元素内容的高度
data = queryData()
initHTML(data)
}
}))
使用IntersectionObserver构造函数实现
多添加一个容器用于实现监听容器是否到底部加载更多效果,当前也可以采用滚动条实现
<div class="container">
<div class="column">
<!-- <div class="card">
<a href="#">
<div class="lazyImageBox" style="height: 280px;">
<img src="" data-image="./3.gif">
</div>
<p>我来了</p>
</a>
</div> -->
</div>
<div class="column">
</div>
<div class="column">
</div>
</div>
<div class="loadMore"></div>
.loadMore {
height: 10px;
}
let container = document.querySelector('.container')
let columns = Array.from(document.querySelectorAll('.column'))
// 获取所有的容器
let lazyImgBoxs = null
// 获取加载更多的容器
let loadMore = document.querySelector('.loadMore')
// 初始化获取数据
/* 需要注意:如果使用浏览器默认的打开浏览,是基于file协议,
不能发送ajax请求,需要安装live serve插件使用该插件打开 */
const queryData = () => {
let data = []
//通过xhr获取数据
let xhr = new XMLHttpRequest;
//该文件在index.html文件中引入,所以地址是基于html页面引入json文件的,false代表同步获取数据
xhr.open('GET', './data.json', false)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText)
}
}
xhr.send()
return data
}
let flag = true
// 初始化页面
const initHTML = data => {
// 当给图片绑定高度的时候会发现,图片清晰度丢失了,这是因为指定容器的宽为230,但是最终返回的图片宽度均为300
// 所以需要按照比例进行缩放
data = data.map(item => {
// AW/AH = BW/BH
let AW = 230, BW = item.width, BH = item.height
let AH = AW / (BW / BH)
item.width = AW
item.height = AH
return item
})
for (let i = 0; i < data.length; i += 3) {
// 每三行为一组
let group = data.slice(i, i + 3)
if (flag) {
flag = false
} else {
// 根据每一组中的图片高度进行小-大排序 升序
group.sort((a, b) => a.height - b.height)
}
// 对每一列进行排序 由大-小排序 降序
columns.sort((a, b) => {
return b.offsetHeight - a.offsetHeight
})
// 根据每行图片数量插入页面中
group.forEach((item, index) => {
let card = document.createElement('div')
card.className = 'card'
card.innerHTML = `<a href="#">
<div class="lazyImageBox" style="height: ${item.height}px;">
<img src="" data-image="${item.pic}">
</div>
<p>${item.title}</p>
</a>`
columns[index].appendChild(card)
})
}
}
// 单张延迟加载函数
const lazyImg = lazyImgBox => {
let img = lazyImgBox.querySelector('img')
let dataImagUrl = img.getAttribute('data-image')
img.onload = () => {
img.style.opacity = 1
}
img.src = dataImagUrl
lazyImgBox.isLoad = true //给每一个图片绑定一个完成标识
}
//IntersectionObserver监听
let obImg = new IntersectionObserver(changes => {
changes.forEach(item => {
if (item.isIntersecting) {
// 代表当前图片与可视区域交叉
lazyImg(item.target)
// 做完加载就取消当前项的监听
obImg.unobserve(item.target)
}
})
}, { threshold: [1] })
const oblazyImgBoxs = () => {
lazyImgBoxs = Array.from(document.querySelectorAll('.lazyImageBox'))
// 已经监听过的元素不需要重复监听
lazyImgBoxs = lazyImgBoxs.filter(item => !item.isLoad)
// 监听筛选过后的元素
lazyImgBoxs.forEach(item => obImg.observe(item))
}
// 实现滚动底部加载更多
// 主题思想是在底部创建一个盒子,只有该盒子出现在视口区域就代表需要获取更多数据
const obloadMore = new IntersectionObserver(changes => {
if (changes[0].isIntersecting) {
let data = queryData() //初始化获取数据
initHTML(data) //初始化渲染页面,让加载容器显示
oblazyImgBoxs()
}
}, { threshold: [0] })
let data = queryData() //初始化获取数据
initHTML(data) //初始化渲染页面,让加载容器显示
oblazyImgBoxs()
// 监听加载更多
obloadMore.observe(loadMore)
轮播图,左右移动无缝衔接
左右移动版轮播图需要注意如何处理无缝衔接,即每次移动到最后一张图片的时候如第一张图片合理的出现在页面中。处理的方法:将第一张图片克隆到最后位置。每次播放最后一个克隆图片的时候,迅速切换回第一张图片,用户视觉效果中是无法发现的(这个时候不加过渡即可)。
页面效果
<div class="container">
<div class="wrapper">
<div class="slide">
<img src="./images/1.png" alt="">
</div>
<div class="slide">
<img src="./images/2.jpg" alt="">
</div>
<div class="slide">
<img src="./images/3.jpg" alt="">
</div>
<div class="slide">
<img src="./images/4.jpg" alt="">
</div>
<div class="slide">
<img src="./images/5.jpg" alt="">
</div>
<!-- 克隆第一张图片到末尾 -->
<!-- <div class="slide">
<img src="./images/1.png" alt="">
</div> -->
</div>
<!-- 底部分页 -->
<div class="pagination">
<!-- <span class="active"></span>
<span></span>
<span></span>
<span></span>
<span></span> -->
</div>
<!-- 左右箭头 -->
<a href="javascript:;" class="btn btn-prev"><</a>
<a href="javascript:;" class="btn btn-next">></a>
</div>
<script src="./index.js"></script>
(function () {
let container = document.querySelector('.container')
let wrapper = container.querySelector('.wrapper')
let pagination = container.querySelector('.pagination')
let paginationList = null //span
let btnPrev = document.querySelector('.btn-prev')
let btnNext = document.querySelector('.btn-next')
//首先克隆第一张图片到末尾
let first_copy = wrapper.children[0].cloneNode(true)
wrapper.appendChild(first_copy)
// 动态生成分页圆点,但是不包括克隆的节点
for (let i = 0; i < wrapper.children.length - 1; i++) {
let span = document.createElement('span')
if (i === 0) {
span.className = 'active'
}
pagination.appendChild(span)
}
// 页面有span元素了在获取DOM
paginationList = Array.from(pagination.querySelectorAll('span'))
// 先实现自动播放图片切换
let index = 0 //当前播放的图片所以 最大为5 一共六张图片
let timer = null
// 自动播放函数
function autoMove() {
//#region
/* wrapper.style.transitionDuration = '0s'
wrapper.style.left = `0px`
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px`
这些根样式相关的代码,在现代浏览器中并不会立即执行
而是插入到渲染队列中,等待一个函数的所有样式均获取了才执行。
因此在这里造成了后面样式会层叠前面的样式,所以瞬间的过渡效果没有实现,
克隆的图片是平移到第二个图片的。
解决方法,在希望立即执行样式的地方,如浏览器立即渲染,即刷新渲染那队列
*/
//#endregion
if (index >= 5) {
// 迅速移动至第一张图
index = 0
// 希望移除过渡效果,形参瞬间移动首部效果
wrapper.style.transitionDuration = '0s'
wrapper.style.left = `0px`
wrapper.offsetLeft; //触发浏览器的重绘,以确保样式更改立即生效
}
index++
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px` //移动left值,且自动往左移动,为负数
paginationHandle()
}
// 处理分页器同步active
function paginationHandle() {
let temp = index //记录下标
// temp的值可能为5,即克隆的图片,那么需要保持克隆的图片也激活第一张图片的样式
temp === 5 ? temp = 0 : null //判断是否为5,否则不执行任何操作
paginationList.forEach((item, index) => {
// 清除所有的active
item.classList.remove('active')
if (temp === index) {
item.classList.add('active')
}
})
}
timer = setInterval(autoMove, 1500);
// 鼠标移入清楚定时器
container.addEventListener('mouseenter', () => {
clearInterval(timer)
})
// 鼠标离开开启定时器
container.addEventListener('mouseleave', () => {
timer = setInterval(autoMove, 1500)
})
// 点击分页器绑定点击事件
paginationList.forEach((item, i) => {
item.onclick = () => {
// 如果点击为当前项,不做任何操作,并排除掉克隆的图片,因为没有为其创建span,否则有bug
if (i === index || index === 5 && i === 0) return
// i的范围0-4,已经排除了克隆的
index = i
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px`
paginationHandle()
}
})
// 右箭头点击事件
btnNext.onclick = autoMove
// 左箭头点击事件
btnPrev.onclick = () => {
if (index <= 0) {
// 迅速移动至第一张图
index = 5
// 希望移除过渡效果,形参瞬间移动首部效果
wrapper.style.transitionDuration = '0s'
wrapper.style.left = `${-index * 564}px`
wrapper.offsetLeft; //触发浏览器的重绘,以确保样式更改立即生效
}
index--
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px` //移动left值,且自动往左移动,为负数
paginationHandle()
}
})()
环形旋转
效果图
<style>
.container {
width: 300px;
height: 300px;
margin: 50px auto;
border: 2px solid #333;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
animation: move 20s linear infinite
}
.item {
position: absolute;
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
}
.item img {
width: 100%;
height: 100%;
/* 容器顺时针移动,图片逆时针移动,保持图片位置不选择 */
animation: move 20s linear infinite reverse
}
@keyframes move {
to {
transform: rotate(360deg);
}
}
</style>
<div class="container">
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
</div>
<script>
let container = document.querySelector('.container')
let items = document.querySelectorAll('.item')
let r = container.clientHeight / 2 //获取半径
let count = items.length //获取图片数量
let pieceDeg = 360 / count //获取每张图片的角度
for (let i = 0; i < count; i++) {
let t = i * pieceDeg //每张图片的角度
// 根据角度转换为弧度
t = (Math.PI / 180) * t
// 借助三角函数,需要操作弧度而非角度,所以需要先转换
// Math.sin(t) 返回角度 t 的正弦值,而 Math.cos(t) 返回角度 t 的余弦值
// x 和 y 分别代表了图片圆心的水平坐标和垂直坐标。
let x = Math.sin(t) * r, y = -Math.cos(t) * r
items[i].style.transform = `translate(${x}px, ${y}px)`
}
</script>
保持宽高比
aspect-ratio
css属性可以设置一个图片的宽高在缩放的时候保持比例缩放。如给一个容器设置:aspect-ratio: 16/9;
缩放比例
按钮边框颜色旋转
<button>按钮边框</button>
首先搭建静态结构,这里需要使用到子绝父相,所以添加定位
:root {
--turn: 360deg;
}
button {
margin: 100px;
padding: 10px 15px;
color: #0ebeff;
background: #000;
border: none;
border-radius: 10px;
outline: none;
/* border 和 outline border 和 outline 很类似,但有如下区别:
outline 不占据空间,绘制于元素内容周围。
根据规范,outline 通常是矩形,但也可以是非矩形的。; */
/* 初始设置用来查看效果,最终取消该效果 */
outline: 4px solid #fff;
/* 给元素设置定位 */
position: relative;
z-index: 1;
}
然后给该元素添加一个before伪元素
,效果如下
button::before {
content: "";
position: absolute;
width: 200px;
height: 200px;
background-color: red;
top: 50%;
left: 50%;
/* 保持背景在最低下 */
z-index: -1;
}
然后设置一个动画,并给该伪元素添加,但是添加完成后,伪元素是沿着中心旋转的,这个时候需要设置旋转点为左上方的顶点
button::before {animation: move 10s linear infinite;}
@keyframes move {
to {
transform: rotate(var(--turn));
}
}
设置如下
transform-origin: 0 0;
设置完后给按钮设置溢出隐藏,这样子外部红色区域就看不见了
overflow: hidden;
之后再次创建一个after伪元素
button::after {
/* 将内部红色区域遮住部分,只保留类似边框区域显示旋转 */
content: '';
position: absolute;
/* 设置自身大小,空出的区域为边框旋转效果区域 */
width: calc(100% - 5px);
height: calc(100% - 5px);
background-color: green;
/* 为了让该元素在button中显示在合理位置 */
top: 2px;
left: 2px;
border-radius: 10px;
;
/* 保持层级在before元素之上 */
z-index: -1;
}
添加完该效果后如图,发现有一个红色区域一直在移动,之后把不需要的颜色取出即可
将父元素的该属性注释,并将绿色背景修改为黑色
outline: 4px solid #fff;
最终效果如下
倾斜按钮
<button class="btn">倾斜按钮</button>
.btn {
width: 100px;
height: 35px;
color: white;
background-color: #000;
border: none;
position: relative;
}
给元素设置圆角
/* 设置左上和右下的圆角 */
border-radius: 15px 0;
设置伪元素,将伪元素设置如图所示位置
.btn::before {
content: '';
position: absolute;
bottom: 0;
left: -15px;
width: 15px;
height: 15px;
background-color: red;
}
然后设置:radial-gradient(blue 5px, red 5px)
css属性,该属性代表设置径向渐变效果,不设置中心位置,默认从一个图像的中心点开始,扩散开始颜色为蓝色,向外扩散到5px,接着剩下的扩散全部为红色。
接着修改扩散圆心的坐标,让其从左上角开始扩散,扩散大小为容器的宽度即可,最后只需要将蓝色设置为透明度即可
background: radial-gradient(circle at 0 0, blue 15px, #000 5px)
右上角该效果一样设置
background: radial-gradient(circle at 100% 100%, transparent, 15px, #000 5px)
最后给按钮设置倾斜即可:transform: skew(-30deg);
设置文字边框
<h1>hello world</h1>
h1 {
color: #000;
text-shadow:
1px 0 #fff,
1px 1px #fff,
1px -1px #fff,
0 1px #fff,
0 -1px #fff,
-1px 0 #fff,
-1px 1px #fff,
-1px -1px #fff;
}
卡片翻转
<div class="card">
<img class="face" src="./3.gif" alt="">
<div class="back">
<h1>我来了</h1>
</div>
</div>
设置基本样式,通过定位让两个容器层叠,保证图片优先显示
.card {
/* 3d效果 */
perspective: 500px;
width: 300px;
height: 300px;
margin: 100px auto;
position: relative;
}
img {
position: absolute;
width: 100%;
height: 100%;
}
.back {
z-index: -1;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
background-color: skyblue;
}
然后给图片设置翻转效果:注意backface-visibility: hidden;
属性功能,这样子当图片翻转过来的时候,显示不可见,以下是鼠标经过的效果。
.face {
transition: .5s;
/* 设置一个元素背面不需要看见,设置为不可见 */
backface-visibility: hidden;
}
.card:hover .face {
transform: rotateY(180deg);
}
这个时候可以利用同样的思路设置背面文字效果
.back {
.....
transform: rotateY(-180deg); //保持翻转之间连续,将文字先翻转,让背面显示,保持文字显示后不是倒着的
backface-visibility: hidden; //背面隐藏
transition: .5s;
}
.card:hover .back {
transform: rotateY(0);
}
原形放大效果
<div class="avatar"></div>
.avatar {
margin: 100px auto;
width: 200px;
height: 200px;
background: url(./3.gif) no-repeat center center;
border-radius: 50%;
position: relative;
}
.avatar::before,
.avatar::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar::before {
/* 遮罩层 */
background-color: rgba(0, 0, 0, .3);
}
.avatar::after {
/* 使该元素获取父元素的值,可以理解为继承使用 ,将父元素的背景图使用*/
background: inherit;
transition: 1s;
}
设置完基本样式后,after伪元素在最顶层显示,其次before伪元素
之后利用裁剪属性:clip-path:circle(radius at position)
,首先设置半径,如果设置为百分比,则半径是相对于父元素宽度计算。将after伪元素进行裁剪,裁剪的区域内可以,区域外不显示。
.avatar::after {
....
/* 裁剪 ,从原心开始裁剪向外扩展,裁剪范围百分之30区域是可见的,0%是全部裁剪了,当前区域全部不可见*/
clip-path: circle(30% at 50% 50%);
}
将裁剪初始设置为全部不可见
clip-path: circle(0 at 50% 50%);
之后设置鼠标经过,恢复显示即可
.avatar:hover::after {
clip-path: circle(50% at 50% 50%); //半径为父元素200px的一半为100px,设置百分比和px效果一致
}
特效字
<div class="box">
<img src="./3.gif" alt="">
<div class="tet">
<span>我是特效字</span>
</div>
</div>
.box {
position: relative;
width: 200px;
height: 200px;
margin: 100px auto;
}
img {
position: absolute;
width: 100%;
}
.tet {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: 1000;
//设置白色,下面添加的属性会根据当前元素的背景色的等进行算法计算,在这里最主要的作用是整个最外围容器的背景是白色
//设置为白色可以将多余不需要看见的图片部分隐藏掉,只显示与文字交融的部分,下面有个例子
background-color: #fff;
}
在上面的代码中有一个图片和文字容器,分别设置定位后,图片默认在下,文字区域在上。这个时候在给文字设置背景色,则按照层级分:图片在下,白色背景在中,最后文字颜色在上
最后给文字添加该css属性,进行颜色混合
//元素的内容将与其背景进行屏幕混合
mix-blend-mode: screen;
这样子就能看见文字颜色区域为动图填充了。
当把背景色设置为别的颜色的时候
background-color: #ccc;
页面文字编辑
使用:contenteditable
属性设置一个元素在页面中的编辑效果,该属性需要设置为布尔值
<h1 contenteditable="true">请编辑我吧</h1>
当点击文字区域的时候会显示编辑区域
代码雨特效
利用canvas绘制,动态创建不同的文字并设置不同的色彩绘制在不同的位置
<canvas class="bg"></canvas>
以下是代码的准备部分
let canvas = document.querySelector('.bg')
let width = window.innerWidth //获取容器宽高,设置canvas的宽高
let height = window.innerHeight
canvas.width = width
canvas.height = height
// 准备绘制上下文,后期页面文字是通过绘制完成的
let ctx = canvas.getContext('2d')
let columnWidth = 20 //定义列宽
let columnCount = Math.floor(width / columnWidth) //获取列数
let columnTNextIndex = new Array(columnCount) //记录每列写到第几个文字
// 如 [2,3,1] 第一列从第二个开始,第二列从第三个开始,第三列从第一个开始
columnTNextIndex.fill(1) //默认所有列都从第一个开始
// 随机色
function getRandomColor() {
const colors = ['#33b5e5', '#0099cc', '#e0441b', '#34495e', '#2ecc71', '#3498db', '#e74c3c', '#9b59b6', '#ecf0f1', '#95a5a6', '#f39c12', '#d35400']
return colors[Math.floor(Math.random() * colors.length)]
}
// 随机字
function getRandomChar() {
let str = 'hello world,i love you'
return str[Math.floor(Math.random() * str.length)]
}
添加如下绘制代码,通过设置2d视觉中每个字母的坐标绘制
// 绘制
function draw() {
//设置每次绘制字体的颜色
ctx.fillStyle = getRandomColor()
ctx.font = `20px 隶书` //字体样式
for (let i = 0; i < columnTNextIndex.length; i++) {
// 每一列都需要绘制
// 需要计算每一列字母的位置
let x = columnWidth * i //每一列的x坐标
let y = 20 * columnTNextIndex[i] //字体大小 * 该列的第几个位置
ctx.fillText(getRandomChar(), x, y) //每次绘制的文字和位置
columnTNextIndex[i]++ //每一列文字绘制过就更改位置准备绘制下一个文字
}
}
setInterval(draw, 50);
当前效果如图
这个时候会出现一个问题,就是绘制会超出当前屏幕的可视区域一直往下绘制,所以需要进行判断,这样子每次超出视口区域的时候都会从顶部重新绘制。在for循环结束的时候添加判断
if (y >= height) {
columnTNextIndex[i] = 0
} else {
columnTNextIndex[i]++ //每一列文字绘制过就更改位置准备绘制下一个文字
}
接着出现一个问题,就是每次绘制过后的文字应该逐渐变淡。在每次draw方法的首部执行绘制透明色
// 每一次调用都会将上一次的区域刷淡
ctx.fillStyle = `rgba(240,240,240,.1)` //设置将填充的半透明颜色
//fillStyle 属性决定了对这个矩形对的填充样式,起始点是(0,0),其次是矩形的宽高
ctx.fillRect(0, 0, width, height)
这样子大致效果就可以了,接下来就是让第二次开始每列的起始时间不同,这可以在判断中利用随机数的概念实现
if (y >= height && Math.random() >= 0.95)
绘制的完整代码
// 绘制
function draw() {
// 每一次调用都会将上一次的区域刷淡
ctx.fillStyle = `rgba(240,240,240,.1)` //设置将填充的半透明颜色
//fillStyle 属性决定了对这个矩形对的填充样式,起始点是(0,0),其次是矩形的宽高
ctx.fillRect(0, 0, width, height)
//设置每次绘制字体的颜色
ctx.fillStyle = getRandomColor()
ctx.font = `20px 隶书` //字体样式
for (let i = 0; i < columnTNextIndex.length; i++) {
// 每一列都需要绘制
// 需要计算每一列字母的位置
let x = columnWidth * i //每一列的x坐标
let y = 20 * columnTNextIndex[i] //字体大小 * 该列的第几个位置
ctx.fillText(getRandomChar(), x, y) //每次绘制的文字和位置
if (y >= height && Math.random() >= 0.95) {
columnTNextIndex[i] = 0
} else {
columnTNextIndex[i]++ //每一列文字绘制过就更改位置准备绘制下一个文字
}
}
}
九宫格图
<div class="box">
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
</div>
通过网格布局快速搭建页面:grid-template-columns
属性实现每列宽度存放子元素
.box {
width: 300px;
height: 300px;
margin: 100px auto;
/* 设置网格布局 */
display: grid;
/* 1fr 是一个可伸缩的比例单位 */
/* repeat()表示轨道列表的重复片段,允许以更紧凑的形式写入大量显示重复模式的列或行 */
/* 1fr 1fr 1fr等价于下面写法 */
grid-template-columns: repeat(3, 1fr);
background-color: aliceblue;
}
.box-item {
box-shadow: inset 0 0 0 1px #fff;
background-image: url(./8.jpg);
background-size: 300px 300px;
transition: 0.5s;
position: relative# console命令有关
## console.table()
使用`console.table()`可以将一些网络请求获取的数据以表格的形式展现出来方便调试。比`console.log()`更直观。
![在这里插入图片描述](https://img-blog.csdnimg.cn/b93ad2354b2b4af0b25f0ca3eb761a71.png)
## console.group(),groupEnd(),groupCollapsed()
可以使用`group()`和`groupEnd()`快速打印一个分组信息。**使用group()打印的分组信息默认展开。** 而使用**groupCollapsed()默认将分组折叠显示**
```javascript
let label = 'Package'
console.group(label)
console.log("one")
console.log("two")
console.log("three")
console.groupEnd(label)
console.dir()
可以使用console.dir()
快速打印一个对象的属性信息
console.time()和timeEnd()
可以使用console.time()
和timeEnd()
进行计时统计,如下代码,time()
告诉浏览器启动计时,timeEnd()
告诉浏览器停止计时,代码主体死循环两秒钟。
console.time('loop') //loop相当于一个标签或变量,做打印提示信息用
const start = Date.now()
while (Date.now() - start < 2000) { }
console.timeEnd('loop')
console.count()和console.countReset()
可以使用console.count()
快速统计指定标签调用的次数,如果为指定标签,则默认为default
标签。
const start = Date.now()
while (Date.now() - start < 20) {
console.count('loop')
}
console.countReset('loop') //清空该标签的统计次数,不清空则下次统计从上次标签断点处开始计算
console.trace()
console.trace()
打印堆栈跟踪,当开发中存在一个函数被多次调用的时候,控制台未报错,但是达不到预期效果的时候,可以查看它的调用情况,就可以使用该打印信息。
如下代码中,console.trace()
位于b函数内部,所以先打印b函数,而b函数又是被a函数调用,所以接着打印a,最后a函数是被全局调用的,所以最后打印全局匿名。
function b() {
console.trace()
}
function a() {
b()
}
a()
给输出语句添加样式
如下代码中定义了一个样式变量,想要应用到输出语句上,首先需要在打印的内容消息前添加%c
代表后面的内容需要被css样式形式,而修饰的样式在第二个参数中存放。
const style = `
padding:5px;
background-color:skyblue;
border:1px solid #333;
font-size:2em;
`
console.log(`%c今天是个好日子`, style)
下面这个是在控制台打印输出,非页面显示
控制台Performance性能优化面板认识
这里是一个测试案例性能分析地址
默认情况下,需要点击红色区域进行录制,录制的结果被分析显示在下方面板中显示。
首先为开启性能优化的时候,性能分析报告的首部会出现红色线条,这就代表代码存在性能问题,且红色线是由多个小块组成,每一个块代表一个js从开始执行到完成渲染的时间。
在往下看会出现该区域,黄色代表js执行,紫色代表渲染执行,绿色代表浏览器绘制执行。会发现紫色占用时间大,代表时间可能出现在渲染过程中。 同时该区域表达的情况也可以从底部的圆形图查看显示具体的信息。
该区域是屏幕快照,需要打开才会显示,该区域代表某一刻执行的显示状态。
在往下就是一些子菜单,其中主要(main)是最常用的渲染主线程,可以查看渲染过程中出现的问题
在渲染主线程中,通过事件循环不断执行任务,如图每一个小方块就是一个任务。如果每一个任务出现了性能问题耗时过长,则在右侧顶部会出现一个红色的三角提示信息。 鼠标悬停的时候可以查看任务耗时。
选中时间线缩小,而不是像上图所示查看完整的信息。在下图中,纵向是一个推栈调用信息,意思就是任务中触发了动画帧已触发,在这里又触发了函数调用,函数调用又会触发app.update()去执行。
当点击了app.update()
区域的时候,可以查看底部调用树
中的报告情况。这里就是一个推栈展开调用情况,可以查看每一个区域所耗的时长,并且还附加了该区域的代码定位。
点击旁边的文件定位跳转,会发现在源代码中已经标注了问题代码部分所耗的时长。会发现引起回流的主要出现在offsetTop
属性的读取上。当修改为一个变量接收offsetTop
后,只会在页面初始化的时候加载一次引起一次回流。后期触发offsetTop
发生改变,否则不会回流。
单行与多行文本省略号
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
默认行为相关
下面是基本代码合在一起,默认整个页面是可以滚动的,但是当用户点击显示按钮的时候让视频区域显示出来的时候,希望可以观看视频,而滚动条不要滚动。但是发现在不添加{ passive: false }
的情况下,滚动条在视频显示的时候依旧可以滚动,这不符合预期。
这个地方涉及到性能问题,滚动事件处理起来很耗时间,额外处理默认行为就更加困难了,如果本身不打算处理默认行为,那么passive:true
即不写为该默认值。而这个时候再次去使用默认行为控制台就会报错提示。如果设置passive:false
即告诉浏览器我可能会做一些处理默认行为的事情通知你一下做好准备。这样子就不会出现问题,并且程序可以按照预期的执行。
<style>
body {
height: 3000px;
}
.box {
display: none;
margin: 0 auto;
width: 500px;
height: 500px;
border: 1px solid #333;
background-color: skyblue;
}
</style>
</head>
<body>
<button class="btn">显示</button>
<button class="btn2">隐藏</button>
<div class="box">我是视频区域</div>
<script>
let box = document.querySelector('.box')
let btn = document.querySelector(".btn")
let btn2 = document.querySelector(".btn2")
btn.onclick = function () {
box.style.display = 'block'
window.addEventListener("wheel", wheelHandler, { passive: false })
}
function wheelHandler(e) {
// 视频区域显示就禁用滚动事件
e.preventDefault();
}
btn2.onclick = function () {
box.style.display = 'none'
// 移除滚轮的事件监听
window.removeEventListener("wheel", wheelHandler)
}
</script>
未添加passive选项的时候如图
计时器问题
当打开一个页面,在该页面中使用到计时器的时候,如果这个时候切换到其他页面中,则浏览器会认为该标签页被隐藏了,为了提示效率,不需要按照计时器指定的时间去执行代码,将时间调成浏览器给定的时间。
setInterval(() => {
console.log("500毫秒执行一次");
}, 500)
setInterval(() => {
console.log("2秒钟执行一次")
}, 2000)
如下图,第一张图未切换标签页的时候,规律性的打印输出,但是第二章图切换的时候,会发现打印输出结果各不相同
如果想保持规律性打印输出,可以在页面切换的时候就停止定时器,需要使用到visibilitychange
事件,出于兼容性原因,请确保使用 document.addEventListener
而不是 window.addEventListener
来注册回调。document.visibilityState 当前页面为visibility,切换隐藏时为hidden
。修改代码如下后,切换标签页的时候也能保持打印输出符合预期。
let time1 = setInterval(() => {
console.log("500毫秒执行一次");
}, 500)
let time2 = setInterval(() => {
console.log("2秒钟执行一次")
}, 2000)
document.addEventListener('visibilitychange', () => {
// document.visibilityState 当前页面为visibility,切换隐藏时为hidden
if (document.visibilityState === 'hidden') {
clearInterval(time1);
clearInterval(time2);
} else {
time1 = setInterval(() => {
console.log("500毫秒执行一次");
}, 500)
time2 = setInterval(() => {
console.log("2秒钟执行一次")
}, 2000)
}
})
鼠标事件
document.addEventListener('click', function (e) {
...........
})
如下代码功能一样,都是鼠标距离可视视口区域左侧或顶部的位置
e.x, e.clientX, e.y, e.clientY
鼠标到页面左侧或顶部的距离,包含了滚动条滚出去的页面
e.pageX, e.pageY
鼠标到屏幕左侧的距离
e.screenX
当前事件和上一个事件之间移动距离的水平差值
e.movementX
鼠标到目标元素e.target
左侧的距离
e.offsetX
Flip动画
传统的transition
和animation
是针对css属性改变的时候去做某些事情,如果改变的是DOM结构
F:first,记录每个元素的起始位置
L:last,记录每个元素结束的位置
I:invert,记录反转元素到起始的距离(起始位置-结束位置,排除为0的情况)
P:play,播放动画回到结束位置
重点是需要知道下面这句话
Element 接口的 animate()
方法是创建一个新的 Animation 的便捷方法,将它应用于元素,然后运行动画。它将返回一个新建的 Animation 对象实例
代码如下
<button>切换</button>
<div class="box">
<div class="item">我是第1个</div>
<div class="item">我是第2个</div>
<div class="item">我是第3个</div>
<div class="item">我是第4个</div>
<div class="item">我是第5个</div>
</div>
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 300px;
height: 300px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.item {
padding: 4px;
border: 1px solid red
}
</style>
<script>
let btn = document.querySelector('button')
let box = document.querySelector('.box')
btn.addEventListener('click', () => {
// 记录位置
record(box)
// 改变元素结构
change()
// flip动画实现
move(box)
})
function record(container) {
for (let i = 0; i < container.children.length; i++) {
let dom = container.children[i]
let rect = dom.getBoundingClientRect() // 获取修改前DOM的大小和相对于视口的位置
// 添加自定义属性绑定起始位置
dom.startX = rect.x
dom.startY = rect.y
}
}
function change() {
const childrens = [...box.children] //转换为真数组
for (let i = 0; i < childrens.length; i++) {
let children = childrens[i] //每一个元素
let j = Math.floor(Math.random() * childrens.length) //随机改变元素位置,固定随机数范围
if (i != j) { //不是同一个元素的时候改变位置
let nextChildren = children.nextElementSibling
// 改变DOM结构
box.insertBefore(children, childrens[j]) //移动的同时会删除上一个children,保持只有一个该元素
box.insertBefore(childrens[j], nextChildren)
}
}
}
function move(container) {
for (let i = 0; i < container.children.length; i++) {
let dom = container.children[i] //修改后的元素
let rect = dom.getBoundingClientRect() //获取修改后的DOM的大小和相对于视口的位置
let cruX = rect.x, curY = rect.y
dom.animate([
// 起始css属性
{ transform: `translate(${dom.startX - cruX}px,${dom.startY - curY}px)` },
//结束css属性
{ transform: `translate(0px,0px)` }
], {
duration: 500
})
}
}
</script>
过渡结束事件多次触发
当一个DOM元素添加了多个过渡效果时候,如果希望在结束过渡效果的时候只会执行一次,可以给事件监听绑定第三个参数once
dom.addEventListener("transitioned", function () {
}, {
once: true
})
高亮关键字
用户输入关键字查询后,显示查询的结果中,将关键字高亮显示。总的思路就是先筛选数据后进行高亮显示
<input type="text">
<div>
</div>
<script>
let input = document.querySelector('input')
let box = document.querySelector('div')
let arr = ["赵云", "韩信", "墨子", "张飞", "张铁", "老夫子"]
// 初始化展示
box.innerHTML = arr.map((item) => {
return `<p>${item}</p>`
}).join('')
input.addEventListener('change', () => {
let key = input.value.trim() //获取内容
let reg
if (key) {
reg = new RegExp(key, 'g') //定义正则,全局匹配,输入的关键字
}
box.innerHTML = arr.filter((item) => {
if (item.includes(input.value)) {
return item
}
}).map(item => {
let name = item
if (reg) {
name = name.replace(reg, function () {
return `<strong style="color: green">${key}</strong>`
})
}
return `<p>${name}</p>`
}).join('')
})
</script>
图片延迟加载
图片加载是一个性能优化的手段,可以大大提高页面的加载响应速度。像电商网站中必备图片优化处理。如大屏展示页面中,当未完全加载到图片之前,先显示loading图。对于首屏的轮播图等地方,优先等待首屏的其他数据加载完成后才会加载真实图片。页面初次加载的时候并非将所有的图片加载回来显示,不在当前可视窗口区域的图片暂时不进行加载,当用户滚动滚轮触发必要条件的时候,才会去加载对应的图片显示。
对图片进行延迟加载一方面可以提高页面的加载速度,另一方面也可以减少用户的网络流量。
首屏加载
<style>
.imageLazyBox {
width: 367px;
height: 500px;
background: url(./3.gif)no-repeat center center #eee;
}
.imageLazyBox img {
width: 100%;
height: 100%;
/* 某些浏览器,如果图片src没有值,则会默认显示一些样式,这不好看。
因此建议将图片元素隐藏 有两种方案
1:display:none 但是会引起回流重绘
2:opacity透明度处理*/
opacity: 0;
transition: opacity 0.3s;
}
</style>
</head>
<body>
<!-- 将图片放置于容器中,给容器设置默认显示占位图(如果不设置容器,那么图片加载完成的时候会突然出现)
初始的时候,src属性不立刻赋值,而是通过自定义属性保存图片地址-->
<div class="imageLazyBox">
<img src="" image-lazy="https://cdn2.thecatapi.com/images/5ef.jpg">
</div>
<script>
function lazyAPI() {
let imageLazyBox = document.querySelector(".imageLazyBox")
let img = imageLazyBox.querySelector('img')
let imageLazy = img.getAttribute('image-lazy')
img.onload = () => {
img.style.opacity = 1
}
img.src = imageLazy
img.removeAttribute("image-lazy")
}
// 等待其他资源加载完成后执行图片获取操作
//1 window.onload = lazyAPI
// 2 定时器
setTimeout(() => {
lazyAPI()
}, 1000)
非可视区域图片延迟加载
将上面的窗口高度调高,且修改代码。
function offset(element) {
let l = element.offsetLeft
let t = element.offsetTop
let p = element.offsetParent
// console.log(l, t, p, element);
while (p && p.tagName != 'body') {
if (!/MSIE 8/.test(navigator.userAgent)) {
//处理浏览器兼容
l += p.clientLeft
t += p.clientTop
}
l += p.offsetLeft
t += p.offsetTop
p = p.offsetParent //结束条件
}
return {
top: t,
left: l
}
}
function lazyAPI(imageLazyBox) {
let img = imageLazyBox.querySelector('img')
let imageLazy = img.getAttribute('image-lazy')
img.onload = () => {
img.style.opacity = 1
}
img.src = imageLazy
img.removeAttribute("image-lazy")
}
let imageLazyBox = document.querySelector(".imageLazyBox")
window.onscroll = () => {
//距离父元素的距离+自身高度
let A = offset(imageLazyBox).top + imageLazyBox.offsetHeight
// 滚动出去的距离+屏幕的高度
let B = document.documentElement.scrollTop + document.documentElement.clientHeight
if (A <= B) {
lazyAPI(imageLazyBox)
}
}
</script>
这个时候滚动页面的时候会出现一个问题,即第一次图片出现的时候,已经执行了一次lazyAPI
函数,并将自定义属性移除了,但是之后的每次滚动都会进入该函数中,但是却没有自定义属性,所以就会报错。所以需要进行判断。方法有如下几种
//方法确定,滚动事件每次都会执行函数体,浪费性能
function lazyAPI(imageLazyBox) {
......
let imageLazy = img.getAttribute('image-lazy')
if (!imageLazy) return
}
//使用变量控制,缺点是每次都会进入滚动事件的回调,但不会执行过多的代码
let flag = true
function lazyAPI(imageLazyBox) {
//结束位置
flag = false
}
window.onscroll = () => {
if (!flag) return //函数首部判断
....
}
//方法三:一旦图片进入完成加载,就取消事件监听
function lazyAPI(imageLazyBox) {
//函数末尾执行
flag = false
if (!flag) {
window.removeEventListener('scroll', fun)
}
}
//将回调函数封装成函数,方便取消事件监听
window.addEventListener('scroll', fun)
function fun() {
//距离父元素的距离+自身高度(想图片出现一半就自身高度一半,想图片一出现就取消自身高度)
let A = offset(imageLazyBox).top + imageLazyBox.offsetHeight
// 滚动出去的距离+屏幕的高度
let B = document.documentElement.scrollTop + document.documentElement.clientHeight
if (A <= B) {
lazyAPI(imageLazyBox)
}
}
但是这样子还是有一个缺点,就是滚动事件触发的太频繁了,可以设置节流,固定时间内只允许触发一次。
window.addEventListener('scroll', throttle(fun))
// 节流
function throttle(fun, delay = 500) {
let previous = 0
let timer = null
return function (...arg) {
let nowTime = new Date()
let remaining = delay - (nowTime - previous)
if (remaining <= 0) {
clearTimeout(timer)
timer = null
previous = nowTime
fun.call(this, ...arg)
} else if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
previous = new Date()
fun.call(this, ...arg)
}, remaining)
}
}
}
借助getBoundingClientRect()实现
接下来使用第二种方法,左图是上面代码的做法
Element.getBoundingClientRect()
方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。
- 容器在视口中
- 容器不在视口中
function fun() {
console.log("进入了");
if (!flag) return
//若 -imageLazyBox.offsetHeight 则为一出现就获取
//若 -imageLazyBox.offsetHeight/2 则为到一半出现就获取
//图片完全出现获取url
let A = imageLazyBox.getBoundingClientRect().bottom
let B = document.documentElement.clientHeight //获取视口高度
if (A <= B) {
lazyAPI(imageLazyBox)
}
}
借助 IntersectionObserver构造函数实现
IntersectionObserver
接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。
基本格式:创建一个新的 IntersectionObserver 对象,当其监听到 一个或多个 目标元素的可见部分(的比例)超过了一个或多个阈值(threshold)时(默认阈值为0),会执行指定的回调函数。调用该对象身上的observe(DOM)
设置监听对象,调用unobserve(DOM)
设置解绑监听。
这里需要注意,执行的触发时机是可以更改的,默认情况下是下代码中的注释部分
/*
1:监听某些DOM元素与浏览器可视窗口的交叉位置的状态信息
2:创建一个 IntersectionObserver 对象,当监听的DOM元素和浏览器
可视窗口的交叉位置状态发送变化,便回执行回调函数。其中changes参数是一个数组集合。
集合中的每一个元素对应一个DOM元素和可视窗口的交叉位置信息。
*:boundingClientRect 等价于 Element.getBoundingClientRect()
*:target为监听的目标元素
*:isIntersecting为目标元素是否可视窗口的交叉了,为布尔值
3:执行时机
*:页面初始化的时候会执行一次监听。
*:容器初次进入可视窗口的时候会执行一次 (触发不频繁)
*:容器完全离开可视窗口的时候会执行一次
4:配置参数
*:root:所监听对象的具体祖先元素。如果未传入值或值为null,则默认使用顶级文档的视窗(一般为html)。
*:rootMargin:计算交叉时添加到根(root)边界盒bounding box的矩形偏移量,可以有效的缩小或扩大根的判定范围从而满足计 算需要。所有的偏移量均可用像素(px)或百分比(%)来表达, 默认值为"0px 0px 0px 0px"。
*:threshold:一个包含阈值的列表, 按升序排列, 列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会触发callback。默认值为0。
*/
let ob = new IntersectionObserver(changes => {
console.log(changes);
})
//监听元素
ob.observe(imageLazyBox)
//解绑监听
ob.unobserve(imageLazyBox)
修改触发时机,设置配置参数threshold
,基本格式:new IntersectionObserver(回调函数,配置对象)
。
其中threshold
传入一个数组,数组的值如下:0代表容器一进入可视区域就触发,0.5代表容器进入可视区域一半的时候触发,1代表容器完全进入可视窗口的时候触发一次。离开的时候也是这样子。
let ob = new IntersectionObserver(changes => {
console.log(changes[0]);
}, {
threshold: [0, 0.5, 1]
})
借助IntersectionObserver
设置图片延迟加载
let flag = true
function lazyAPI(imageLazyBox) {
let img = imageLazyBox.querySelector('img')
let imageLazy = img.getAttribute('image-lazy')
// if (!imageLazy) return
img.onload = () => {
img.style.opacity = 1
}
img.src = imageLazy
img.removeAttribute("image-lazy")
flag = false
// if (!flag) {
// window.removeEventListener('scroll', throttle(fun))
// }
}
let imageLazyBox = document.querySelector(".imageLazyBox")
let ob = new IntersectionObserver(changes => {
let item = changes[0] //只有一个监听元素
if (item.isIntersecting) { //完全进入后会执行一次
lazyAPI(item.target)
ob.unobserve(item.target) //移除监听
}
}, {
threshold: [1]
})
ob.observe(imageLazyBox)
使用原生img属性loading
loading=lazy
:延迟加载图像,直到它和视口接近到一个计算得到的距离(由浏览器定义)。目的是在需要图像之前,避免加载图像所需要的网络和存储带宽。这通常会提高大多数典型用场景中内容的性能。
<img src="https://cdn2.thecatapi.com/images/5ef.jpg" loading="lazy">
瀑布流效果
案例分析:存在一个布局容器,容器中分为固定列数,但是每一列宽度一致,即图片宽度一致,但是允许图片的高度不同。初始情况下,获取图片数据,将顺序图片填满第一行,由于图片高度不统一可能会造成两边高中间矮的效果,如果下次填充还是按照顺序从左到右依次填充,假设在最坏情况下,图片每次填充一行都是两边高中间矮,那么就会造成视觉效果很难看。所以需要采用瀑布流布局效果,将每次需要填充的图片数据先按照高度排序,然后每次将最高的图片插入到已有容器列中最矮的一列,将最矮的图片插入到已有列数中最高的一列(也可以不对图片进行高度排序,直接顺序将每次插入的图片插入到列最矮的地方)。这里需要处理页面可视区域的布局情况和非可视区域的布局情况,对于非可视区域需要进行图片懒加载效果,同时添加滚动条刷新不断获取数据的操作。
封装如图所示结构
data.json中封装模拟数据,格式如下,其中所有的宽度一致,高度改变
[
{
"id": 1,
"pic": "images/8.jpg",
"width": 300,
"height": 433,
"title": "泰勒·斯威夫特(Taylor Swift) , 1989年12月13日出生于美国宾州,美国歌手、演员。2006年出道,同年发行专辑《泰勒·斯威夫特》,该专辑获得美国唱片业协会的白金唱片认证",
"link": "https://baike.sogou.com/v1850208.htm?fromTitle=%E9%9C%89%E9%9C%89"
},
....
]
<div class="container">
<div class="column">
<!-- <div class="card">
<a href="#">
<div class="lazyImageBox" style="height: 280px;">
<img src="" data-image="./3.gif">
</div>
<p>我来了</p>
</a>
</div> -->
</div>
<div class="column">
</div>
<div class="column">
</div>
</div>
html,
body {
background: #d6d7db;
}
* {
box-sizing: border-box;
}
.container {
margin: 20px auto;
width: 760px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.container .column {
width: 240px;
}
.card {
margin-bottom: 10px;
padding: 5px;
background: #fff;
box-shadow: 3px 3px 10px 0 #222;
}
.card a {
display: block;
text-decoration: none;
}
.card a .lazyImageBox {
/* height,图片不显示的时候需要根据图片的高度设置,撑开容器占位 */
background: url(./3.gif) no-repeat center center #f4f4f4;
background-size: cover;
overflow: hidden;
}
.card a .lazyImageBox img {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity .3s;
}
.card a p {
margin-top: 5px;
color: #222;
font-size: 12px;
line-height: 20px;
}
使用getBoundingClientRect方法实现
let container = document.querySelector('.container')
let columns = document.querySelectorAll('.column')
// 获取所有的容器
let lazyImgBoxs = null
// 转换类数组/伪数组为真数组
columns = Array.from(columns)
// 节流
function throttle(fun, delay = 500) {
let previous = 0;
let timer = null;
return function (...args) {
let nowTime = new Date()
let remaining = delay - (nowTime - previous)
if (remaining <= 0) {
clearTimeout(timer)
timer = null
previous = nowTime
fun.call(this, ...args)
} else if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
previous = new Date()
fun.call(this, ...args)
}, remaining)
}
}
}
// 初始化获取数据
/* 需要注意:如果使用浏览器默认的打开浏览,是基于file协议,
不能发送ajax请求,需要安装live serve插件使用该插件打开 */
const queryData = () => {
let data = []
//通过xhr获取数据
let xhr = new XMLHttpRequest;
//该文件在index.html文件中引入,所以地址是基于html页面引入json文件的,false代表同步获取数据
xhr.open('GET', './data.json', false)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText)
}
}
xhr.send()
return data
}
let flag = true
// 初始化页面
const initHTML = data => {
// 当给图片绑定高度的时候会发现,图片清晰度丢失了,这是因为指定容器的宽为230,但是最终返回的图片宽度均为300
// 所以需要按照比例进行缩放
data = data.map(item => {
// AW/AH = BW/BH
let AW = 230, BW = item.width, BH = item.height
let AH = AW / (BW / BH)
item.width = AW
item.height = AH
return item
})
for (let i = 0; i < data.length; i += 3) {
// 每三行为一组
let group = data.slice(i, i + 3)
if (flag) {
flag = false
} else {
// 根据每一组中的图片高度进行小-大排序 升序
group.sort((a, b) => a.height - b.height)
}
// 对每一列进行排序 由大-小排序 降序
columns.sort((a, b) => {
return b.offsetHeight - a.offsetHeight
})
// 根据每行图片数量插入页面中
group.forEach((item, index) => {
let card = document.createElement('div')
card.className = 'card'
card.innerHTML = `<a href="#">
<div class="lazyImageBox" style="height: ${item.height}px;">
<img src="" data-image="${item.pic}">
</div>
<p>${item.title}</p>
</a>`
columns[index].appendChild(card)
})
}
lazyImgBoxs = Array.from(document.querySelectorAll('.lazyImageBox'))
}
// 单张延迟加载函数
const lazyImg = lazyImgBox => {
let img = lazyImgBox.querySelector('img')
let dataImagUrl = img.getAttribute('data-image')
img.onload = () => {
img.style.opacity = 1
}
img.src = dataImagUrl
lazyImgBox.isLoad = true //给每一个图片绑定一个完成标识
}
// 遍历所有的图片实现单张图片加载
const lazyImgs = () => {
// 必须先让lazyImgBoxs获取到所有图片容器
lazyImgBoxs.forEach(item => {
if (item.isLoad) return
// 判断执行时机
// 获取视口高度
let B = document.documentElement.clientHeight
let A = item.getBoundingClientRect().bottom
if (A <= B) {
lazyImg(item)
}
})
}
let data = queryData() //初始化获取数据
initHTML(data) //初始化渲染页面,让加载容器显示
setTimeout(lazyImgs, 250)
window.addEventListener('scroll', throttle(() => {
lazyImgs()
//通过监听滚动条是否到底部实现下拉刷新获取更多数据
let HTML = document.documentElement
// +50是控制误差范围
if ((HTML.clientHeight + HTML.scrollTop + 50) >= HTML.scrollHeight) {
// HTML.scrollHeight是元素内容的高度
data = queryData()
initHTML(data)
}
}))
使用IntersectionObserver构造函数实现
多添加一个容器用于实现监听容器是否到底部加载更多效果,当前也可以采用滚动条实现
<div class="container">
<div class="column">
<!-- <div class="card">
<a href="#">
<div class="lazyImageBox" style="height: 280px;">
<img src="" data-image="./3.gif">
</div>
<p>我来了</p>
</a>
</div> -->
</div>
<div class="column">
</div>
<div class="column">
</div>
</div>
<div class="loadMore"></div>
.loadMore {
height: 10px;
}
let container = document.querySelector('.container')
let columns = Array.from(document.querySelectorAll('.column'))
// 获取所有的容器
let lazyImgBoxs = null
// 获取加载更多的容器
let loadMore = document.querySelector('.loadMore')
// 初始化获取数据
/* 需要注意:如果使用浏览器默认的打开浏览,是基于file协议,
不能发送ajax请求,需要安装live serve插件使用该插件打开 */
const queryData = () => {
let data = []
//通过xhr获取数据
let xhr = new XMLHttpRequest;
//该文件在index.html文件中引入,所以地址是基于html页面引入json文件的,false代表同步获取数据
xhr.open('GET', './data.json', false)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText)
}
}
xhr.send()
return data
}
let flag = true
// 初始化页面
const initHTML = data => {
// 当给图片绑定高度的时候会发现,图片清晰度丢失了,这是因为指定容器的宽为230,但是最终返回的图片宽度均为300
// 所以需要按照比例进行缩放
data = data.map(item => {
// AW/AH = BW/BH
let AW = 230, BW = item.width, BH = item.height
let AH = AW / (BW / BH)
item.width = AW
item.height = AH
return item
})
for (let i = 0; i < data.length; i += 3) {
// 每三行为一组
let group = data.slice(i, i + 3)
if (flag) {
flag = false
} else {
// 根据每一组中的图片高度进行小-大排序 升序
group.sort((a, b) => a.height - b.height)
}
// 对每一列进行排序 由大-小排序 降序
columns.sort((a, b) => {
return b.offsetHeight - a.offsetHeight
})
// 根据每行图片数量插入页面中
group.forEach((item, index) => {
let card = document.createElement('div')
card.className = 'card'
card.innerHTML = `<a href="#">
<div class="lazyImageBox" style="height: ${item.height}px;">
<img src="" data-image="${item.pic}">
</div>
<p>${item.title}</p>
</a>`
columns[index].appendChild(card)
})
}
}
// 单张延迟加载函数
const lazyImg = lazyImgBox => {
let img = lazyImgBox.querySelector('img')
let dataImagUrl = img.getAttribute('data-image')
img.onload = () => {
img.style.opacity = 1
}
img.src = dataImagUrl
lazyImgBox.isLoad = true //给每一个图片绑定一个完成标识
}
//IntersectionObserver监听
let obImg = new IntersectionObserver(changes => {
changes.forEach(item => {
if (item.isIntersecting) {
// 代表当前图片与可视区域交叉
lazyImg(item.target)
// 做完加载就取消当前项的监听
obImg.unobserve(item.target)
}
})
}, { threshold: [1] })
const oblazyImgBoxs = () => {
lazyImgBoxs = Array.from(document.querySelectorAll('.lazyImageBox'))
// 已经监听过的元素不需要重复监听
lazyImgBoxs = lazyImgBoxs.filter(item => !item.isLoad)
// 监听筛选过后的元素
lazyImgBoxs.forEach(item => obImg.observe(item))
}
// 实现滚动底部加载更多
// 主题思想是在底部创建一个盒子,只有该盒子出现在视口区域就代表需要获取更多数据
const obloadMore = new IntersectionObserver(changes => {
if (changes[0].isIntersecting) {
let data = queryData() //初始化获取数据
initHTML(data) //初始化渲染页面,让加载容器显示
oblazyImgBoxs()
}
}, { threshold: [0] })
let data = queryData() //初始化获取数据
initHTML(data) //初始化渲染页面,让加载容器显示
oblazyImgBoxs()
// 监听加载更多
obloadMore.observe(loadMore)
轮播图,左右移动无缝衔接
左右移动版轮播图需要注意如何处理无缝衔接,即每次移动到最后一张图片的时候如第一张图片合理的出现在页面中。处理的方法:将第一张图片克隆到最后位置。每次播放最后一个克隆图片的时候,迅速切换回第一张图片,用户视觉效果中是无法发现的(这个时候不加过渡即可)。
页面效果
<div class="container">
<div class="wrapper">
<div class="slide">
<img src="./images/1.png" alt="">
</div>
<div class="slide">
<img src="./images/2.jpg" alt="">
</div>
<div class="slide">
<img src="./images/3.jpg" alt="">
</div>
<div class="slide">
<img src="./images/4.jpg" alt="">
</div>
<div class="slide">
<img src="./images/5.jpg" alt="">
</div>
<!-- 克隆第一张图片到末尾 -->
<!-- <div class="slide">
<img src="./images/1.png" alt="">
</div> -->
</div>
<!-- 底部分页 -->
<div class="pagination">
<!-- <span class="active"></span>
<span></span>
<span></span>
<span></span>
<span></span> -->
</div>
<!-- 左右箭头 -->
<a href="javascript:;" class="btn btn-prev"><</a>
<a href="javascript:;" class="btn btn-next">></a>
</div>
<script src="./index.js"></script>
(function () {
let container = document.querySelector('.container')
let wrapper = container.querySelector('.wrapper')
let pagination = container.querySelector('.pagination')
let paginationList = null //span
let btnPrev = document.querySelector('.btn-prev')
let btnNext = document.querySelector('.btn-next')
//首先克隆第一张图片到末尾
let first_copy = wrapper.children[0].cloneNode(true)
wrapper.appendChild(first_copy)
// 动态生成分页圆点,但是不包括克隆的节点
for (let i = 0; i < wrapper.children.length - 1; i++) {
let span = document.createElement('span')
if (i === 0) {
span.className = 'active'
}
pagination.appendChild(span)
}
// 页面有span元素了在获取DOM
paginationList = Array.from(pagination.querySelectorAll('span'))
// 先实现自动播放图片切换
let index = 0 //当前播放的图片所以 最大为5 一共六张图片
let timer = null
// 自动播放函数
function autoMove() {
//#region
/* wrapper.style.transitionDuration = '0s'
wrapper.style.left = `0px`
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px`
这些根样式相关的代码,在现代浏览器中并不会立即执行
而是插入到渲染队列中,等待一个函数的所有样式均获取了才执行。
因此在这里造成了后面样式会层叠前面的样式,所以瞬间的过渡效果没有实现,
克隆的图片是平移到第二个图片的。
解决方法,在希望立即执行样式的地方,如浏览器立即渲染,即刷新渲染那队列
*/
//#endregion
if (index >= 5) {
// 迅速移动至第一张图
index = 0
// 希望移除过渡效果,形参瞬间移动首部效果
wrapper.style.transitionDuration = '0s'
wrapper.style.left = `0px`
wrapper.offsetLeft; //触发浏览器的重绘,以确保样式更改立即生效
}
index++
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px` //移动left值,且自动往左移动,为负数
paginationHandle()
}
// 处理分页器同步active
function paginationHandle() {
let temp = index //记录下标
// temp的值可能为5,即克隆的图片,那么需要保持克隆的图片也激活第一张图片的样式
temp === 5 ? temp = 0 : null //判断是否为5,否则不执行任何操作
paginationList.forEach((item, index) => {
// 清除所有的active
item.classList.remove('active')
if (temp === index) {
item.classList.add('active')
}
})
}
timer = setInterval(autoMove, 1500);
// 鼠标移入清楚定时器
container.addEventListener('mouseenter', () => {
clearInterval(timer)
})
// 鼠标离开开启定时器
container.addEventListener('mouseleave', () => {
timer = setInterval(autoMove, 1500)
})
// 点击分页器绑定点击事件
paginationList.forEach((item, i) => {
item.onclick = () => {
// 如果点击为当前项,不做任何操作,并排除掉克隆的图片,因为没有为其创建span,否则有bug
if (i === index || index === 5 && i === 0) return
// i的范围0-4,已经排除了克隆的
index = i
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px`
paginationHandle()
}
})
// 右箭头点击事件
btnNext.onclick = autoMove
// 左箭头点击事件
btnPrev.onclick = () => {
if (index <= 0) {
// 迅速移动至第一张图
index = 5
// 希望移除过渡效果,形参瞬间移动首部效果
wrapper.style.transitionDuration = '0s'
wrapper.style.left = `${-index * 564}px`
wrapper.offsetLeft; //触发浏览器的重绘,以确保样式更改立即生效
}
index--
wrapper.style.transitionDuration = '0.3s'
wrapper.style.left = `${-index * 564}px` //移动left值,且自动往左移动,为负数
paginationHandle()
}
})()
环形旋转
效果图
<style>
.container {
width: 300px;
height: 300px;
margin: 50px auto;
border: 2px solid #333;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
animation: move 20s linear infinite
}
.item {
position: absolute;
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
}
.item img {
width: 100%;
height: 100%;
/* 容器顺时针移动,图片逆时针移动,保持图片位置不选择 */
animation: move 20s linear infinite reverse
}
@keyframes move {
to {
transform: rotate(360deg);
}
}
</style>
<div class="container">
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
<div class="item"><img src="./3.gif" alt=""></div>
</div>
<script>
let container = document.querySelector('.container')
let items = document.querySelectorAll('.item')
let r = container.clientHeight / 2 //获取半径
let count = items.length //获取图片数量
let pieceDeg = 360 / count //获取每张图片的角度
for (let i = 0; i < count; i++) {
let t = i * pieceDeg //每张图片的角度
// 根据角度转换为弧度
t = (Math.PI / 180) * t
// 借助三角函数,需要操作弧度而非角度,所以需要先转换
// Math.sin(t) 返回角度 t 的正弦值,而 Math.cos(t) 返回角度 t 的余弦值
// x 和 y 分别代表了图片圆心的水平坐标和垂直坐标。
let x = Math.sin(t) * r, y = -Math.cos(t) * r
items[i].style.transform = `translate(${x}px, ${y}px)`
}
</script>
保持宽高比
aspect-ratio
css属性可以设置一个图片的宽高在缩放的时候保持比例缩放。如给一个容器设置:aspect-ratio: 16/9;
缩放比例
按钮边框颜色旋转
<button>按钮边框</button>
首先搭建静态结构,这里需要使用到子绝父相,所以添加定位
:root {
--turn: 360deg;
}
button {
margin: 100px;
padding: 10px 15px;
color: #0ebeff;
background: #000;
border: none;
border-radius: 10px;
outline: none;
/* border 和 outline border 和 outline 很类似,但有如下区别:
outline 不占据空间,绘制于元素内容周围。
根据规范,outline 通常是矩形,但也可以是非矩形的。; */
/* 初始设置用来查看效果,最终取消该效果 */
outline: 4px solid #fff;
/* 给元素设置定位 */
position: relative;
z-index: 1;
}
然后给该元素添加一个before伪元素
,效果如下
button::before {
content: "";
position: absolute;
width: 200px;
height: 200px;
background-color: red;
top: 50%;
left: 50%;
/* 保持背景在最低下 */
z-index: -1;
}
然后设置一个动画,并给该伪元素添加,但是添加完成后,伪元素是沿着中心旋转的,这个时候需要设置旋转点为左上方的顶点
button::before {animation: move 10s linear infinite;}
@keyframes move {
to {
transform: rotate(var(--turn));
}
}
设置如下
transform-origin: 0 0;
设置完后给按钮设置溢出隐藏,这样子外部红色区域就看不见了
overflow: hidden;
之后再次创建一个after伪元素
button::after {
/* 将内部红色区域遮住部分,只保留类似边框区域显示旋转 */
content: '';
position: absolute;
/* 设置自身大小,空出的区域为边框旋转效果区域 */
width: calc(100% - 5px);
height: calc(100% - 5px);
background-color: green;
/* 为了让该元素在button中显示在合理位置 */
top: 2px;
left: 2px;
border-radius: 10px;
;
/* 保持层级在before元素之上 */
z-index: -1;
}
添加完该效果后如图,发现有一个红色区域一直在移动,之后把不需要的颜色取出即可
将父元素的该属性注释,并将绿色背景修改为黑色
outline: 4px solid #fff;
最终效果如下
倾斜按钮
<button class="btn">倾斜按钮</button>
.btn {
width: 100px;
height: 35px;
color: white;
background-color: #000;
border: none;
position: relative;
}
给元素设置圆角
/* 设置左上和右下的圆角 */
border-radius: 15px 0;
设置伪元素,将伪元素设置如图所示位置
.btn::before {
content: '';
position: absolute;
bottom: 0;
left: -15px;
width: 15px;
height: 15px;
background-color: red;
}
然后设置:radial-gradient(blue 5px, red 5px)
css属性,该属性代表设置径向渐变效果,不设置中心位置,默认从一个图像的中心点开始,扩散开始颜色为蓝色,向外扩散到5px,接着剩下的扩散全部为红色。
接着修改扩散圆心的坐标,让其从左上角开始扩散,扩散大小为容器的宽度即可,最后只需要将蓝色设置为透明度即可
background: radial-gradient(circle at 0 0, blue 15px, #000 5px)
右上角该效果一样设置
background: radial-gradient(circle at 100% 100%, transparent, 15px, #000 5px)
最后给按钮设置倾斜即可:transform: skew(-30deg);
设置文字边框
<h1>hello world</h1>
h1 {
color: #000;
text-shadow:
1px 0 #fff,
1px 1px #fff,
1px -1px #fff,
0 1px #fff,
0 -1px #fff,
-1px 0 #fff,
-1px 1px #fff,
-1px -1px #fff;
}
卡片翻转
<div class="card">
<img class="face" src="./3.gif" alt="">
<div class="back">
<h1>我来了</h1>
</div>
</div>
设置基本样式,通过定位让两个容器层叠,保证图片优先显示
.card {
/* 3d效果 */
perspective: 500px;
width: 300px;
height: 300px;
margin: 100px auto;
position: relative;
}
img {
position: absolute;
width: 100%;
height: 100%;
}
.back {
z-index: -1;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
background-color: skyblue;
}
然后给图片设置翻转效果:注意backface-visibility: hidden;
属性功能,这样子当图片翻转过来的时候,显示不可见,以下是鼠标经过的效果。
.face {
transition: .5s;
/* 设置一个元素背面不需要看见,设置为不可见 */
backface-visibility: hidden;
}
.card:hover .face {
transform: rotateY(180deg);
}
这个时候可以利用同样的思路设置背面文字效果
.back {
.....
transform: rotateY(-180deg); //保持翻转之间连续,将文字先翻转,让背面显示,保持文字显示后不是倒着的
backface-visibility: hidden; //背面隐藏
transition: .5s;
}
.card:hover .back {
transform: rotateY(0);
}
原形放大效果
<div class="avatar"></div>
.avatar {
margin: 100px auto;
width: 200px;
height: 200px;
background: url(./3.gif) no-repeat center center;
border-radius: 50%;
position: relative;
}
.avatar::before,
.avatar::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar::before {
/* 遮罩层 */
background-color: rgba(0, 0, 0, .3);
}
.avatar::after {
/* 使该元素获取父元素的值,可以理解为继承使用 ,将父元素的背景图使用*/
background: inherit;
transition: 1s;
}
设置完基本样式后,after伪元素在最顶层显示,其次before伪元素
之后利用裁剪属性:clip-path:circle(radius at position)
,首先设置半径,如果设置为百分比,则半径是相对于父元素宽度计算。将after伪元素进行裁剪,裁剪的区域内可以,区域外不显示。
.avatar::after {
....
/* 裁剪 ,从原心开始裁剪向外扩展,裁剪范围百分之30区域是可见的,0%是全部裁剪了,当前区域全部不可见*/
clip-path: circle(30% at 50% 50%);
}
将裁剪初始设置为全部不可见
clip-path: circle(0 at 50% 50%);
之后设置鼠标经过,恢复显示即可
.avatar:hover::after {
clip-path: circle(50% at 50% 50%); //半径为父元素200px的一半为100px,设置百分比和px效果一致
}
特效字
<div class="box">
<img src="./3.gif" alt="">
<div class="tet">
<span>我是特效字</span>
</div>
</div>
.box {
position: relative;
width: 200px;
height: 200px;
margin: 100px auto;
}
img {
position: absolute;
width: 100%;
}
.tet {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: 1000;
//设置白色,下面添加的属性会根据当前元素的背景色的等进行算法计算,在这里最主要的作用是整个最外围容器的背景是白色
//设置为白色可以将多余不需要看见的图片部分隐藏掉,只显示与文字交融的部分,下面有个例子
background-color: #fff;
}
在上面的代码中有一个图片和文字容器,分别设置定位后,图片默认在下,文字区域在上。这个时候在给文字设置背景色,则按照层级分:图片在下,白色背景在中,最后文字颜色在上
最后给文字添加该css属性,进行颜色混合
//元素的内容将与其背景进行屏幕混合
mix-blend-mode: screen;
这样子就能看见文字颜色区域为动图填充了。
当把背景色设置为别的颜色的时候
background-color: #ccc;
页面文字编辑
使用:contenteditable
属性设置一个元素在页面中的编辑效果,该属性需要设置为布尔值
<h1 contenteditable="true">请编辑我吧</h1>
当点击文字区域的时候会显示编辑区域
代码雨特效
利用canvas绘制,动态创建不同的文字并设置不同的色彩绘制在不同的位置
<canvas class="bg"></canvas>
以下是代码的准备部分
let canvas = document.querySelector('.bg')
let width = window.innerWidth //获取容器宽高,设置canvas的宽高
let height = window.innerHeight
canvas.width = width
canvas.height = height
// 准备绘制上下文,后期页面文字是通过绘制完成的
let ctx = canvas.getContext('2d')
let columnWidth = 20 //定义列宽
let columnCount = Math.floor(width / columnWidth) //获取列数
let columnTNextIndex = new Array(columnCount) //记录每列写到第几个文字
// 如 [2,3,1] 第一列从第二个开始,第二列从第三个开始,第三列从第一个开始
columnTNextIndex.fill(1) //默认所有列都从第一个开始
// 随机色
function getRandomColor() {
const colors = ['#33b5e5', '#0099cc', '#e0441b', '#34495e', '#2ecc71', '#3498db', '#e74c3c', '#9b59b6', '#ecf0f1', '#95a5a6', '#f39c12', '#d35400']
return colors[Math.floor(Math.random() * colors.length)]
}
// 随机字
function getRandomChar() {
let str = 'hello world,i love you'
return str[Math.floor(Math.random() * str.length)]
}
添加如下绘制代码,通过设置2d视觉中每个字母的坐标绘制
// 绘制
function draw() {
//设置每次绘制字体的颜色
ctx.fillStyle = getRandomColor()
ctx.font = `20px 隶书` //字体样式
for (let i = 0; i < columnTNextIndex.length; i++) {
// 每一列都需要绘制
// 需要计算每一列字母的位置
let x = columnWidth * i //每一列的x坐标
let y = 20 * columnTNextIndex[i] //字体大小 * 该列的第几个位置
ctx.fillText(getRandomChar(), x, y) //每次绘制的文字和位置
columnTNextIndex[i]++ //每一列文字绘制过就更改位置准备绘制下一个文字
}
}
setInterval(draw, 50);
当前效果如图
这个时候会出现一个问题,就是绘制会超出当前屏幕的可视区域一直往下绘制,所以需要进行判断,这样子每次超出视口区域的时候都会从顶部重新绘制。在for循环结束的时候添加判断
if (y >= height) {
columnTNextIndex[i] = 0
} else {
columnTNextIndex[i]++ //每一列文字绘制过就更改位置准备绘制下一个文字
}
接着出现一个问题,就是每次绘制过后的文字应该逐渐变淡。在每次draw方法的首部执行绘制透明色
// 每一次调用都会将上一次的区域刷淡
ctx.fillStyle = `rgba(240,240,240,.1)` //设置将填充的半透明颜色
//fillStyle 属性决定了对这个矩形对的填充样式,起始点是(0,0),其次是矩形的宽高
ctx.fillRect(0, 0, width, height)
这样子大致效果就可以了,接下来就是让第二次开始每列的起始时间不同,这可以在判断中利用随机数的概念实现
if (y >= height && Math.random() >= 0.95)
绘制的完整代码
// 绘制
function draw() {
// 每一次调用都会将上一次的区域刷淡
ctx.fillStyle = `rgba(240,240,240,.1)` //设置将填充的半透明颜色
//fillStyle 属性决定了对这个矩形对的填充样式,起始点是(0,0),其次是矩形的宽高
ctx.fillRect(0, 0, width, height)
//设置每次绘制字体的颜色
ctx.fillStyle = getRandomColor()
ctx.font = `20px 隶书` //字体样式
for (let i = 0; i < columnTNextIndex.length; i++) {
// 每一列都需要绘制
// 需要计算每一列字母的位置
let x = columnWidth * i //每一列的x坐标
let y = 20 * columnTNextIndex[i] //字体大小 * 该列的第几个位置
ctx.fillText(getRandomChar(), x, y) //每次绘制的文字和位置
if (y >= height && Math.random() >= 0.95) {
columnTNextIndex[i] = 0
} else {
columnTNextIndex[i]++ //每一列文字绘制过就更改位置准备绘制下一个文字
}
}
}
九宫格图
<div class="box">
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
<div class="box-item"></div>
</div>
通过网格布局快速搭建页面:grid-template-columns
属性实现每列宽度存放子元素
.box {
width: 300px;
height: 300px;
margin: 100px auto;
/* 设置网格布局 */
display: grid;
/* 1fr 是一个可伸缩的比例单位 */
/* repeat()表示轨道列表的重复片段,允许以更紧凑的形式写入大量显示重复模式的列或行 */
/* 1fr 1fr 1fr等价于下面写法 */
grid-template-columns: repeat(3, 1fr);
background-color: aliceblue;
}
.box-item {
box-shadow: inset 0 0 0 1px #fff;
background-image: url(./8.jpg);
background-size: 300px 300px;
transition: 0.5s;
position: relative;
}
之后先设置图片之间的间隔,先设置最左边竖着三个元素的位置,接下来两列就是同理操作
.box-item:nth-child(3n+1) {
/* 这里不设置margin会将容器拉伸宽度,非移动位置 */
/* margin-left: -20px */
left: -20px;
/* 设置x方向位置 */
background-position-x: 0;
}
.box-item:nth-child(3n+2) {
left: 0;
background-position-x: -100px;
}
.box-item:nth-child(3n+3) {
left: 20px;
background-position-x: -200px;
}
接下来设置纵向的位置偏移,但是发现同时设置1-3,4-6,7-9存在困难,但是可以同时设置1-9的样式,然后再设置1-6的样式将前面1-9的层叠掉,以此类推
.box-item:nth-child(-n+9) {
top: 20px;
background-position-y: -200px;
}
.box-item:nth-child(-n+6) {
top: 0;
background-position-y: -100px;
}
.box-item:nth-child(-n+3) {
top: -20px;
background-position-y: 0;
}
最后设置一个鼠标移入的样式
.box:hover .box-item {
top: 0;
left: 0;
box-shadow: none;
}
多行文本溢出手写
首先搭建静态结构,然后省略号单独使用一个容器控制,采样浮动布局让文字环绕
<div class="box">
<div class="more">...</div>
<div class="container">
......
</div>
</div>
.box {
padding: 15px;
margin: 100px auto;
width: 200px;
height: 200px;
background-color: #ccc;
}
.container {
height: 100%;
overflow: hidden;
line-height: 20px;
}
.more {
float: right;
}
然后给整个容器设置一个容器撑开内容往下。然后将内部部分移动到固定显示
.box::before {
content: '';
display: block;
height: 180px;
}
.container {
...
margin-top: -180px;
}