效果图
解题思路
将图片全部定位至中心点,然后x轴就变动translateX ,y轴同理;
这里有两个问题
浏览器: 以左上角为原点0,0 越往下y越大
数学坐标系:以中心点为原点0,0 越往下y越小;
曲线函数:坐标确定了原点确定了,就需要对应的曲线函数来描述这条线段
我们一个个来解决
一、如何把数学坐标系运用到浏览器之中,转化y轴的上下大小反序;
translateX 变化多少就是多少,translateY 变化多少就是反向移动多少,所以重要的是这个反向我们如何来描述,其实这里也很简单,我们使用css变量,然后在变化的时候,setProperty对应的变量值,然后在css里我们做对应的描述;
html
<style>
#container {
width: 100vw;
height: 100vh;
background-color: aqua;
position: relative;
overflow: auto;
}
.curveImg {
--size: 30px;
--dx: 1;
--dy: 1;
border-radius: calc(var(--size) / 2);
width: var(--size);
height: var(--size);
position: absolute;
left: 50%;
top: 50%;
margin-left: calc(var(--size) / 2);
margin-top: calc(var(--size) / 2);
object-fit: cover; /* 保持图片纵横比 */
transition: all 0.8s ease-out ;
transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * -1px));
}
</style>
<body>
<div id="container"></div>
</body>
此时我们已经完成了准备工作,接着完成反向排序的准备工作,dy越大则在html中排列就越小,超越原点即为负数;
二、将原点挪至中心点
原点在与你想让它在那个位置,它都可以排列到那个位置,这里我想让它在容器内渲染,所以我想设置在容器内的中心位置,那么这个中心位置,其实就是我给定的最大值与最小值和的一半就是我的中心点;
那么我只要让其对应的横坐标点,减去当前的原点,就是它当前需要移动的距离,也就是dx,y同理;
曲线函数
不管是曲线,还是抛物线,还是斜线,其实都是有一个方程式,或者叫做函数来表示;
那么如果是在数学中;
y=x; 那就表示一段45度的斜线;
y=-x ; 那就表示一段-45度的斜线;
y=sin(x); 那就表示连绵的曲线;
数学坐标系中是无限延伸的,但在我们的浏览器中,他是有一个范围呢;所以我们还需要给他一个范围
如果是在代码中,如果我们得到了曲线的表示函数,接下来就是将其用html、css、js来绘制到我们的电脑屏幕中;
我们先来写一个类来表示这个计算类
javascript
class Curve {
/**
* 创建一个计算类的实例 *
* @constructor
* @param {Function} curveFunc 要添加的任务实例
* @param {Array} xRange x取值范围
* @param {Array} yRange y取值范围
*/
constructor(curveFunc, xRange, yRange) {
this.xRange = xRange
this.yRange = yRange
this.curveFunc = curveFunc
}
/**
* 计算曲线函数
* @param {Number} x x值
* @returns {Number} y y值
*/
getY(x) {
let y = this.curveFunc(x)
if (y < this.yRange[0]) {
y = this.yRange[0]
}
if (y > this.yRange[1]) {
y = this.yRange[1]
}
return y
}
}
上面我们已经创建好一个计算类、具体释义有jsdoc注释;
那么我们如何来表示曲线或者直线呢
javascript
const line = new Curve(x=>x,[-1,1],[-1,1])
const wave = new Curve(
x => Math.sin(x),
[-1 * Math.PI, 3 * Math.PI],
[-1, 1]
)
这样就表示出来了;
曲线:y = wave.getY(x);
直线:y = line.getY(x);
那么关于坐标关系我们已经用代码描述出来绘制就是最简单的事了;
html
<div id="container"></div>
假设我们现在有这么一个div;
然后我们先在div里创建一些图片(当然这里也可以换成你喜欢的样式);
javascript
const box = document.querySelector("#container")
const initImg = className => {
const frag = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
const img = document.createElement("img")
img.classList.add(className)
img.src = "./test.jpg" // 设置图片路径
frag.appendChild(img)
}
box.appendChild(frag)
}
initImg("curveImg")
接下来就是渲染
javascript
const layout = (curve, doms, width, height) => {
const [minX, maxX] = curve.xRange
const [minY, maxY] = curve.yRange
// 步长
const xStep = (maxX - minX) / (doms.length - 1)
// 与实际坐标轴的比例
const xScale = width / (maxX - minX)
const yScale = height / (maxY - minY)
const cx = (maxX + minX) / 2
const cy = (maxY + minY) / 2
for (let i = 0; i < doms.length; i++) {
const dom = doms[i]
const x = minX + i * xStep
const y = curve.getY(x)
const dx = (x - cx) * xScale
const dy = (y - cy) * yScale
dom.style.setProperty("--dx", dx)
dom.style.setProperty("--dy", dy)
}
}
接下来只要我们执行layout函数,那么图片就会进行对应的渲染;
上全部代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#container {
width: 100vw;
height: 100vh;
background-color: aqua;
position: relative;
overflow: auto;
}
.curveImg {
--size: 30px;
--dx: 1;
--dy: 1;
border-radius: calc(var(--size) / 2);
width: var(--size);
height: var(--size);
position: absolute;
left: 50%;
top: 50%;
margin-left: calc(var(--size) / 2);
margin-top: calc(var(--size) / 2);
object-fit: cover; /* 保持图片纵横比 */
transition: all 0.8s ease-out ;
transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * -1px));
}
</style>
</head>
<body>
<div>
<button onclick="renderLine()">直线</button>
<button onclick="renderWave()">曲线</button>
<button onclick="renderLineX()">x线</button>
<button onclick="renderWaveX()">交叉曲线线</button>
</div>
<div id="container"></div>
<script>
class Curve {
/**
* 创建一个计算类的实例 *
* @constructor
* @param {Function} curveFunc 要添加的任务实例
* @param {Array} xRange x取值范围
* @param {Array} yRange y取值范围
*/
constructor(curveFunc, xRange, yRange) {
this.xRange = xRange
this.yRange = yRange
this.curveFunc = curveFunc
}
/**
* 计算曲线函数
* @param {Number} x x值
* @returns {Number} y y值
*/
getY(x) {
let y = this.curveFunc(x)
if (y < this.yRange[0]) {
y = this.yRange[0]
}
if (y > this.yRange[1]) {
y = this.yRange[1]
}
return y
}
}
const layout = (curve, doms, width, height) => {
const [minX, maxX] = curve.xRange
const [minY, maxY] = curve.yRange
const xStep = (maxX - minX) / (doms.length - 1)
const xScale = width / (maxX - minX)
const yScale = height / (maxY - minY)
const cx = (maxX + minX) / 2
const cy = (maxY + minY) / 2
for (let i = 0; i < doms.length; i++) {
const dom = doms[i]
const x = minX + i * xStep
const y = curve.getY(x)
const dx = (x - cx) * xScale
const dy = (y - cy) * yScale
dom.style.setProperty("--dx", dx)
dom.style.setProperty("--dy", dy)
}
}
const box = document.querySelector("#container")
const initImg = className => {
const frag = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
const img = document.createElement("img")
img.classList.add(className)
img.src = "./test.jpg" // 设置图片路径
frag.appendChild(img)
}
box.appendChild(frag)
}
initImg("curveImg")
const images = document.querySelectorAll(".curveImg")
const wave = new Curve(
x => Math.sin(x),
[-1 * Math.PI, 3 * Math.PI],
[-1, 1]
)
const wave2 = new Curve(
x => Math.sin(x),
[0 * Math.PI, 4 * Math.PI],
[-1, 1]
)
const line = new Curve(x => x, [-1, 1], [-1, 1])
const line2 = new Curve(x => -x, [-1, 1], [-1, 1])
const middleIndex = Math.floor(images.length / 2)
const dom1 = Array.from(images).slice(0,middleIndex);
const dom2 = Array.from(images).slice(middleIndex);
function renderLine() {
layout(line, images, box.clientWidth - 200, box.clientHeight - 200)
}
function renderWave() {
layout(wave, images, box.clientWidth - 200, box.clientHeight - 200)
}
function renderLineX() {
layout(line, dom1, box.clientWidth - 200, box.clientHeight - 200)
layout(line2, dom2, box.clientWidth - 200, box.clientHeight - 200)
}
function renderWaveX() {
layout(wave, dom1, box.clientWidth - 200, box.clientHeight - 200)
layout(wave2, dom2, box.clientWidth - 200, box.clientHeight - 200)
}
document.addEventListener("DOMContentLoaded", () => {
renderLine()
})
</script>
</body>
</html>