Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TASK: Image collage with randomly positioned images #553

Merged
merged 25 commits into from Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e8a7eca
[TASK] WIP - First draft of image collage with randomly positioned im…
rtp-cgs Nov 22, 2023
6710548
BUGFIX: Update path to ImageCollage ts in Main.ts
bbehrendt-mm Feb 20, 2024
ef6ab6a
TASK: Wait for images to load before placing them & adjust positionin…
bbehrendt-mm Feb 20, 2024
ddcf3f6
Merge branch 'rebrand' into task/image-collage-organism
jonnitto Feb 20, 2024
4f54997
TASK: Register data directly on component
jonnitto Feb 20, 2024
06013f6
TASK: Refactor alpine js function
jonnitto Feb 20, 2024
f1c7628
TASK: Update position on resize
jonnitto Feb 20, 2024
7fd04d4
TASK: Add squareIcons
bbehrendt-mm Feb 20, 2024
4e77e14
Merge branch 'rebrand' into task/image-collage-organism
jonnitto Feb 20, 2024
34a7a42
TASK: Redo overlap check, allow icons to overlap images
bbehrendt-mm Feb 20, 2024
6f1d9fa
Merge remote-tracking branch 'rtp-cgs/task/image-collage-organism' in…
bbehrendt-mm Feb 20, 2024
3b462cc
TASK: Render collage with full screen height
bbehrendt-mm Feb 21, 2024
4135ab8
TASK: Use dummy image sources
bbehrendt-mm Feb 21, 2024
aa9846d
BUGFIX: Rendering order and prevent extreme overlap of elements with …
bbehrendt-mm Feb 21, 2024
0aca487
TASK: Add atropos, add sensible srcsets
bbehrendt-mm Feb 21, 2024
d2943a3
TASK: Remove case images
jonnitto Feb 21, 2024
09e9899
TASK: Use z-10 instead of z-[10]
jonnitto Feb 21, 2024
af3ea4c
Merge branch 'rebrand' into task/image-collage-organism
jonnitto Feb 22, 2024
539e6b0
Fix: Import of css
jonnitto Feb 22, 2024
0c80ebf
Task: Apply Atropos to each element individually
bbehrendt-mm Feb 22, 2024
213e6cc
TASK: Reduce shadow a bit
jonnitto Feb 22, 2024
e2af39b
TASK: Add class entry point for square icon
jonnitto Feb 22, 2024
99401ab
TASK: Prettier file
jonnitto Feb 22, 2024
8004b05
Merge branch 'rebrand' into task/image-collage-organism
jonnitto Feb 22, 2024
9f2b1c8
TASK: Move import
jonnitto Feb 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -13,6 +13,7 @@ prototype(Neos.Presentation:SquareIcon) < prototype(Neos.Fusion:Component) {
text = PropTypes:String
iconName = PropTypes:String
color = PropTypes:String
class = PropTypes:String
}

@private.backgroundColorClass = Neos.Fusion:Match {
Expand All @@ -27,7 +28,7 @@ prototype(Neos.Presentation:SquareIcon) < prototype(Neos.Fusion:Component) {
color = null

renderer = afx`
<div @if={props.iconName} class={['w-44 lg:w-56 lg:p-4 p-3 flex flex-col square-icon aspect-square text-white', private.backgroundColorClass]}>
<div @if={props.iconName} class={['w-44 lg:w-56 lg:p-4 p-3 flex flex-col square-icon aspect-square text-white', private.backgroundColorClass, props.class]}>
<Neos.Presentation:Icon name={props.iconName} class="w-8 lg:w-12 h-auto" />
<div @if={props.text} class="text mt-auto break-words hyphens-auto text-lg lg:text-2xl font-medium">{props.text}</div>
</div>
Expand Down
@@ -0,0 +1,112 @@
prototype(Neos.Presentation:Module.ImageCollage) < prototype(Neos.Fusion:Component) {

@styleguide {
title = "Image Collage"
props {
headline.text = 'Projects created with Neos CMS'
description.text = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.'
squareIcons = Neos.Fusion:Map {
items = ${[
['Lorem ipsum', 'heart', 'purple'],
['dolor sit', 'circle', 'yellow'],
['amet, consetetur', 'clock', 'lightblue']
]}
itemName = 'item'
itemRenderer = Neos.Fusion:DataStructure {
text = ${item[0]}
iconName = ${item[1]}
color = ${item[2]}
}
}
images = Neos.Fusion:Map {
items = ${[
[ "Case 1", 800, 600, '#000000' ],
[ "Case 2", 1920, 1080, '#222222' ],
[ "Case 3", 600, 1024, '#444444' ],
[ "Case 4", 1080, 1920, '#666666' ],
[ "Case 5", 600, 600, '#888888' ],
[ "Case 6", 1440, 1024, '#AAAAAA' ],
[ "Case 7", 500, 400, '#CCCCCC' ]
]}
itemName = 'item'
itemRenderer = Neos.Fusion:DataStructure {
imageSource = Sitegeist.Kaleidoscope:DummyImageSource {
title = ${item[0]}
baseWidth = ${item[1]}
baseHeight = ${item[2]}
backgroundColor = ${item[3]}
}
alternativeText = ${'Case ' + props.item[0]}
}
}
}
}

@propTypes {
headline = PropTypes:DataStructure
description = PropTypes:DataStructure
squareIcons = PropTypes:Array {
type = PropTypes:DataStructure {
imageSource = PropTypes:InstanceOf {
type = '\\Sitegeist\\Kaleidoscope\\Domain\\ImageSourceInterface'
}
alternativeText = PropTypes:String
}
}
images = PropTypes:Array {
type = PropTypes:DataStructure {
text = PropTypes:String
iconName = PropTypes:String
color = PropTypes:String
}
}
}

renderer = afx`
<Neos.Presentation:Background variant="gradient" class="overflow-clip h-screen flex flex-col">
<div class="flex-initial">
<Neos.Presentation:Spacing size y>
<div class="grid md:grid-cols-2 items-center">
<Neos.Presentation:Headline {...props.headline} display="headline-lg" />
<Neos.Presentation:Paragraph {...props.description} display="lead" />
</div>
</Neos.Presentation:Spacing>
</div>
<div class="grow" x-data="collage"
"x-on:resize.window.debounce"="processElements">
<figure class="relative h-full n-spacing--size">
<Neos.Fusion:Loop items={props.squareIcons}>
<div class="atropos atropos-image-collage-item absolute z-10 opacity-0 transition-all overflow-visible">
<div class="atropos-scale">
<div class="atropos-rotate">
<div class="atropos-inner">
<Neos.Presentation:SquareIcon class="image-collage-item" {...item} />
</div>
</div>
</div>
</div>
</Neos.Fusion:Loop>
<Neos.Fusion:Loop items={props.images}>
<div class="atropos atropos-image-collage-item opacity-0 transition-all absolute">
<div class="atropos-scale">
<div class="atropos-rotate">
<div class="atropos-inner">
<Sitegeist.Kaleidoscope:Image
class="image-collage-item h-auto w-auto"
imageSource={item.imageSource}
alt={item.alternativeText}
srcset="160w, 320w, 480w,1x ,2x"
sizes="(min-width: 1440px) 480px, 33vw"
lazy={true}
/>
</div>
</div>
</div>
</div>
</Neos.Fusion:Loop>
</figure>
</div>

</Neos.Presentation:Background>
`
}
@@ -0,0 +1,5 @@
@import url("atropos/atropos.css");

.atropos-active .atropos-shadow {
opacity: 0.5 !important;
}
@@ -0,0 +1,110 @@
import Alpine from 'alpinejs';
import Atropos from 'atropos';

// function that returns a random number
function getRandomNumber(min, max, substract = 0) {
return Math.round(min + Math.random() * (max - min) - substract);
}

// get the size of an element
function getSize(element) {
return { x: element.clientWidth, y: element.clientHeight };
}

Alpine.data('collage', () => ({
atropos: null,
figure: null,
positions: [],
rendered: [],
elements: [],
maxX: 0,
maxY: 0,
padding: 30,
objectMargin: 10,
maxAttempts: 50,
placeElement(element, size, type, attempts = 0) {
if (attempts >= this.maxAttempts) {
// console.error('Max attempts reached');
return;
}

const x = getRandomNumber(this.padding, this.maxX - this.padding, size.x / 2);
const y = getRandomNumber(this.padding, this.maxY - this.padding - size.y);

if (this.isOverlap(x, y, size, type)) {
attempts++;
this.placeElement(element, size, type, attempts);
return;
}

element.style.setProperty('left', x + 'px');
element.style.setProperty('top', y + 'px');
this.positions.push({ x, y, size, type });

// Push another element-box to prevent objects from different types to overlap entirely
this.positions.push({
x: x + size.x * 0.25,
y: y + size.y * 0.25,
size: { x: size.x / 2, y: size.y / 2 },
type: '*',
});

this.rendered.push(element);
element.classList.remove('opacity-0');
},
isOverlap(x, y, size, type) {
// return true if overlapping another element of the same type
for (const p of this.positions.filter((p) => p.type === '*' || p.type === type)) {
if (x - this.objectMargin > p.x + p.size.x || p.x > x + this.objectMargin + size.x) continue;
if (y - this.objectMargin > p.y + p.size.y || p.y > y + this.objectMargin + size.y) continue;
return true;
}

return false;
},
processElements() {
this.maxX = this.figure.clientWidth;
this.maxY = this.figure.clientHeight;
this.positions = [];
this.rendered = [];
this.elements.forEach((element) => {
element.classList.add('opacity-0');

// Get the inner image if it exists and set max height and width
let image = element.querySelector('img.image-collage-item');
image?.style.setProperty('max-width', this.maxX / 3 + 'px');
image?.style.setProperty('max-height', this.maxY / 3 + 'px');

if (!image || image.complete) {
// Element is not an image, or is already loaded; we can place
// it right away
this.placeElement(element, getSize(element), image ? 'img' : 'div');
} else {
// We need to wait for this image to load until we can place it
image.addEventListener('load', () => {
this.placeElement(element, getSize(element), 'img');
});
}
});
},
init() {
this.figure = this.$el.querySelector('figure');
this.elements = [...(this.figure?.querySelectorAll('.atropos-image-collage-item') ?? [])];

// Init atropos
this.$el.querySelectorAll('.atropos-image-collage-item').forEach((item) => {
Atropos({
el: item,
eventsEl: this.figure,
commonOrigin: false,

// SquareItems should elevate higher than image items
activeOffset: item.querySelector('img.image-collage-item')
? Math.random() * 20
: 50 + Math.random() * 10,
});
});

this.processElements();
},
}));
Expand Up @@ -8,6 +8,7 @@ import collapse from '@alpinejs/collapse';
import clipboard from '@ryangjchandler/alpine-clipboard';
import typewriter from '@marcreichel/alpine-typewriter';
import '../Molecule/LogoBar/LogoBar';
import '../Organism/ImageCollage';

// @ts-ignore
Alpine.plugin([anchor, clipboard, collapse, focus, intersect, typewriter]);
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -42,6 +42,7 @@
"@floating-ui/dom": "^1.6.3",
"@marcreichel/alpine-typewriter": "^1.2.0",
"@ryangjchandler/alpine-clipboard": "^2.3.0",
"alpinejs": "^3.13.5"
"alpinejs": "^3.13.5",
"atropos": "^2.0.2"
}
}
5 changes: 5 additions & 0 deletions yarn.lock
Expand Up @@ -508,6 +508,11 @@ async@^2.5.0:
dependencies:
lodash "^4.17.14"

atropos@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/atropos/-/atropos-2.0.2.tgz#8024e845487a69662b70fdb83f5e81039c934def"
integrity sha512-8f0u0hEOlBTWTSvzY17TcHuQjxUIpkTBq70/I4+UF5B43ORtOoRjm8TPBYEgLM8Ba9AWf6PDtkagbYoybdjaKg==

autoprefixer@^10.4.17:
version "10.4.17"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be"
Expand Down