Viết lại fadin sử dụng trong theme Astra (ok)

src\test.ts

// Global exposure (similar to the original IIFE)
interface Window {
  fadin: typeof fadin;
}
// interfaces.ts
interface AnimationQueueOptions {
  items: HTMLElement[];
  func: (item: HTMLElement) => void;
  delay: number;
}
interface FadinOptions {
  delay?: number;
  selector?: string;
  noInitalScrollEvent?: boolean;
  animationFunction?: (item: HTMLElement) => void;
}
interface VisibleHiddenItems {
  visible: HTMLElement[];
  hidden: HTMLElement[];
}
// animation-queue.ts
class AnimationQueue {
  private stack: HTMLElement[];
  private userFunction: (item: HTMLElement) => void;
  private delay: number;
  private _interval: number | undefined;
  constructor(options: AnimationQueueOptions) {
    this.stack = options.items;
    this.userFunction = options.func;
    this.delay = options.delay;
  }
  pause(): void {
    if (this._interval) {
      clearInterval(this._interval);
    }
  }
  start(): void {
    if (!this.isDone()) {
      this._interval = window.setInterval(() => this.fire(), this.delay);
    }
  }
  reset(): void {
    this.stack = [];
    this.pause();
  }
  fire(): void {
    this.check();
    const item = this.stack.shift();
    if (item) {
      this.userFunction(item);
    }
    this.check();
  }
  check(): void {
    if (this.stack.length === 0) {
      if (this._interval) {
        clearInterval(this._interval);
      }
    }
  }
  isDone(): boolean {
    return this.stack.length <= 0;
  }
}
// animation-service.ts
class AnimationService {
  private query: string;
  private toAnimate: HTMLElement[];
  constructor(query: string) {
    this.query = query;
    this.toAnimate = Array.from(document.querySelectorAll<HTMLElement>(query));
  }
  animateItems(delay: number, customAnimationFunction?: (item: HTMLElement) => void): void {
    const itemsToAnimate = this.getItemsToAnimate();
    if (itemsToAnimate && itemsToAnimate.length > 0) {
      new AnimationQueue({
        items: itemsToAnimate,
        delay: delay,
        func: (item: HTMLElement) => {
          return customAnimationFunction ? customAnimationFunction(item) : this.assignDelayAndOpacity(item);
        }
      }).start();
    }
  }
  getItemsToAnimate(): HTMLElement[] | undefined {
    if (!this.isDone()) {
      const { visible, hidden } = this.getVisibleAndHiddenItems();
      this.toAnimate = hidden; // Only keep hidden items for the next check
      return visible;
    }
    return undefined;
  }
  reset(): void {
    this.toAnimate = Array.from(document.querySelectorAll<HTMLElement>(this.query));
    this.getVisibleAndHiddenItems().hidden.forEach((item: HTMLElement) => {
      item.style.opacity = "0";
      item.style.transform = "translate3d(0px, 100px, 0px)";
    });
  }
  isDone(): boolean {
    return this.toAnimate.length <= 0;
  }
  assignDelayAndOpacity(element: HTMLElement): void {
    if (element) {
      const delay = this.getDelay(element);
      Object.assign(element.style, {
        opacity: 1,
        transform: "translate3d(0,0,0)"
      });
      if (delay) {
        Object.assign(element.style, {
          transitionDelay: delay
        });
      }
    }
  }
  private getVisibleAndHiddenItems(): VisibleHiddenItems {
    return this.toAnimate.reduce((acc: VisibleHiddenItems, element: HTMLElement) => {
      const status = this.shouldBeVisible(element) ? "visible" : "hidden";
      acc[status].push(element);
      return acc;
    }, { visible: [], hidden: [] });
  }
  private getDelay(element: HTMLElement): string | undefined {
    return element?.dataset?.delay;
  }
  private shouldBeVisible(element: HTMLElement): boolean {
    const rect = element.getBoundingClientRect();
    const viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
    return !(rect.bottom < 0 || rect.top - viewportHeight >= 0) && element.style.opacity !== "1";
  }
}
// fadin.ts
const defaultOptions: FadinOptions = {
  delay: 200,
  selector: ".fadin",
  noInitalScrollEvent: false,
  animationFunction: undefined
};
class Fadin {
  private animationService: AnimationService;
  private _detach: () => void;
  constructor(options: FadinOptions = defaultOptions) {
    const mergedOptions = { ...defaultOptions, ...options };
    this.animationService = new AnimationService(mergedOptions.selector!);
    this._detach = this.setUpEventListener("scroll", () => {
      this.animationService.animateItems(mergedOptions.delay!, mergedOptions.animationFunction);
    });
    if (!mergedOptions.noInitalScrollEvent) {
      this.sendScroll();
    }
  }
  isDone(): boolean {
    return this.animationService.isDone();
  }
  detach(): () => void {
    return this._detach;
  }
  reset(): void {
    this.animationService.reset();
    this.sendScroll();
  }
  sendScroll(): void {
    window.dispatchEvent(new Event("scroll"));
  }
  private setUpEventListener(eventType: string, callback: () => void): () => void {
    const listener = () => {
      if (this.isDone()) {
        window.removeEventListener(eventType, listener, true);
      }
      callback();
    };
    window.addEventListener(eventType, listener);
    return () => window.removeEventListener(eventType, listener, true);
  }
}
// Exported function for convenience
function fadin(selector: string, options?: FadinOptions): Fadin {
  return new Fadin({ selector, ...options });
}
window.fadin = fadin;

Áp dụng

src\test.js

var __assign = (this && this.__assign) || function () {
  __assign = Object.assign || function (t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
      s = arguments[i];
      for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
        t[p] = s[p];
    }
    return t;
  };
  return __assign.apply(this, arguments);
};
// animation-queue.ts
var AnimationQueue = /** @class */ (function () {
  function AnimationQueue(options) {
    this.stack = options.items;
    this.userFunction = options.func;
    this.delay = options.delay;
  }
  AnimationQueue.prototype.pause = function () {
    if (this._interval) {
      clearInterval(this._interval);
    }
  };
  AnimationQueue.prototype.start = function () {
    var _this = this;
    if (!this.isDone()) {
      this._interval = window.setInterval(function () { return _this.fire(); }, this.delay);
    }
  };
  AnimationQueue.prototype.reset = function () {
    this.stack = [];
    this.pause();
  };
  AnimationQueue.prototype.fire = function () {
    this.check();
    var item = this.stack.shift();
    if (item) {
      this.userFunction(item);
    }
    this.check();
  };
  AnimationQueue.prototype.check = function () {
    if (this.stack.length === 0) {
      if (this._interval) {
        clearInterval(this._interval);
      }
    }
  };
  AnimationQueue.prototype.isDone = function () {
    return this.stack.length <= 0;
  };
  return AnimationQueue;
}());
// animation-service.ts
var AnimationService = /** @class */ (function () {
  function AnimationService(query) {
    this.query = query;
    this.toAnimate = Array.from(document.querySelectorAll(query));
  }
  AnimationService.prototype.animateItems = function (delay, customAnimationFunction) {
    var _this = this;
    var itemsToAnimate = this.getItemsToAnimate();
    if (itemsToAnimate && itemsToAnimate.length > 0) {
      new AnimationQueue({
        items: itemsToAnimate,
        delay: delay,
        func: function (item) {
          return customAnimationFunction ? customAnimationFunction(item) : _this.assignDelayAndOpacity(item);
        }
      }).start();
    }
  };
  AnimationService.prototype.getItemsToAnimate = function () {
    if (!this.isDone()) {
      var _a = this.getVisibleAndHiddenItems(), visible = _a.visible, hidden = _a.hidden;
      this.toAnimate = hidden; // Only keep hidden items for the next check
      return visible;
    }
    return undefined;
  };
  AnimationService.prototype.reset = function () {
    this.toAnimate = Array.from(document.querySelectorAll(this.query));
    this.getVisibleAndHiddenItems().hidden.forEach(function (item) {
      item.style.opacity = "0";
      item.style.transform = "translate3d(0px, 100px, 0px)";
    });
  };
  AnimationService.prototype.isDone = function () {
    return this.toAnimate.length <= 0;
  };
  AnimationService.prototype.assignDelayAndOpacity = function (element) {
    if (element) {
      var delay = this.getDelay(element);
      Object.assign(element.style, {
        opacity: 1,
        transform: "translate3d(0,0,0)"
      });
      if (delay) {
        Object.assign(element.style, {
          transitionDelay: delay
        });
      }
    }
  };
  AnimationService.prototype.getVisibleAndHiddenItems = function () {
    var _this = this;
    return this.toAnimate.reduce(function (acc, element) {
      var status = _this.shouldBeVisible(element) ? "visible" : "hidden";
      acc[status].push(element);
      return acc;
    }, { visible: [], hidden: [] });
  };
  AnimationService.prototype.getDelay = function (element) {
    var _a;
    return (_a = element === null || element === void 0 ? void 0 : element.dataset) === null || _a === void 0 ? void 0 : _a.delay;
  };
  AnimationService.prototype.shouldBeVisible = function (element) {
    var rect = element.getBoundingClientRect();
    var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
    return !(rect.bottom < 0 || rect.top - viewportHeight >= 0) && element.style.opacity !== "1";
  };
  return AnimationService;
}());
// fadin.ts
var defaultOptions = {
  delay: 200,
  selector: ".fadin",
  noInitalScrollEvent: false,
  animationFunction: undefined
};
var Fadin = /** @class */ (function () {
  function Fadin(options) {
    if (options === void 0) { options = defaultOptions; }
    var _this = this;
    var mergedOptions = __assign(__assign({}, defaultOptions), options);
    this.animationService = new AnimationService(mergedOptions.selector);
    this._detach = this.setUpEventListener("scroll", function () {
      _this.animationService.animateItems(mergedOptions.delay, mergedOptions.animationFunction);
    });
    if (!mergedOptions.noInitalScrollEvent) {
      this.sendScroll();
    }
  }
  Fadin.prototype.isDone = function () {
    return this.animationService.isDone();
  };
  Fadin.prototype.detach = function () {
    return this._detach;
  };
  Fadin.prototype.reset = function () {
    this.animationService.reset();
    this.sendScroll();
  };
  Fadin.prototype.sendScroll = function () {
    window.dispatchEvent(new Event("scroll"));
  };
  Fadin.prototype.setUpEventListener = function (eventType, callback) {
    var _this = this;
    var listener = function () {
      if (_this.isDone()) {
        window.removeEventListener(eventType, listener, true);
      }
      callback();
    };
    window.addEventListener(eventType, listener);
    return function () { return window.removeEventListener(eventType, listener, true); };
  };
  return Fadin;
}());
// Exported function for convenience
function fadin(selector, options) {
  return new Fadin(__assign({ selector: selector }, options));
}
window.fadin = fadin;

src\test.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>
    .fadin {
      opacity: 0;
      transition-property: opacity, transform;
      transform: translate3d(0, 100px, 0);
      transition-duration: 1s;
    }
  </style>
</head>
<body>
  <p class="fadin">Lorem ipsum dolor sit amet.</p>
  <p class="fadin">Lorem ipsum dolor sit amet.</p>
  <p class="fadin" style="margin-top: 450px;">Lorem ipsum dolor sit amet.</p>
  <p class="fadin" style="margin-top: 450px;">Lorem ipsum dolor sit amet.</p>
  <p class="fadin" style="margin-top: 450px;">Lorem ipsum dolor sit amet.</p>
  <script src="test.js"></script>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      fadin(".fadin")
    }
    );
    console.log("aaaaaa");
  </script>
</body>
</html>

Nghiên cứu từ file

astra-addon/assets/js/minified/reveal-effect.min.js

!function (t, e) {
  "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = t || self).fadin = e()
}(this, function () {
  "use strict";
  var n = function () {
    return (n = Object.assign || function (t) {
      for (var e, n = 1, i = arguments.length; n < i; n++)
        for (var o in e = arguments[n])
          Object.prototype.hasOwnProperty.call(e, o) && (t[o] = e[o]);
      return t
    }
    ).apply(this, arguments)
  }
    , o = function () {
      function t(t) {
        this.stack = t.items,
          this.userFunction = t.func,
          this.delay = t.delay
      }
      return t.prototype.pause = function () {
        clearInterval(this._interval)
      }
        ,
        t.prototype.start = function () {
          this.isDone() || (this._interval = window.setInterval(this.fire.bind(this), this.delay))
        }
        ,
        t.prototype.reset = function () {
          this.stack = [],
            this.pause()
        }
        ,
        t.prototype.fire = function () {
          this.check(),
            this.userFunction(this.stack.shift()),
            this.check()
        }
        ,
        t.prototype.check = function () {
          if (!this.stack.length)
            return clearInterval(this._interval)
        }
        ,
        t.prototype.isDone = function () {
          return this.stack.length <= 0
        }
        ,
        t
    }()
    , i = function () {
      function t(t) {
        this.query = t,
          this.toAnimate = Array.from(document.querySelectorAll(t))
      }
      return t.prototype.animateItems = function (t, e) {
        var n = this
          , i = this.getItemsToAnimate();
        i && new o({
          items: i,
          delay: t,
          func: function (t) {
            return e ? e(t) : n.assignDelayAndOpacity(t)
          }
        }).start()
      }
        ,
        t.prototype.getItemsToAnimate = function () {
          if (!this.isDone()) {
            var t = this.getVisibleAndHiddenItems()
              , e = t.visible
              , n = t.hidden;
            return this.toAnimate = n,
              e
          }
        }
        ,
        t.prototype.reset = function () {
          this.toAnimate = Array.from(document.querySelectorAll(this.query)),
            this.getVisibleAndHiddenItems().hidden.forEach(function (t) {
              return t.style.opacity = "0",
                t.style.transform = "translate3d(0px, 100px, 0px)",
                t
            })
        }
        ,
        t.prototype.isDone = function () {
          return this.toAnimate.length <= 0
        }
        ,
        t.prototype.assignDelayAndOpacity = function (t) {
          if (t) {
            var e = this.getDelay(t);
            Object.assign(t.style, {
              opacity: 1
            }),
              Object.assign(t.style, {
                transform: "translate3d(0,0,0)"
              }),
              e && Object.assign(t.style, {
                transitionDelay: e
              })
          }
        }
        ,
        t.prototype.getVisibleAndHiddenItems = function () {
          var i = this;
          return this.toAnimate.reduce(function (t, e) {
            var n = i.shouldBeVisible(e) ? "visible" : "hidden";
            return t[n] = t[n].concat(e),
              t
          }, {
            visible: [],
            hidden: []
          })
        }
        ,
        t.prototype.getDelay = function (t) {
          if (t && t.dataset && t.dataset.delay)
            return t.dataset.delay
        }
        ,
        t.prototype.shouldBeVisible = function (t) {
          var e = t.getBoundingClientRect()
            , n = Math.max(document.documentElement.clientHeight, window.innerHeight);
          return !(e.bottom < 0 || 0 <= e.top - n) && "1" !== t.style.opacity
        }
        ,
        t
    }()
    , r = {
      delay: 200,
      selector: ".fadin",
      noInitalScrollEvent: !1,
      animationFunction: void 0
    }
    , s = function () {
      function t(t) {
        void 0 === t && (t = r);
        var e = this
          , n = Object.assign({}, r, t);
        this.animationService = new i(n.selector),
          this._detach = this.setUpEventListener("scroll", function () {
            e.animationService.animateItems(n.delay, n.animationFunction)
          }),
          n.noInitalScrollEvent || this.sendScroll()
      }
      return t.prototype.isDone = function () {
        return this.animationService.isDone()
      }
        ,
        t.prototype.detach = function () {
          return this._detach()
        }
        ,
        t.prototype.reset = function () {
          this.animationService.reset(),
            this.sendScroll()
        }
        ,
        t.prototype.sendScroll = function () {
          window.dispatchEvent(new Event("scroll"))
        }
        ,
        t.prototype.setUpEventListener = function (t, e) {
          var n = this
            , i = function () {
              n.isDone() && window.removeEventListener(t, i, !0),
                e()
            };
          return window.addEventListener(t, i.bind(this)),
            function () {
              return window.removeEventListener(t, i, !0)
            }
        }
        ,
        t
    }();
  return function (t, e) {
    return new s(n({
      selector: t
    }, e))
  }
});

Nhờ Gemini giải quyết bài toán chuyển từ js sang ts

Có vẻ như bạn đã cung cấp một tệp JavaScript triển khai thư viện hoạt ảnh "fadin". Bạn đang yêu cầu chuyển đổi nó thành TypeScript hoặc làm cho nó dễ hiểu hơn.

Tôi chắc chắn có thể giúp bằng cách chia nhỏ mã JavaScript thành các phần dễ hiểu hơn và giải thích chức năng của nó. Việc chuyển đổi nó sang TypeScript sẽ liên quan đến việc thêm các định nghĩa kiểu, mà tôi cũng có thể minh họa.


Hiểu về mã JavaScript

Mã JavaScript được cung cấp thiết lập một mô-đun có tên là mô-đun fadinnày sẽ hoạt hình hóa các thành phần khi chúng hiển thị trong khung nhìn. Nó sử dụng phương pháp hoạt hình so le.

Sau đây là phân tích các thành phần chính:

1. n(Object.assign polyfill)

Đây là một polyfill đơn giản cho Object.assign. Nó đảm bảo rằng các trình duyệt cũ hơn không hỗ trợ gốc Object.assignvẫn có thể sử dụng chức năng của nó. Nó hợp nhất các thuộc tính từ một hoặc nhiều đối tượng nguồn vào một đối tượng đích.

2. o(Hàng đợi hoạt hình / Trình quản lý khoảng thời gian)

Lớp này ( otạm gọi là AnimationQueue) chịu trách nhiệm quản lý danh sách các mục và thực thi một chức năng nhất định trên mỗi mục với độ trễ được chỉ định.

  • constructor(t): Lấy một đối tượng với items(một mảng các phần tử để hoạt hình), func(hàm áp dụng cho từng mục) và delay(khoảng thời gian giữa các mục hoạt hình).

  • pause(): Dừng hoạt ảnh bằng cách xóa _interval.

  • start(): Bắt đầu hoạt ảnh. Nếu còn mục nào, nó sẽ thiết lập lệnh setIntervalgọi fire()lặp lại.

  • reset(): Xóa stack(các mục cần hoạt hình) và tạm dừng khoảng thời gian.

  • fire(): Xóa mục đầu tiên khỏi stack, gọi the userFunctiontrên mục đó, sau đó kiểm tra xem tất cả các mục đã được xử lý hay chưa.

  • check(): Kiểm tra xem stackcó trống không. Nếu trống, nó sẽ xóa _interval.

  • isDone(): Trả về truenếu tất cả các mục đã được hoạt hình hóa.

3. i(Dịch vụ hoạt hình)

Lớp này ( itạm gọi là AnimationService) quản lý các thành phần được hoạt hình hóa và xác định khả năng hiển thị của chúng.

  • constructor(t): Sử dụng querybộ chọn (ví dụ: ".fadin") và tìm tất cả các phần tử khớp trên trang, lưu trữ chúng trong toAnimate.

  • animateItems(t, e): Đây là trình kích hoạt hoạt ảnh cốt lõi. Nó lấy các mục hiện đang hiển thị và ẩn, sau đó tạo một AnimationQueuethể hiện để hoạt ảnh hóa các mục hiển thị. Nó cho phép một hàm hoạt ảnh tùy chỉnh ( e) hoặc mặc định là assignDelayAndOpacity.

  • getItemsToAnimate(): Lọc toAnimatedanh sách thành visiblecác hiddenmục dựa trên việc chúng có trong khung nhìn hay không. Sau đó, nó cập nhật this.toAnimateđể chỉ chứa hiddencác mục cho chu kỳ hoạt ảnh tiếp theo và trả về visiblecác mục.

  • reset(): Đặt lại toAnimatedanh sách thành tất cả các phần tử khớp với truy vấn ban đầu và đặt độ mờ ban đầu cũng như kiểu chuyển đổi của chúng (làm chúng ẩn đi và hơi lệch đi).

  • isDone(): Kiểm tra xem có còn thành phần nào để hoạt hình hay không.

  • assignDelayAndOpacity(t): Đây là hàm hoạt ảnh mặc định. Nó áp dụng opacity: 1, transform: translate3d(0,0,0)cho phần tử và tùy chọn a transitionDelaynếu được chỉ định trong thuộc tính của phần tử data-delay.

  • getVisibleAndHiddenItems(): Lặp lại toAnimatevà phân loại các phần tử theo visiblehoặc hiddendựa trên shouldBeVisible().

  • getDelay(t): Lấy data-delaythuộc tính từ một phần tử.

  • shouldBeVisible(t): Xác định xem một phần tử hiện có nằm trong khung nhìn hay không và độ mờ của phần tử đó có phải đã bằng 1 hay chưa.

4. r(Tùy chọn mặc định)

Đối tượng này ( r, chúng ta hãy gọi nó là defaultOptions) xác định cấu hình mặc định cho fadinthư viện, chẳng hạn như bộ chọn mặc định ( .fadin), độ trễ và liệu có kích hoạt sự kiện cuộn ban đầu hay không.

5. s(Lớp chính Fadin)

Lớp này ( schúng ta có thể gọi là Fadin) là điểm vào chính để sử dụng thư viện.

  • constructor(t): Khởi tạo Fadinphiên bản với các tùy chọn được cung cấp (hợp nhất chúng với defaultOptions). Nó tạo ra AnimationServicevà thiết lập trình lắng nghe sự kiện cuộn. Nếu noInitalScrollEventlà false, nó sẽ gửi một sự kiện cuộn để kích hoạt hoạt ảnh ban đầu.

  • isDone(): Kiểm tra xem dịch vụ hoạt hình đã hoàn tất việc tạo hoạt ảnh cho tất cả các thành phần hay chưa.

  • detach(): Xóa trình lắng nghe sự kiện cuộn.

  • reset(): Đặt lại AnimationServicevà gửi sự kiện cuộn mới để kích hoạt lại hoạt ảnh.

  • sendScroll(): Gửi một sự kiện cuộn mới trên window.

  • setUpEventListener(t, e): Một hàm trợ giúp để thêm một trình lắng nghe sự kiện và trả về một hàm để tách nó ra. Nó cũng tự động xóa trình lắng nghe sau khi tất cả các hoạt ảnh hoàn tất.

6. Chức năng xuất khẩu

Phần cuối cùng của tập lệnh xuất ra một hàm tạo một Fadinthể hiện mới, cho phép bạn sử dụng nó như fadin(".my-elements", { delay: 300 }).


Giải thích đơn giản (Khái niệm)

Hãy tưởng tượng bạn có một loạt hộp trên một trang web và bạn muốn chúng xuất hiện từng cái một một cách mượt mà khi bạn cuộn xuống và chúng xuất hiện trong chế độ xem của bạn.

Mã JavaScript này thực hiện điều đó bằng cách:

  1. Tìm các hộp : Xác định tất cả các thành phần trên trang mà bạn muốn tạo hiệu ứng động (ví dụ: các thành phần có lớp fadin).

  2. Kiểm tra khả năng hiển thị : Khi bạn cuộn, nó sẽ liên tục kiểm tra xem hộp nào vừa xuất hiện trên màn hình của bạn.

  3. Hoạt hình từng cái một : Đối với các hộp hiển thị, tính năng này làm cho chúng xuất hiện mượt mà, thường có độ trễ nhỏ giữa mỗi hộp, khiến chúng "mờ dần" và có khả năng trượt lên.

  4. Quản lý hoạt ảnh : Theo dõi các hộp đã xuất hiện và dừng kiểm tra hộp mới khi tất cả các hộp được chỉ định đã được hoạt ảnh.

  5. Tùy chọn : Bạn có thể tùy chỉnh các yếu tố như tốc độ xuất hiện, độ trễ giữa mỗi hộp và thậm chí xác định hiệu ứng hoạt hình tùy chỉnh của riêng bạn.


Chuyển đổi TypeScript (Ví dụ minh họa cho lớp khóa)

Việc chuyển đổi toàn bộ tệp sang TypeScript sẽ liên quan đến việc thêm giao diện và kiểu cho tất cả các lớp và phương thức của chúng. Sau đây là ví dụ về cách các lớp AnimationQueue( o) và AnimationService( i) có thể trông như thế nào trong TypeScript, cùng với một số giao diện:

Kiểu chữ

// interfaces.ts
interface AnimationQueueOptions {
  items: HTMLElement[];
  func: (item: HTMLElement) => void;
  delay: number;
}
interface FadinOptions {
  delay?: number;
  selector?: string;
  noInitalScrollEvent?: boolean;
  animationFunction?: (item: HTMLElement) => void;
}
interface VisibleHiddenItems {
  visible: HTMLElement[];
  hidden: HTMLElement[];
}
// animation-queue.ts
class AnimationQueue {
  private stack: HTMLElement[];
  private userFunction: (item: HTMLElement) => void;
  private delay: number;
  private _interval: number | undefined;
  constructor(options: AnimationQueueOptions) {
    this.stack = options.items;
    this.userFunction = options.func;
    this.delay = options.delay;
  }
  pause(): void {
    if (this._interval) {
      clearInterval(this._interval);
    }
  }
  start(): void {
    if (!this.isDone()) {
      this._interval = window.setInterval(() => this.fire(), this.delay);
    }
  }
  reset(): void {
    this.stack = [];
    this.pause();
  }
  fire(): void {
    this.check();
    const item = this.stack.shift();
    if (item) {
      this.userFunction(item);
    }
    this.check();
  }
  check(): void {
    if (this.stack.length === 0) {
      if (this._interval) {
        clearInterval(this._interval);
      }
    }
  }
  isDone(): boolean {
    return this.stack.length <= 0;
  }
}
// animation-service.ts
class AnimationService {
  private query: string;
  private toAnimate: HTMLElement[];
  constructor(query: string) {
    this.query = query;
    this.toAnimate = Array.from(document.querySelectorAll < HTMLElement > (query));
  }
  animateItems(delay: number, customAnimationFunction?: (item: HTMLElement) => void): void {
    const itemsToAnimate = this.getItemsToAnimate();
    if (itemsToAnimate && itemsToAnimate.length > 0) {
      new AnimationQueue({
        items: itemsToAnimate,
        delay: delay,
        func: (item: HTMLElement) => {
          return customAnimationFunction ? customAnimationFunction(item) : this.assignDelayAndOpacity(item);
        }
      }).start();
    }
  }
  getItemsToAnimate(): HTMLElement[] | undefined {
    if (!this.isDone()) {
      const { visible, hidden } = this.getVisibleAndHiddenItems();
      this.toAnimate = hidden; // Only keep hidden items for the next check
      return visible;
    }
    return undefined;
  }
  reset(): void {
    this.toAnimate = Array.from(document.querySelectorAll < HTMLElement > (this.query));
    this.getVisibleAndHiddenItems().hidden.forEach((item: HTMLElement) => {
      item.style.opacity = "0";
      item.style.transform = "translate3d(0px, 100px, 0px)";
    });
  }
  isDone(): boolean {
    return this.toAnimate.length <= 0;
  }
  assignDelayAndOpacity(element: HTMLElement): void {
    if (element) {
      const delay = this.getDelay(element);
      Object.assign(element.style, {
        opacity: 1,
        transform: "translate3d(0,0,0)"
      });
      if (delay) {
        Object.assign(element.style, {
          transitionDelay: delay
        });
      }
    }
  }
  private getVisibleAndHiddenItems(): VisibleHiddenItems {
    return this.toAnimate.reduce((acc: VisibleHiddenItems, element: HTMLElement) => {
      const status = this.shouldBeVisible(element) ? "visible" : "hidden";
      acc[status].push(element);
      return acc;
    }, { visible: [], hidden: [] });
  }
  private getDelay(element: HTMLElement): string | undefined {
    return element?.dataset?.delay;
  }
  private shouldBeVisible(element: HTMLElement): boolean {
    const rect = element.getBoundingClientRect();
    const viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
    return !(rect.bottom < 0 || rect.top - viewportHeight >= 0) && element.style.opacity !== "1";
  }
}
// fadin.ts
const defaultOptions: FadinOptions = {
  delay: 200,
  selector: ".fadin",
  noInitalScrollEvent: false,
  animationFunction: undefined
};
class Fadin {
  private animationService: AnimationService;
  private _detach: () => void;
  constructor(options: FadinOptions = defaultOptions) {
    const mergedOptions = { ...defaultOptions, ...options };
    this.animationService = new AnimationService(mergedOptions.selector!);
    this._detach = this.setUpEventListener("scroll", () => {
      this.animationService.animateItems(mergedOptions.delay!, mergedOptions.animationFunction);
    });
    if (!mergedOptions.noInitalScrollEvent) {
      this.sendScroll();
    }
  }
  isDone(): boolean {
    return this.animationService.isDone();
  }
  detach(): () => void {
    return this._detach;
  }
  reset(): void {
    this.animationService.reset();
    this.sendScroll();
  }
  sendScroll(): void {
    window.dispatchEvent(new Event("scroll"));
  }
  private setUpEventListener(eventType: string, callback: () => void): () => void {
    const listener = () => {
      if (this.isDone()) {
        window.removeEventListener(eventType, listener, true);
      }
      callback();
    };
    window.addEventListener(eventType, listener);
    return () => window.removeEventListener(eventType, listener, true);
  }
}
// Exported function for convenience
function fadin(selector: string, options?: FadinOptions): Fadin {
  return new Fadin({ selector, ...options });
}
// Global exposure (similar to the original IIFE)
declare global {
  interface Window {
    fadin: typeof fadin;
  }
}
window.fadin = fadin;

Phiên bản TypeScript bổ sung tính năng an toàn kiểu bằng cách định nghĩa giao diện cho các tùy chọn và nhập rõ ràng các biến và tham số hàm. Điều này giúp mã dễ đọc, dễ hiểu và dễ bảo trì hơn vì bạn biết chính xác loại dữ liệu mà từng phần của mã mong đợi.

Bạn có muốn biết lời giải thích chi tiết hơn về một phần cụ thể nào đó của mã hoặc một số ví dụ về cách sử dụng fadinthư viện này không?

Last updated

Was this helpful?