mirror of
https://gitee.com/coder-xiaomo/algorithm-visualization
synced 2025-09-09 22:21:39 +08:00
639 lines
22 KiB
JavaScript
639 lines
22 KiB
JavaScript
/**
|
||
* WorkSpace class v0.1.0
|
||
*
|
||
* @author coder-xiaomo
|
||
* @date 2022-05-15
|
||
*
|
||
* Released under the MIT license
|
||
*/
|
||
class WorkSpace {
|
||
static settings = null;
|
||
static primaryCanvas = null;
|
||
|
||
constructor(settings) {
|
||
this.settings = settings
|
||
settings.workSpace = this
|
||
|
||
// 清除原有内容
|
||
settings.container
|
||
.style("width", settings.width)
|
||
.style("height", settings.height)
|
||
// .attr("width", settings.width)
|
||
// .attr("height", settings.height)
|
||
.html("")
|
||
|
||
// 创建工作区SVG
|
||
this.primaryCanvas = settings.container.append("svg")
|
||
// 添加id
|
||
.attr("id", "primaryCanvas")
|
||
// 设置 SVG 宽高
|
||
.attr("width", "100%")
|
||
.attr("height", "100%")
|
||
// 背景色
|
||
.style("background-color", settings.colorMap["background"])
|
||
}
|
||
}
|
||
|
||
class ViBase {
|
||
static workSpace = null;
|
||
constructor(workSpace) {
|
||
this.workSpace = workSpace
|
||
}
|
||
}
|
||
class ArrayVi extends ViBase {
|
||
// [47, 11, 50, 13, 16, 49, 8, 9, 38, 27, 20]
|
||
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||
static listData
|
||
|
||
// 网页加载完毕初始化事件
|
||
initialize({ elementId }) {
|
||
let controlDiv = document.getElementById("control-div")
|
||
controlDiv.style.textAlign = "center"
|
||
|
||
var sortClassList = getSortClassList();
|
||
console.log(sortClassList);
|
||
var DOMFragment = document.createDocumentFragment()
|
||
|
||
var selector = document.createElement("select")
|
||
for (let i = 0; i < sortClassList.length; i++) {
|
||
const sortClass = new sortClassList[i](animation)
|
||
const sortClassInfo = sortClass.info()
|
||
|
||
// 跳过未完成的算法
|
||
if (!sortClassInfo['available'])
|
||
continue
|
||
|
||
let option = document.createElement("option")
|
||
option.value = sortClassInfo['name']
|
||
option.innerText = sortClassInfo['name']
|
||
selector.appendChild(option)
|
||
}
|
||
DOMFragment.appendChild(selector)
|
||
|
||
let ctrlBtn = document.createElement("button")
|
||
ctrlBtn.innerHTML = "开始排序"
|
||
let that = this
|
||
ctrlBtn.onclick = function () {
|
||
// 点击排序算法按钮
|
||
// if (!that.updateListDataArray(elementId, { doNotAlert: false }))
|
||
// return
|
||
if (!that.listData || that.listData.length == 0) {
|
||
alert("数组为空")
|
||
return
|
||
}
|
||
|
||
// 隐藏一些东西,显示一些东西
|
||
controlDiv.style.display = 'none'
|
||
d3.select("#console-div")
|
||
.style("display", "")
|
||
d3.select("#console-current-algorithm")
|
||
.style("text-align", "center")
|
||
.html(selector.value/*sortClassInfo['name']*/)
|
||
|
||
// 找到对应的算法,然后开始排序
|
||
for (let i = 0; i < sortClassList.length; i++) {
|
||
const sortClass = new sortClassList[i](animation)
|
||
const sortClassInfo = sortClass.info()
|
||
|
||
if (sortClassInfo['name'] === selector.value) {
|
||
sortClass.doSortWithAnimation(elementId)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
DOMFragment.appendChild(ctrlBtn)
|
||
|
||
controlDiv.appendChild(DOMFragment)
|
||
|
||
// 生成一个随机数组
|
||
this.randomListDataArray(elementId)
|
||
|
||
// 显示 siderbar
|
||
d3.select("#sidebar").style("display", "")
|
||
}
|
||
|
||
// 将数组显示到输入框中
|
||
updateListDataInput() {
|
||
document.getElementById("array-input").value = this.listData.join(",")
|
||
}
|
||
|
||
// Array 内容改变时
|
||
updateListDataArray(elementId, { doNotAlert = false }) {
|
||
var val = document.getElementById("array-input").value
|
||
try {
|
||
var preList = val.replaceAll(",", ",").split(",")
|
||
this.listData = []
|
||
preList.forEach(element => {
|
||
if (element.trim() === "" || isNaN(element))
|
||
return
|
||
this.listData.push(Number(element))
|
||
});
|
||
arrayVi.initArray(elementId, this.listData)
|
||
} catch (err) {
|
||
console.log(err)
|
||
if (!doNotAlert)
|
||
alert("输入不正确,请检查!")
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// 随机 Array
|
||
randomListDataArray(elementId) {
|
||
function getRandom(length) {
|
||
return Math.floor(Math.random() * length); // 可均衡获取 0 到 length-1 的随机整数。
|
||
}
|
||
// 获取一个 6 - 12 以内的随机数
|
||
var len = 6 + getRandom(13 - 6)
|
||
|
||
this.listData = []
|
||
for (let i = 0; i < len; i++) {
|
||
this.listData.push(getRandom(51))
|
||
}
|
||
this.updateListDataInput()
|
||
this.updateListDataArray(elementId, { doNotAlert: false })
|
||
}
|
||
|
||
// 绘制数组
|
||
initArray(elementId, listData) {
|
||
console.log("initArray")
|
||
let fragment = shape.getLinkedListFragment(elementId, listData, {
|
||
x: 100,
|
||
y: 100,
|
||
width: "100px",
|
||
height: "100px",
|
||
})
|
||
|
||
// console.log(fragment)
|
||
workSpace.primaryCanvas.html("")
|
||
|
||
// 添加水印 居中
|
||
var watermarkWidth = settings.outerSize.height * 0.65
|
||
shape.addWatermark(elementId, {
|
||
imageSrc: "./assets/image/logo-small.svg",
|
||
})
|
||
.attr('id', 'watermark-c-c')
|
||
.attr('x', settings.outerSize.width / 2)
|
||
.attr('y', settings.outerSize.height / 2)
|
||
.style('width', watermarkWidth + 'px')
|
||
.style('height', watermarkWidth + 'px')
|
||
.style('transform', `translate(-${watermarkWidth / 2}px, -${watermarkWidth / 2}px)`)
|
||
.style('opacity', '0.015')
|
||
.style('transition', '0.2s')
|
||
|
||
// 添加水印 右下角
|
||
var watermarkWidth = 60
|
||
shape.addWatermark(elementId, {
|
||
imageSrc: "./assets/image/logo-small.svg",
|
||
})
|
||
.attr('id', 'watermark-r-b')
|
||
.attr('x', settings.outerSize.width)
|
||
.attr('y', settings.outerSize.height)
|
||
.style('width', watermarkWidth + 'px')
|
||
.style('height', watermarkWidth + 'px')
|
||
.style('transform', 'translate(-80px, -80px)')
|
||
|
||
workSpace.primaryCanvas.node().appendChild(fragment)
|
||
document.getElementById(elementId).customAttr = fragment.customAttr
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Shape class v0.1.0
|
||
*
|
||
* @author coder-xiaomo
|
||
* @date 2022-05-15
|
||
*
|
||
* Released under the MIT license
|
||
*/
|
||
class Shape {
|
||
static workSpace = null;
|
||
constructor(workSpace) {
|
||
this.workSpace = workSpace
|
||
}
|
||
|
||
addShape(shape, id) {
|
||
var settings = this.workSpace.settings
|
||
return workSpace.primaryCanvas.append(shape)
|
||
.style("transform", `translate(${settings.margin.left}px, ${settings.margin.top}px)`)
|
||
.attr("id", id)
|
||
}
|
||
addShape_NoTransform(shape, id) {
|
||
return workSpace.primaryCanvas.append(shape)
|
||
.attr("id", id)
|
||
}
|
||
|
||
// 添加矩形
|
||
rectangle(id, { x, y, width, height, fillColor = "white", strokeColor = "black" }) {
|
||
return this.addShape("rect", id)
|
||
.attr("x", x)
|
||
.attr("y", y)
|
||
.attr("width", width)
|
||
.attr("height", height)
|
||
.style("fill", fillColor)
|
||
.style("stroke", strokeColor)
|
||
}
|
||
|
||
// 添加圆形
|
||
circle(id, { x, y, radius, fillColor = "white", strokeColor = "black" }) {
|
||
return this.addShape("circle", id)
|
||
.attr("cx", x)
|
||
.attr("cy", y)
|
||
.attr("r", radius)
|
||
.style("fill", fillColor)
|
||
.style("stroke", strokeColor)
|
||
}
|
||
|
||
// 添加文本
|
||
text(id, { x, y, text, fillColor }) {
|
||
return this.addShape("text", id)
|
||
.attr("x", x)
|
||
.attr("y", y)
|
||
.text(text)
|
||
.style("fill", fillColor)
|
||
}
|
||
|
||
// 添加线
|
||
line(id, { x1, y1, x2, y2, strokeColor }) {
|
||
return this.addShape("line", id)
|
||
.attr("x1", x1)
|
||
.attr("y1", y1)
|
||
.attr("x2", x2)
|
||
.attr("y2", y2)
|
||
.style("stroke", strokeColor)
|
||
}
|
||
|
||
// 添加路径
|
||
path(id, d, { fillColor = "white", strokeColor = "black" }) {
|
||
return this.addShape("path", id)
|
||
.attr("d", d)
|
||
.style("fill", fillColor)
|
||
.style("stroke", strokeColor)
|
||
}
|
||
|
||
// 添加坐标轴
|
||
axis(id, { transform, axis }) {
|
||
return this.addShape_NoTransform("g", id)
|
||
.attr("transform", transform)
|
||
.call(axis)
|
||
}
|
||
|
||
// 添加一个链表节点
|
||
addNode(id, x, y, width, height, text) {
|
||
var primaryCanvas = workSpace.primaryCanvas
|
||
primaryCanvas.append("rect", id)
|
||
.attr("x", x)
|
||
.attr("y", y)
|
||
.attr("width", width)
|
||
.attr("height", height)
|
||
.style("fill", workSpace.settings.colorMap["fill"])
|
||
.style("stroke", workSpace.settings.colorMap["stroke"])
|
||
primaryCanvas.append("text", id + "_text")
|
||
.attr("x", x)
|
||
.attr("y", y)
|
||
// .attr("x", x + width / 2)
|
||
// .attr("y", y + height / 2)
|
||
.text(text)
|
||
.style("fill", workSpace.settings.colorMap["text"])
|
||
}
|
||
|
||
|
||
// 添加一个图片水印
|
||
addWatermark(id, { imageSrc }) {
|
||
var primaryCanvas = workSpace.primaryCanvas
|
||
return primaryCanvas.append("image", id)
|
||
.attr("xlink:href", imageSrc)
|
||
}
|
||
|
||
// 绘制一个链表
|
||
getLinkedListFragment(id, nodes) {
|
||
var settings = this.workSpace.settings
|
||
|
||
let displayMaxWidth = 1 * settings.innerSize.width
|
||
let displayMaxHeight = 1 * settings.innerSize.height
|
||
let areaWidth = 1 * nodes.length // 按照1个Unit来计算
|
||
let areaHeight = 1 // 按照1个Unit来计算
|
||
|
||
let oneUnit = 100
|
||
// 可以假设高度相等比较宽度,这样好理解
|
||
if (displayMaxWidth / displayMaxHeight > areaWidth / areaHeight) {
|
||
// 展示区域左右有多余空间
|
||
oneUnit = displayMaxHeight / areaHeight
|
||
} else {
|
||
// 展示区域上下有空间(或刚刚好)
|
||
oneUnit = displayMaxWidth / areaWidth
|
||
}
|
||
|
||
// 定义最大值
|
||
if (oneUnit > 120) oneUnit = 120
|
||
|
||
let fragment = document.createDocumentFragment()
|
||
fragment.customAttr = {
|
||
id: id,
|
||
nodes: nodes,
|
||
type: "linkedList",
|
||
oneUnit: oneUnit,
|
||
gsapTimeline: gsap.timeline({
|
||
onStart: function () {
|
||
consoleClear()
|
||
},
|
||
onComplete: function () {
|
||
consoleLog(`排序完成`)
|
||
console.log("all done")
|
||
// this.seek(0)
|
||
}
|
||
})
|
||
}
|
||
// console.log(fragment.customAttr)
|
||
|
||
// <g></g> 元素不能设置 width 和 height
|
||
|
||
let g = d3.select(fragment)
|
||
.append("svg:g")
|
||
.attr("id", id)
|
||
.attr("fill", "white")
|
||
.attr("transform", `translate(${settings.margin.left}, ${settings.margin.top})`)
|
||
|
||
for (let i = 0; i < nodes.length; i++) {
|
||
let node = nodes[i]
|
||
let _g = g.append("svg:g")
|
||
|
||
_g.append("svg:rect")
|
||
.attr("x", i * oneUnit)
|
||
.attr("y", 0)
|
||
.attr("width", oneUnit)
|
||
.attr("height", oneUnit)
|
||
.attr("fill", settings.colorMap["fill"])
|
||
.attr("stroke", settings.colorMap["stroke"])
|
||
|
||
_g.append("svg:text")
|
||
.text(node)
|
||
.attr("x", i * oneUnit + oneUnit / 2)
|
||
.attr("y", oneUnit / 2)
|
||
.attr("width", oneUnit)
|
||
.attr("height", oneUnit)
|
||
.attr("fill", settings.colorMap["text"])
|
||
|
||
// 调试用
|
||
if (settings.debugMode)
|
||
_g.append("svg:text")
|
||
.text(i)
|
||
.attr("x", i * oneUnit)
|
||
.attr("y", oneUnit)
|
||
.attr("width", oneUnit)
|
||
.attr("height", oneUnit)
|
||
.attr("fill", "black")
|
||
|
||
// console.log(text.node().getBBox())
|
||
// console.log(text.node().getBoundingClientRect())
|
||
}
|
||
|
||
// g.append("svg:rect")
|
||
// .attr("width", oneUnit * nodes.length)
|
||
// .attr("height", oneUnit)
|
||
// .style("fill", "none")
|
||
// .style("stroke", "green")
|
||
return fragment
|
||
}
|
||
}
|
||
|
||
class VectorAnimation {
|
||
constructor(workSpace) {
|
||
this.workSpace = workSpace
|
||
}
|
||
|
||
swapElementAttr(attrName/* or attrNameList */, element1, element2) {
|
||
function exchange(attrName) {
|
||
// 保存 element1 的属性,将 element2 的属性赋值给 element1, 再将保存的属性赋值给 element2
|
||
var tmp = element1.getAttribute(attrName)
|
||
element1.setAttribute(attrName, element2.getAttribute(attrName))
|
||
element2.setAttribute(attrName, tmp)
|
||
}
|
||
if (typeof attrName === "string") {
|
||
exchange(attrName)
|
||
} else if (typeof attrName === "object") {
|
||
for (let i = 0; i < attrName.length; i++) {
|
||
exchange(attrName[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
swapElementInnerHTML(element1, element2) {
|
||
var tmp = element1.innerHTML
|
||
element1.innerHTML = element2.innerHTML
|
||
element2.innerHTML = tmp
|
||
}
|
||
|
||
swapElementsAttr(elementPairList) {
|
||
// [
|
||
// [attrName or attrNameList, element1, element2],
|
||
// [attrName or attrNameList, element1, element2],
|
||
// ...
|
||
// ]
|
||
for (let i = 0; i < elementPairList.length; i++) {
|
||
let elementPair = elementPairList[i]
|
||
this.swapElementAttr(elementPair[0], elementPair[1], elementPair[2])
|
||
}
|
||
}
|
||
|
||
// 交换数组元素
|
||
swapLinkedListItems(id, [fromIndex, toIndex]) {
|
||
if (fromIndex < toIndex)
|
||
[fromIndex, toIndex] = [toIndex, fromIndex]
|
||
|
||
var settings = this.workSpace.settings
|
||
let linkedList = document.getElementById(id)
|
||
let customAttr = linkedList.customAttr
|
||
// console.log(customAttr)
|
||
|
||
var gList = linkedList.childNodes
|
||
|
||
let from = gList[fromIndex]
|
||
let to = gList[toIndex]
|
||
|
||
var deltaX = customAttr.oneUnit * (fromIndex - toIndex);
|
||
var deltaY = customAttr.oneUnit * 1.08
|
||
|
||
var animateSettings = this.workSpace.settings.animation.getConf()
|
||
|
||
// 如果是相邻的两个元素交换,或者要交换的两个元素是同一个元素
|
||
if (Math.abs(fromIndex - toIndex) <= 1) {
|
||
deltaY /= 2
|
||
animateSettings.duration *= 0.6
|
||
}
|
||
|
||
var that = this
|
||
let timeline = gsap.timeline({
|
||
onStart: function () {
|
||
displayCurrentArray(customAttr.nodes)
|
||
|
||
consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`)
|
||
},
|
||
onComplete: function () {
|
||
// 交换DOM元素中的值
|
||
that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1])
|
||
|
||
// 交换 node 列表中的值
|
||
// console.log(customAttr.nodes)
|
||
let tmp = customAttr.nodes[fromIndex]
|
||
customAttr.nodes[fromIndex] = customAttr.nodes[toIndex]
|
||
customAttr.nodes[toIndex] = tmp
|
||
|
||
// console.log(customAttr.nodes)
|
||
displayCurrentArray(customAttr.nodes)
|
||
|
||
console.log("animation done (swap)")
|
||
}
|
||
}).add([
|
||
gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
|
||
gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
|
||
gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }),
|
||
gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }),
|
||
gsap.to(from.childNodes, { ...animateSettings, y: -deltaY }),
|
||
gsap.to(to.childNodes, { ...animateSettings, y: deltaY }),
|
||
]).add([
|
||
gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }),
|
||
gsap.to(to.childNodes, { ...animateSettings, x: deltaX }),
|
||
]).add([
|
||
gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }),
|
||
gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }),
|
||
gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }),
|
||
gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }),
|
||
gsap.to(from.childNodes, { ...animateSettings, y: 0 }),
|
||
gsap.to(to.childNodes, { ...animateSettings, y: 0 }),
|
||
]).add([
|
||
// 恢复到动画之前的状态
|
||
gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }),
|
||
gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }),
|
||
])
|
||
|
||
customAttr.gsapTimeline.add(timeline)
|
||
}
|
||
|
||
// 高亮数组元素
|
||
highlightLinkedListItems(id, indexList, onStartCallback) {
|
||
let linkedList = document.getElementById(id)
|
||
let customAttr = linkedList.customAttr
|
||
var gList = linkedList.childNodes
|
||
|
||
if (typeof (indexList) === "number")
|
||
indexList = [indexList]
|
||
|
||
var hightlightElementList_fill = []
|
||
var hightlightElementList_text = []
|
||
for (let i = 0; i < indexList.length; i++) {
|
||
const index = indexList[i];
|
||
hightlightElementList_fill.push(gList[index].childNodes[0])
|
||
hightlightElementList_text.push(gList[index].childNodes[1])
|
||
}
|
||
var animateSettings = this.workSpace.settings.animation.getConf()
|
||
|
||
let timeline = gsap.timeline({
|
||
onStart: function () {
|
||
displayCurrentArray(customAttr.nodes)
|
||
|
||
if (onStartCallback)
|
||
onStartCallback()
|
||
},
|
||
onComplete: function () {
|
||
console.log("animation done (hightlight)")
|
||
}
|
||
}).add([
|
||
gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
|
||
gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text_focus"] }),
|
||
]).add([
|
||
gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill"] }),
|
||
gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text"] }),
|
||
])
|
||
|
||
customAttr.gsapTimeline.add(timeline)
|
||
}
|
||
|
||
// 比较数组元素
|
||
compareLinkedListItems(id, index1, index2) {
|
||
this.highlightLinkedListItems(id, [index1, index2], function () {
|
||
consoleLog(`比较索引为 ${index1} 和 ${index2} 的两个元素`)
|
||
})
|
||
}
|
||
|
||
// 弹出/弹回 数组元素
|
||
popupLinkedListItems(id, index, { popBack = false }) {
|
||
let linkedList = document.getElementById(id)
|
||
let customAttr = linkedList.customAttr
|
||
var gList = linkedList.childNodes
|
||
|
||
var deltaY = customAttr.oneUnit * 1.08
|
||
if (popBack) deltaY = 0
|
||
|
||
let timeline = gsap.timeline({
|
||
onStart: function () {
|
||
if (typeof (onStartCallback) === "function")
|
||
onStartCallback()
|
||
},
|
||
onComplete: function () {
|
||
console.log("animation done (popup)")
|
||
}
|
||
}).add([
|
||
gsap.to(gList[index], { ...this.workSpace.settings.animation.getConf(), y: deltaY }),
|
||
])
|
||
|
||
customAttr.gsapTimeline.add(timeline)
|
||
}
|
||
|
||
// 交换相邻数组元素位置(仅在水平方向呼唤,垂直方向不做调整) 【fromIndex 是y方向上突出的元素】
|
||
exchangeLinkedListItems(id, fromIndex, toIndex) {
|
||
if (fromIndex < toIndex)
|
||
[fromIndex, toIndex] = [toIndex, fromIndex]
|
||
|
||
var settings = this.workSpace.settings
|
||
let linkedList = document.getElementById(id)
|
||
let customAttr = linkedList.customAttr
|
||
// console.log(customAttr)
|
||
|
||
var gList = linkedList.childNodes
|
||
|
||
let from = gList[fromIndex]
|
||
let to = gList[toIndex]
|
||
|
||
var deltaX = customAttr.oneUnit * (fromIndex - toIndex);
|
||
|
||
var deltaY = customAttr.oneUnit * 1.08 // 要跟 popupLinkedListItems 函数中的 deltaY 完全一致
|
||
var animateSettings = settings.animation.getConf()
|
||
|
||
var that = this
|
||
let timeline = gsap.timeline({
|
||
onStart: function () {
|
||
displayCurrentArray(customAttr.nodes)
|
||
|
||
consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`)
|
||
},
|
||
onComplete: function () {
|
||
// 交换DOM元素中的值
|
||
that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1])
|
||
|
||
// 交换 node 列表中的值
|
||
// console.log(customAttr.nodes)
|
||
let tmp = customAttr.nodes[fromIndex]
|
||
customAttr.nodes[fromIndex] = customAttr.nodes[toIndex]
|
||
customAttr.nodes[toIndex] = tmp
|
||
|
||
// console.log(customAttr.nodes)
|
||
displayCurrentArray(customAttr.nodes)
|
||
|
||
console.log("animation done (exchange)")
|
||
}
|
||
}).add([
|
||
gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }),
|
||
gsap.to(to.childNodes, { ...animateSettings, x: deltaX }),
|
||
]).add([
|
||
// 恢复到动画之前的状态
|
||
gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0 }),
|
||
gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0 }),
|
||
// 由于两个元素一个突出,一个不突出,所以还要交换两个y
|
||
gsap.to(from, { ...animateSettings, duration: 0, y: 0 }),
|
||
gsap.to(to, { ...animateSettings, duration: 0, y: deltaY }),
|
||
])
|
||
|
||
customAttr.gsapTimeline.add(timeline)
|
||
}
|
||
} |