update news & event 1
This commit is contained in:
@ -73,6 +73,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\images\3000\NewFolder\" />
|
||||
<Folder Include="wwwroot\images\wait-section\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -1,86 +1,114 @@
|
||||
<section class="con-parallax con-page-section">
|
||||
<div class="bg-wrapper parallax" data-dept="0.1">
|
||||
<img src="~/images/3000/plh-@(ViewData["GroupID"]).jpg" alt="Background" class="bg-img">
|
||||
</div>
|
||||
<div class="con section page-section section-break" data-content>
|
||||
<div class="r">
|
||||
<h1 class="title">@ViewData["Title"]</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-wrapper parallax" data-dept="0.1">
|
||||
<img src="~/images/3000/plh-@(ViewData["GroupID"]).jpg" alt="Background" class="bg-img">
|
||||
</div>
|
||||
<div class="con section page-section section-break" data-content>
|
||||
<div class="r">
|
||||
<h1 class="title">@ViewData["Title"]</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="con section pt-0">
|
||||
<div class="r">
|
||||
<div class="r-n-g">
|
||||
<div class="c-12 c-l-4 c-x-3 p-20">
|
||||
<div class="d-f f-c con-sidebarR">
|
||||
<h3 class="mb-2">Catagories</h3>
|
||||
<div class="sidebarR">
|
||||
@Html.Raw(await ServicesModel.GetSidebar(2))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-12 c-l-8 c-x-9 p-20">
|
||||
<div class="d-f">
|
||||
<h2 class="mb-4 title underline">Why GSSA Services Are Essential</h2>
|
||||
</div>
|
||||
<p class="mb-3">In today’s competitive air cargo market, GSSA services are essential for airlines and brokers looking to optimize their operations and maximize revenue. More than 26% of global air cargo is managed through GSSAs, and this market share continues to grow as more airlines leverage these services to expand their presence in new regions.</p>
|
||||
<p class="mb-3">By working with GSSA, airlines and brokers gain access to comprehensive services such as cargo sales, market analysis, capacity management and compliance with local regulations. This approach allows them to capture new business opportunities and reduce operational costs.</p>
|
||||
<div class="d-f">
|
||||
<h2 class="mt-2 mb-5 title underline">Key Benefits of GSSA Services</h2>
|
||||
</div>
|
||||
<div class="cfull">
|
||||
<div class="r-n-g">
|
||||
<div class="c-12 c-m-6 c-x-4">
|
||||
<div class="d-f p-1">
|
||||
<img class="m-2" decoding="async" width="60" height="60" src="https://aircgc.com/wp-content/uploads/2024/04/duty-and-tax-calculation.svg" class="style-svg" alt="">
|
||||
<div class="d-f f-c">
|
||||
<h3>Cost Reduction</h3>
|
||||
<p class="desc">
|
||||
Using a GSSA eliminates the need for airlines to establish local offices or hire dedicated sales teams in each region. This reduces overhead costs while ensuring a strong market presence
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-12 c-m-6 c-x-4">
|
||||
<div class="d-f p-1">
|
||||
<img class="m-2" decoding="async" width="60" height="60" src="https://aircgc.com/wp-content/uploads/2024/03/enhanced-security.svg" class="style-svg" alt="">
|
||||
<div class="d-f f-c">
|
||||
<h3>Local Expertise and Market Knowledge</h3>
|
||||
<p class="desc">
|
||||
<div class="r-n-g">
|
||||
<div class="c-12 c-l-4 c-x-3 p-20">
|
||||
<div class="d-f f-c con-sidebarR">
|
||||
<h3 class="mb-2">Catagories</h3>
|
||||
<div class="sidebarR">
|
||||
@Html.Raw(await ServicesModel.GetSidebar(2))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-12 c-l-8 c-x-9 p-20">
|
||||
<div class="d-f f-c">
|
||||
<p class="sub-title">
|
||||
We Are Air Cargo GSSA
|
||||
</p>
|
||||
<h2 class="title"> If You’ve Ever Heard of A Cargo GSSA Before, This Is How It Works</h2>
|
||||
<img class="w-100" src="~/images/3000/3001/gssa-overview.png" alt="Air Cargo GSSA illustration" />
|
||||
</div>
|
||||
<div class="r-n-g">
|
||||
<div class="c-12 c-x-6 c-xm-4 p-20">
|
||||
<div class="d-f">
|
||||
<h3 class="mb-4 title underline">Being a Cargo GSSA/GSA means</h3>
|
||||
</div>
|
||||
<ul class="order-list mb-4">
|
||||
<li class="d-f a-i-start">
|
||||
<i class="atg atg-radiobutton a-2x"></i>
|
||||
General Sales & Service Agents (GSSAs)/General Sales Agents (GSAs) represent airlines commercializing air freight capacity and supervise complex local cargo operations and administration and financial services for a assigned specific territory or region
|
||||
</li>
|
||||
<li class="d-f a-i-start mt-4">
|
||||
<i class="atg atg-radiobutton a-2x"></i>
|
||||
Represent for Airlines typically work with freight forwarders and handling agents (GHAs) to deliver transportation services to shippers.
|
||||
</li>
|
||||
<li class="d-f a-i-start mt-4">
|
||||
<i class="atg atg-radiobutton a-2x"></i>
|
||||
A extremely helpful contract with minimum cost, resources and top efficiency for Airlines in looking to expand into new markets or streamline costs in an existing location
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="c-12 c-x-6 c-xm-8 p-20">
|
||||
<div class="con-image">
|
||||
<img class="w-100" src="~/images/3000/3001/gssa-operations-team.png" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-f mt-4">
|
||||
<h3 class="mb-5 title underline">Why any Airlines should operate as Cargo GSSAs/GSAs model</h3>
|
||||
</div>
|
||||
<div class="cfull">
|
||||
<div class="r-n-g">
|
||||
<div class="c-12 c-m-6 c-x-4">
|
||||
<div class="d-f p-1">
|
||||
<img class="m-2" decoding="async" width="60" height="60" src="https://aircgc.com/wp-content/uploads/2024/04/duty-and-tax-calculation.svg" class="style-svg" alt="">
|
||||
<div class="d-f f-c">
|
||||
<h3>Cost Reduction</h3>
|
||||
<p class="desc">
|
||||
Using a GSSA eliminates the need for airlines to establish local offices or hire dedicated sales teams in each region. This reduces overhead costs while ensuring a strong market presence
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-12 c-m-6 c-x-4">
|
||||
<div class="d-f p-1">
|
||||
<img class="m-2" decoding="async" width="60" height="60" src="https://aircgc.com/wp-content/uploads/2024/03/enhanced-security.svg" class="style-svg" alt="">
|
||||
<div class="d-f f-c">
|
||||
<h3>Local Expertise and Market Knowledge</h3>
|
||||
<p class="desc">
|
||||
GSSAs possess in-depth knowledge of local markets and maintain strong relationships with key stakeholders, enabling them to maximize revenue for airlines by tapping into regional opportunities </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-12 c-m-6 c-x-4">
|
||||
<div class="d-f p-1">
|
||||
<img class="m-2" decoding="async" width="60" height="60" src="https://aircgc.com/wp-content/uploads/2024/03/comprehensive-support-and-expertise-from-start-to-finish-1.svg" class="style-svg" alt="">
|
||||
<div class="d-f f-c">
|
||||
<h3>Flexibility and Innovation</h3>
|
||||
<p class="desc">
|
||||
GSSAs provide flexible solutions that adapt to changing market conditions. From digital booking systems to real-time data analytics, GSSAs help airlines and brokers optimize routes, reduce costs and ensure efficient cargo handling
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-f">
|
||||
<h2 class="mt-2 mb-4 title underline">
|
||||
Our GSSA Services at TWA
|
||||
</h2>
|
||||
</div>
|
||||
<p class="mb-3">
|
||||
Air Cargo Green Capabilities offers a range of specialized GSSA services to help airlines and brokers streamline their cargo operations and maximize revenue:
|
||||
</p>
|
||||
<ul class="order-list mb-4">
|
||||
<li class="d-f a-i-center"><i class="atg atg-radiobutton a-2x"></i>GSSA Counseling: Air Cargo Green Capabilities provides airlines with innovative solutions to optimize costs and improve operational efficiency. By implementing eco-friendly approaches, we help airlines reduce environmental impact and capture a larger share in premium cargo segments, such as pharmaceuticals and oversized goods.</li>
|
||||
<li class="d-f a-i-center mt-4"><i class="atg atg-radiobutton a-2x"></i>GSSA Pharma: Focusing on the safe transport of pharmaceutical goods, GSSA Pharma offers specialized risk assessment and logistics solutions to ensure the integrity of sensitive shipments. Our collaboration with major global organizations like UNICEF and the Red Cross enables us to support humanitarian projects while expanding the reach of pharma logistics.</li>
|
||||
<li class="d-f a-i-center mt-4"><i class="atg atg-radiobutton a-2x"></i>GSSA for Airlines: We help airlines improve their cargo operations by maximizing the use of high-demand cargo segments like pharma, e-commerce, and oversized goods. Air Cargo Green Capabilities assists airlines with capacity management and long-term planning, ensuring they meet growing market demands while optimizing revenue.</li>
|
||||
<li class="d-f a-i-center mt-4"><i class="atg atg-radiobutton a-2x"></i>GSSA for Charter Brokers: This service supports charter brokers in expanding their market reach and improving operational flexibility. We help brokers discover new charter opportunities and manage the logistics of complex operations, ensuring they can meet the needs of their clients efficiently and profitably.</li></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-12 c-m-6 c-x-4">
|
||||
<div class="d-f p-1">
|
||||
<img class="m-2" decoding="async" width="60" height="60" src="https://aircgc.com/wp-content/uploads/2024/03/comprehensive-support-and-expertise-from-start-to-finish-1.svg" class="style-svg" alt="">
|
||||
<div class="d-f f-c">
|
||||
<h3>Flexibility and Innovation</h3>
|
||||
<p class="desc">
|
||||
GSSAs provide flexible solutions that adapt to changing market conditions. From digital booking systems to real-time data analytics, GSSAs help airlines and brokers optimize routes, reduce costs and ensure efficient cargo handling
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-f">
|
||||
<h2 class="mt-2 mb-4 title underline">
|
||||
Our GSSA Services at TWA
|
||||
</h2>
|
||||
</div>
|
||||
<p class="mb-3">
|
||||
Air Cargo Green Capabilities offers a range of specialized GSSA services to help airlines and brokers streamline their cargo operations and maximize revenue:
|
||||
</p>
|
||||
<ul class="order-list mb-4">
|
||||
<li class="d-f a-i-center"><i class="atg atg-radiobutton a-2x"></i>GSSA Counseling: Air Cargo Green Capabilities provides airlines with innovative solutions to optimize costs and improve operational efficiency. By implementing eco-friendly approaches, we help airlines reduce environmental impact and capture a larger share in premium cargo segments, such as pharmaceuticals and oversized goods.</li>
|
||||
<li class="d-f a-i-center mt-4"><i class="atg atg-radiobutton a-2x"></i>GSSA Pharma: Focusing on the safe transport of pharmaceutical goods, GSSA Pharma offers specialized risk assessment and logistics solutions to ensure the integrity of sensitive shipments. Our collaboration with major global organizations like UNICEF and the Red Cross enables us to support humanitarian projects while expanding the reach of pharma logistics.</li>
|
||||
<li class="d-f a-i-center mt-4"><i class="atg atg-radiobutton a-2x"></i>GSSA for Airlines: We help airlines improve their cargo operations by maximizing the use of high-demand cargo segments like pharma, e-commerce, and oversized goods. Air Cargo Green Capabilities assists airlines with capacity management and long-term planning, ensuring they meet growing market demands while optimizing revenue.</li>
|
||||
<li class="d-f a-i-center mt-4"><i class="atg atg-radiobutton a-2x"></i>GSSA for Charter Brokers: This service supports charter brokers in expanding their market reach and improving operational flexibility. We help brokers discover new charter opportunities and manage the logistics of complex operations, ensuring they can meet the needs of their clients efficiently and profitably.</li></ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -88,6 +116,6 @@ GSSAs possess in-depth knowledge of local markets and maintain strong relationsh
|
||||
@Html.Raw(ViewBag.Content)
|
||||
|
||||
@section jsLib {
|
||||
<script src="@Url.AbsoluteContent("~/js/ext_libs/swiper-bundle.min.js")" js-lib></script>
|
||||
<script type="module" src="@Url.AbsoluteContent("~/js/js-page/3000.js")" js-lib dynamic="LoadingServices"></script>
|
||||
<script src="@Url.AbsoluteContent("~/js/ext_libs/swiper-bundle.min.js")" js-lib></script>
|
||||
<script type="module" src="@Url.AbsoluteContent("~/js/js-page/3000.js")" js-lib dynamic="LoadingServices"></script>
|
||||
}
|
||||
@ -2129,7 +2129,7 @@ video.bg-video {
|
||||
padding: 25px;
|
||||
background: var(--text-color-lW3);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 40px
|
||||
transition: top .3s ease-in-out
|
||||
}
|
||||
.sidebarR .nav-item {
|
||||
font-size: .98rem;
|
||||
|
||||
BIN
TWA-App/wwwroot/images/3000/3001/gssa-operations-team.png
Normal file
BIN
TWA-App/wwwroot/images/3000/3001/gssa-operations-team.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 676 KiB |
BIN
TWA-App/wwwroot/images/3000/3001/gssa-overview.png
Normal file
BIN
TWA-App/wwwroot/images/3000/3001/gssa-overview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 MiB |
@ -1,5 +1,5 @@
|
||||
import AMenu from '/js/libs/js-AMenu.js'
|
||||
|
||||
import AElementFixed from '/js/libs/js-AElementFixed.js'
|
||||
function runServices () {
|
||||
|
||||
const parallaxEls = document.querySelectorAll('.con-page-section .parallax');
|
||||
@ -24,6 +24,12 @@ function runServices () {
|
||||
|
||||
window.LoadingServices = function () {
|
||||
runServices();
|
||||
const aef = new AElementFixed({
|
||||
ref: '#fHeader',
|
||||
target: '.con-sidebarR',
|
||||
breakpoint: 992,
|
||||
unsnapThreshold: 40
|
||||
});
|
||||
}
|
||||
|
||||
function updateParallax(t, parallaxEls) {
|
||||
|
||||
264
TWA-App/wwwroot/js/libs/js-AElementFixed.js
Normal file
264
TWA-App/wwwroot/js/libs/js-AElementFixed.js
Normal file
@ -0,0 +1,264 @@
|
||||
// AElementFixed.snap-up-near-ref.js
|
||||
// - Snap xuống (portal lên body) khi PROBE ra khỏi top viewport (như trước)
|
||||
// - Khi lướt LÊN lại: nếu TARGET (đang ở trong CHA) tiến gần REF (±snapUpProximity quanh ref.bottom+unsnapThreshold) → SNAP ABSOLUTE
|
||||
// - Unsnap khi PROBE vào lại viewport và |ref.top - target.top| > unsnapThreshold (giữ logic cũ)
|
||||
// - Khi đang snap: nếu đáy TARGET chạm/vượt đáy CHA → trả về CHA & pin bottom:0
|
||||
export default class AElementFixed extends window.AObject {
|
||||
constructor(opts = {}) {
|
||||
super();
|
||||
this.o = Object.assign({
|
||||
ref: null, // Element | selector
|
||||
target: null, // Element | selector
|
||||
breakpoint: 992, // < => mobile: unsnap
|
||||
unsnapThreshold: 40, // px: ngưỡng unsnap và offset top khi snap
|
||||
enableTargetBottomSnap: true, // snap nếu target ra đáy viewport (khi đang ở CHA)
|
||||
gutterLeft: 20, // px: left khi pin bottom về CHA
|
||||
snapUpProximity: 24 // px: khoảng "gần" để snap khi lướt lên lại
|
||||
}, opts);
|
||||
|
||||
this.ref = typeof this.o.ref === 'string' ? document.querySelector(this.o.ref) : this.o.ref;
|
||||
this.el = typeof this.o.target === 'string' ? document.querySelector(this.o.target) : this.o.target;
|
||||
if (!this.ref || !this.el) throw new Error('[AElementFixed] Missing ref/target');
|
||||
|
||||
// state
|
||||
this.snapped = false;
|
||||
this._isPortaled = false;
|
||||
this._hostParent = null; // CHA ban đầu
|
||||
this._loopId = null; // rAF: loop khi probe trong viewport (check snap-up & unsnap)
|
||||
this._snapLoopId = null; // rAF: monitor chạm đáy CHA khi đang snap
|
||||
this._needIntroPose = true;// hiệu ứng intro khi snap
|
||||
this._snapWidth = null; // lock width (px) khi portal
|
||||
|
||||
this._bind();
|
||||
this._initObservers();
|
||||
this._onResize();
|
||||
}
|
||||
|
||||
update(opts = {}) {
|
||||
Object.assign(this.o, opts);
|
||||
this._stopInViewLoop();
|
||||
this._stopSnapMonitor();
|
||||
this._teardownObservers();
|
||||
this._initObservers();
|
||||
this._onResize();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._stopInViewLoop();
|
||||
this._stopSnapMonitor();
|
||||
this._teardownObservers();
|
||||
this._unsnapAbsolute();
|
||||
window.removeEventListener('resize', this._onResize);
|
||||
}
|
||||
|
||||
// ===== private =====
|
||||
_bind() {
|
||||
this._onResize = this._onResize.bind(this);
|
||||
window.addEventListener('resize', this._onResize);
|
||||
}
|
||||
|
||||
_onResize() {
|
||||
const desktop = window.innerWidth >= this.o.breakpoint;
|
||||
if (!desktop) {
|
||||
this._stopInViewLoop();
|
||||
this._stopSnapMonitor();
|
||||
this._unsnapAbsolute();
|
||||
}
|
||||
}
|
||||
|
||||
_initObservers() {
|
||||
// PROBE: đặt TRƯỚC target trong CHA để giữ vị trí flow
|
||||
this.probe = document.createElement('div');
|
||||
this.probe.setAttribute('data-aef-probe', '');
|
||||
Object.assign(this.probe.style, { width: '1px', height: '1px', pointerEvents: 'none', opacity: 0 });
|
||||
this.el.parentElement.insertBefore(this.probe, this.el);
|
||||
|
||||
// PROBE với root = viewport
|
||||
this.ioProbeVP = new IntersectionObserver((entries) => {
|
||||
for (const e of entries) {
|
||||
// PROBE rời viewport phía TRÊN => SNAP ngay
|
||||
if (!e.isIntersecting && e.boundingClientRect.top < 0) {
|
||||
if (!this.snapped) this._snapAbsolute();
|
||||
this._stopInViewLoop();
|
||||
continue;
|
||||
}
|
||||
// PROBE vào viewport => bật loop để:
|
||||
// - nếu đang snap: xét UNSNAP
|
||||
// - nếu không snap: xét SNAP-UP khi target gần ref
|
||||
if (e.isIntersecting) {
|
||||
this._needIntroPose = true;
|
||||
this._startInViewLoop();
|
||||
}
|
||||
}
|
||||
}, { root: null, threshold: 0 });
|
||||
this.ioProbeVP.observe(this.probe);
|
||||
|
||||
// (Tuỳ chọn) TARGET với root = viewport: out đáy viewport (khi ở CHA) → snap
|
||||
if (this.o.enableTargetBottomSnap) {
|
||||
this.ioTargetVP = new IntersectionObserver((entries) => {
|
||||
for (const e of entries) {
|
||||
if (this.snapped) continue;
|
||||
// Bắt "đáy chạm đáy viewport" (vẫn intersect) để mượt hơn:
|
||||
const vpBottom = e.rootBounds ? e.rootBounds.bottom : document.documentElement.clientHeight;
|
||||
const EPS = 0.75;
|
||||
if (e.isIntersecting) {
|
||||
console.log(e.target.style.bottom);
|
||||
if (e.target.style.bottom === '0px') {
|
||||
this._startInViewLoop();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { root: null, threshold: [0, 1] });
|
||||
this.ioTargetVP.observe(this.el);
|
||||
}
|
||||
}
|
||||
|
||||
_teardownObservers() {
|
||||
if (this.ioProbeVP) { this.ioProbeVP.disconnect(); this.ioProbeVP = null; }
|
||||
if (this.ioTargetVP) { this.ioTargetVP.disconnect(); this.ioTargetVP = null; }
|
||||
if (this.probe && this.probe.parentNode) this.probe.parentNode.removeChild(this.probe);
|
||||
this.probe = null;
|
||||
}
|
||||
|
||||
// ===== Loop khi PROBE đang trong viewport: xét SNAP-UP & UNSNAP =====
|
||||
_startInViewLoop() {
|
||||
if (this._loopId) return;
|
||||
const tick = () => {
|
||||
const rr = this.ref.getBoundingClientRect();
|
||||
const tr = this.el.getBoundingClientRect();
|
||||
const anchor = rr.bottom + this.o.unsnapThreshold; // mốc bám khi snap
|
||||
|
||||
if (this.snapped) {
|
||||
// UNSNAP: nếu lệch khỏi mốc quá ngưỡng (tránh dính khi user kéo mạnh)
|
||||
const dy = Math.abs(tr.top - rr.top);
|
||||
if (dy > this.o.unsnapThreshold) {
|
||||
this._unsnapAbsolute(); // trả về CHA, giữ width
|
||||
this._stopInViewLoop();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// SNAP-UP khi lướt lên: target gần ref (quanh anchor)
|
||||
const delta = Math.abs(tr.top - anchor);
|
||||
if (delta <= this.o.snapUpProximity) {
|
||||
this._snapAbsolute();
|
||||
// sau snap, tiếp tục monitor chạm đáy CHA; loop này tự dừng khi ioProbe báo ra viewport
|
||||
this._stopInViewLoop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._loopId = requestAnimationFrame(tick);
|
||||
};
|
||||
this._loopId = requestAnimationFrame(tick);
|
||||
}
|
||||
_stopInViewLoop() {
|
||||
if (this._loopId) { cancelAnimationFrame(this._loopId); this._loopId = null; }
|
||||
}
|
||||
|
||||
// ===== Snap monitor: đang snap → nếu đáy target chạm/vượt đáy CHA => pin bottom:0 =====
|
||||
_startSnapMonitor() {
|
||||
if (this._snapLoopId) return;
|
||||
const tickSnap = () => {
|
||||
if (!this.snapped || !this._isPortaled || !this._hostParent) {
|
||||
this._stopSnapMonitor();
|
||||
return;
|
||||
}
|
||||
const pr = this._hostParent.getBoundingClientRect();
|
||||
const er = this.el.getBoundingClientRect();
|
||||
if (er.bottom >= pr.bottom) {
|
||||
this._pinToHostBottom();
|
||||
this._stopSnapMonitor();
|
||||
return;
|
||||
}
|
||||
this._snapLoopId = requestAnimationFrame(tickSnap);
|
||||
};
|
||||
this._snapLoopId = requestAnimationFrame(tickSnap);
|
||||
}
|
||||
_stopSnapMonitor() {
|
||||
if (this._snapLoopId) { cancelAnimationFrame(this._snapLoopId); this._snapLoopId = null; }
|
||||
}
|
||||
|
||||
// ===== helpers =====
|
||||
_portalToBody() {
|
||||
if (this._isPortaled) return;
|
||||
this._hostParent = this.el.parentNode;
|
||||
|
||||
// lock width trước khi portal để không nhảy layout
|
||||
this._snapWidth = this.el.offsetWidth;
|
||||
if (this._snapWidth && Number.isFinite(this._snapWidth)) {
|
||||
this.el.style.width = this._snapWidth + 'px';
|
||||
}
|
||||
|
||||
document.body.appendChild(this.el);
|
||||
this._isPortaled = true;
|
||||
}
|
||||
|
||||
// Trả về CHA: append vào host (đứng sau mọi thứ; nếu probe ở cuối thì tự sau probe)
|
||||
_restoreToHostBeforeProbe() {
|
||||
if (!this._isPortaled) return;
|
||||
if (this._hostParent) this._hostParent.appendChild(this.el);
|
||||
else document.body.appendChild(this.el); // fallback hiếm
|
||||
this._isPortaled = false;
|
||||
// GIỮ style.width để giữ bề ngang như yêu cầu
|
||||
}
|
||||
|
||||
// Pin về CHA bottom:0 (absolute trong CHA), giữ width
|
||||
_pinToHostBottom() {
|
||||
this._restoreToHostBeforeProbe();
|
||||
|
||||
const host = this._hostParent || this.el.parentNode;
|
||||
if (host && getComputedStyle(host).position === 'static') {
|
||||
host.style.position = 'relative';
|
||||
}
|
||||
|
||||
this.el.style.position = 'absolute';
|
||||
this.el.style.left = this.o.gutterLeft + 'px';
|
||||
this.el.style.top = '';
|
||||
this.el.style.bottom = '0px';
|
||||
|
||||
this.snapped = false;
|
||||
}
|
||||
|
||||
// ===== Snap / Unsnap =====
|
||||
_snapAbsolute() {
|
||||
if (this.snapped) return;
|
||||
this.snapped = true;
|
||||
|
||||
// đo trước khi portal để giữ LEFT & intro pose
|
||||
const tRectBefore = this.el.getBoundingClientRect();
|
||||
const rRect = this.ref.getBoundingClientRect();
|
||||
|
||||
const pageLeft = Math.round(tRectBefore.left);
|
||||
const pageTopNow = Math.round(tRectBefore.top + this.o.unsnapThreshold);
|
||||
const pageTopFinal = Math.round(rRect.bottom + this.o.unsnapThreshold);
|
||||
|
||||
// portal lên body (đồng thời lock width)
|
||||
this._portalToBody();
|
||||
|
||||
// intro pose → frame kế tiếp về top final
|
||||
this.el.style.position = 'absolute';
|
||||
this.el.style.left = pageLeft + 'px';
|
||||
this.el.style.top = (this._needIntroPose ? pageTopNow : pageTopFinal) + 'px';
|
||||
this.el.style.bottom = '';
|
||||
|
||||
if (this._needIntroPose) {
|
||||
requestAnimationFrame(() => { this.el.style.top = pageTopFinal + 'px'; });
|
||||
this._needIntroPose = false;
|
||||
}
|
||||
|
||||
this._startSnapMonitor();
|
||||
}
|
||||
|
||||
_unsnapAbsolute() {
|
||||
if (!this.snapped) return;
|
||||
this.snapped = false;
|
||||
|
||||
// không xoá width → giữ nguyên bề ngang
|
||||
this.el.style.position = '';
|
||||
this.el.style.left = '';
|
||||
this.el.style.top = '';
|
||||
this.el.style.bottom = '';
|
||||
|
||||
this._restoreToHostBeforeProbe();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user