add FlipCard and Carousel bricks widgets + CSS

This commit is contained in:
yumoqing 2026-07-23 14:57:14 +08:00
parent 0d42a87052
commit 6c94ce66a2
4 changed files with 170 additions and 1 deletions

View File

@ -12,7 +12,7 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \
line.js pie.js bar.js gobang.js period.js iconbarpage.js \ line.js pie.js bar.js gobang.js period.js iconbarpage.js \
keypress.js asr.js webspeech.js countdown.js progressbar.js \ keypress.js asr.js webspeech.js countdown.js progressbar.js \
qaframe.js svg.js videoplayer.js scatter.js radar.js kline.js \ qaframe.js svg.js videoplayer.js scatter.js radar.js kline.js \
heatmap.js map.js qr.js textfiles.js agent.js api_doc.js " heatmap.js map.js qr.js textfiles.js agent.js api_doc.js flipcard.js carousel.js "
echo ${SOURCES} echo ${SOURCES}
cat ${SOURCES} > ../dist/bricks.js cat ${SOURCES} > ../dist/bricks.js
# uglifyjs --compress --mangle -- ../dist/bricks.js > ../dist/bricks.min.js # uglifyjs --compress --mangle -- ../dist/bricks.js > ../dist/bricks.min.js

75
bricks/carousel.js Normal file
View File

@ -0,0 +1,75 @@
// Carousel — auto-rotating FlipCard strip
var bricks = window.bricks || {};
bricks.Carousel = class extends bricks.Layout {
/* options: { cards:[{front:{widgettype:...}, back:{widgettype:...}}], cardWidth, cardHeight, interval } */
constructor(opts) {
super(opts);
this.set_css('bricks-carousel');
this.set_style('position','relative');
this.set_style('overflow','hidden');
this._init();
}
async _init() {
var cd = this.opts.cards || [];
if (!cd.length) return;
var cw = this.opts.cardWidth || 280;
var ch = this.opts.cardHeight || 360;
var iv = this.opts.interval || 3000;
var sf = this.opts.scaleFactors || [1, 0.85, 0.7, 0.55, 0.4];
var ox = this.opts.offsets || [0, 220, 390, 500, 600];
var N = cd.length;
var mid = Math.floor(N / 2);
this._cards = [];
for (var i = 0; i < N; i++) {
var fc = new bricks.FlipCard({ width: cw + 'px', height: ch + 'px' });
fc.set_style('position', 'absolute');
fc.set_style('left', '50%');
fc.set_style('top', '50%');
fc.set_style('marginLeft', (-cw / 2) + 'px');
fc.set_style('marginTop', (-ch / 2) + 'px');
fc.set_style('transition', 'all .5s cubic-bezier(.4,0,.2,1)');
fc.set_style('transformOrigin', 'center center');
fc._pos = i - mid;
// Build front/back content asynchronously
if (cd[i].front) {
var fw = await bricks.widgetBuild(cd[i].front);
if (fw) fc.front.appendChild(fw.dom_element);
}
if (cd[i].back) {
var bw = await bricks.widgetBuild(cd[i].back);
if (bw) fc.back.appendChild(bw.dom_element);
}
this._cards.push(fc);
this.dom_element.appendChild(fc.dom_element);
}
this._sf = sf; this._ox = ox;
this._rotate(0);
this._timer = setInterval(this._rotate.bind(this, 1), iv);
}
_rotate(dir) {
var cards = this._cards, sf = this._sf, ox = this._ox;
if (!cards) return;
cards.forEach(function(c) {
var np = c._pos - dir;
if (np > 4) np = -4; if (np < -4) np = 4;
c._pos = np;
var a = Math.abs(np), idx = Math.min(a, sf.length - 1);
var x = np >= 0 ? ox[idx] : -ox[idx];
c.set_styles({
transform: 'translateX(' + x + 'px) scale(' + sf[idx] + ')',
zIndex: String(5 - idx),
opacity: String(idx === 4 ? 0 : sf[idx]),
pointerEvents: a > 3 ? 'none' : 'auto'
});
});
}
create() { this.dom_element = document.createElement('div'); }
destroy() { if (this._timer) clearInterval(this._timer); super.destroy(); }
};
bricks.Factory.register('Carousel', bricks.Carousel);

View File

@ -912,3 +912,63 @@ hr {
height: auto; height: auto;
margin: 0; margin: 0;
} }
/* === FlipCard widget === */
.bricks-flipcard {
perspective: 1000px;
cursor: pointer;
}
.bricks-flipcard-inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
transform-style: preserve-3d;
}
.bricks-flipcard:hover .bricks-flipcard-inner {
transform: rotateY(180deg);
}
.bricks-flipcard-front,
.bricks-flipcard-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
top: 0;
left: 0;
border-radius: 16px;
overflow: hidden;
}
.bricks-flipcard-back {
transform: rotateY(180deg);
}
.bricks-flipcard-front {
background: #fff;
box-shadow: 0 18px 54px rgba(90, 60, 160, 0.1), 0 4px 16px rgba(90, 60, 160, 0.05);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
gap: 12px;
}
.bricks-flipcard-back {
background: #3b82f6;
box-shadow: 0 18px 54px rgba(59, 130, 246, 0.2);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
gap: 12px;
}
/* === Carousel widget === */
.bricks-carousel {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}

34
bricks/flipcard.js Normal file
View File

@ -0,0 +1,34 @@
// FlipCard — hover to flip, front/back as flat DOM templates
var bricks = window.bricks || {};
bricks.FlipCard = class extends bricks.JsWidget {
/* options: { width, height } — front/back styled via CSS child selectors */
constructor(opts) {
super(opts);
this.set_css('bricks-flipcard');
var w = this.opts.width || '280px';
var h = this.opts.height || '360px';
this.set_style('width', w);
this.set_style('height', h);
this._dom_inner = null;
this._dom_front = null;
this._dom_back = null;
}
create() {
this.dom_element = document.createElement('div');
this._dom_inner = document.createElement('div');
this._dom_inner.className = 'bricks-flipcard-inner';
this._dom_front = document.createElement('div');
this._dom_front.className = 'bricks-flipcard-front';
this._dom_back = document.createElement('div');
this._dom_back.className = 'bricks-flipcard-back';
this._dom_inner.appendChild(this._dom_front);
this._dom_inner.appendChild(this._dom_back);
this.dom_element.appendChild(this._dom_inner);
}
// Caller builds front/back content into these containers
get front() { return this._dom_front; }
get back() { return this._dom_back; }
};
bricks.Factory.register('FlipCard', bricks.FlipCard);