Feature Request: Focal point on media #805
Replies: 4 comments 2 replies
-
👍🏼 bumping this! |
Beta Was this translation helpful? Give feedback.
-
Bumping this! Would love to see this, I've seen this feature on DatoCMS already. Really neat feature to have. |
Beta Was this translation helpful? Give feedback.
-
Consider this added to our roadmap 👍 We marked as Priority 2 but I do think this is a hugely valuable feature, so we'll see what we can do to expedite it immediately! |
Beta Was this translation helpful? Give feedback.
-
We also needed a focus point selector for our frontend app, because in some cases the most important image got overlayed in our hero component. This is why we've created a field group for this issue. In the import { Field } from "payload/types";
import { FocusImageComponent } from "./FocusImageComponent";
const FocusImage: Field = {
name: 'focusImage',
label: false,
type: 'group',
fields: [
{
name: 'image',
label: 'Image',
relationTo: 'media',
type: 'upload',
},
{
type: 'row',
fields: [
{
name: 'x',
type: 'number',
defaultValue: 50,
},
{
name: 'y',
type: 'number',
defaultValue: 50,
},
],
admin: {
components: {
Field: FocusImageComponent
}
},
}
]
};
export default FocusImage; This is the FocusImage field group. It contains an image and a row with x and y coordinates. The row is being replaced by a custom field written in React, which you'll see below. It reads the sibling image state. Once the image has been set it will display that image and the user is able on it to set the focus point. This click is translated to an X and Y value expressed in percentage. Which enabled us to use import React, { useState, useEffect } from "react";
import { Label, useFormFields, useForm, getSiblingData, useAllFormFields } from "payload/components/forms";
import "./FocusImage.scss";
import Upload from "payload/dist/admin/components/forms/field-types/Upload"
import fieldTypes from "payload/dist/admin/components/forms/field-types";
import { useConfig } from 'payload/components/utilities';
type Props = { label: string, path: string, required: boolean };
export const FocusImageComponent: React.FC<Props> = ({ label, path, required }) => {
const config = useConfig();
const [imageId, setImageId]: [string, (value: string) => void] = useFormFields(([fields, dispatch]) => [
fields[`${path}.image`].value as string, (value: string) => dispatch({ type: 'UPDATE', path: `${path}.image`, value: value })
]);
const [image, setImage] = useState(undefined);
const [fetching, setFetching] = useState(false);
const [x, setX]: [number, (value: number) => void] = useFormFields(([fields, dispatch]) => [
fields[`${path}.x`].value as number, (value: number) => dispatch({ type: 'UPDATE', path: `${path}.x`, value: value })
]);
const [y, setY]: [number, (value: number) => void] = useFormFields(([fields, dispatch]) => [
fields[`${path}.y`].value as number, (value: number) => dispatch({ type: 'UPDATE', path: `${path}.y`, value: value })
]);
const { setModified } = useForm();
const [mouseDown, setMouseDown] = useState(false);
useEffect(() => {
if (imageId && !image && !fetching) {
setImageId(imageId)
setFetching(true);
const fetchImage = async () => {
const response = await fetch(`${config.serverURL}${config.routes.api}/media/${imageId}`, {
credentials: 'include',
});
if (response.ok) {
const data = await response.json();
setImage(data);
}
setFetching(false);
}
fetchImage();
} else if (imageId === null && image) {
setImageId(imageId);
setImage(undefined);
}
if (!mouseDown) {
window.removeEventListener('mouseup', handleMouseUp);
}
})
const handleFocusChange = (e, drag = false) => {
if (!e?.nativeEvent?.offsetY || !e?.nativeEvent?.offsetX) return;
const focusOffset = [e.nativeEvent.offsetY, e.nativeEvent.offsetX];
const imageSize = [e.target.clientHeight, e.target.clientWidth];
const focusPointY = (((focusOffset[0]) / imageSize[0]) * 100).toFixed(3);
const focusPointX = (((focusOffset[1]) / imageSize[1]) * 100).toFixed(3);
setValues(Number(focusPointX), Number(focusPointY));
}
const handleMouseDown = (e) => {
setMouseDown(true);
window.addEventListener('mouseup', handleMouseUp);
}
const handleMouseUp = (e) => {
setMouseDown(false);
}
const handleMouseMove = (e) => {
if (mouseDown) {
handleFocusChange(e);
}
}
const setValues = (x: number, y: number) => {
setX(x);
setY(y);
setModified(true);
}
return (
<>
{image?.url &&
<>
<div className="focus__heading">
<label className="field-label">Focus ({`X: ${x}%, Y: ${y}%`})</label>
<div className="focus__reset" onClick={() => setValues(50, 50)}>Reset Focus</div>
</div>
<figure className="focus__area">
<div className="focus__picker">
<img className="focus__picker__image" src={image?.url as string} alt="" onClick={handleFocusChange} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} />
<span style={{ top: `${y}%`, left: `${x}%` }} className="focus__picker__indicator"></span>
</div>
</figure>
</>
}
</>
);
}; I'll also include the styling for anyone that wants to drag-and-drop this custom field in their own project. .focus__heading {
display: flex;
align-items: center;
gap: 10px;
.focus__reset {
cursor: pointer;
line-height: 25px;
padding-bottom: 0.4807692308rem;
text-decoration: underline;
}
.focus__reset:hover {
text-decoration: underline;
}
}
.focus__area {
position: relative;
user-select: none;
margin: 0;
padding: 0;
width: 100%;
max-width: 300px;
background-color: #fff;
background-color: var(--theme-elevation-100);
user-select: none;
box-shadow: 0 2px 3px 0 rgba(0, 2, 4, 0.05), 0 10px 4px -8px rgba(0, 2, 4, 0.02);
font-family: var(--font-body);
border-radius: 0;
font-size: 1rem;
line-height: 1.9230769231rem;
.focus__picker {
position: relative;
width: 100%;
background-color: #fff;
cursor: crosshair;
left: 0;
bottom: 0;
z-index: 1;
.focus__picker__image {
position: relative;
object-fit: contain;
height: 100%;
width: 100%;
}
.focus__picker__indicator {
position: absolute;
width: 20px;
height: 20px;
background: rgba(0, 0, 0, 0.7);
border: 0.5px solid #fff;
border-radius: 50%;
transform: translate(-50%, -50%);
}
}
}
@media (max-width: 700px), (max-height: 300px) {
.focus__picker {
display: none;
}
} Let me know what you think of it, if you like it or if you have improvements. |
Beta Was this translation helpful? Give feedback.
-
I would love a feature available in the CMS where CMS-users can identify the most important part of an image; by clicking on the image to set a 'focus point'. A bit like this PHP package (https://github.com/flokosiol/kirby-focus).
By selecting a focus point on the image; all the crops that are generated are taking that focus point in account to generate crops where the selected focus point is the center of the image (if possible of course).
Right now; the Sharpen repository is being used and this is technically already possible. There's also a open question about this feature in the Sharpen repository itself. lovell/sharp#2740
Beta Was this translation helpful? Give feedback.
All reactions