Making the eyelid line SVGs stay next to the eyelids SVGs no matter what proportion the whole SVG is

2 weeks ago 15
ARTICLE AD BOX

I have this following code, which shows a pair of eyelids:

const INITIAL_LINE_Y_IN_OUTLINE_SVG_UNITS = 2.75; const MIN_EYELID_PERCENT = 29; const MAX_EYELID_PERCENT = 70; const eyelidlineLeft = document.getElementById("eyelid_outline"); const eyelidlineRight = document.getElementById("right_eyelid_outline"); const eyelids = document.getElementById("eyelid"); const pupils = document.getElementById('pupils'); let isBlinking = false; function updateEyelidsAndOutlines(event) { if (isBlinking) { return; } if (!eyelids || !eyelidlineLeft || !eyelidlineRight) { console.error("One or more eyelid elements not found. Cannot update."); return; } const windowHeight = window.innerHeight; const mousePercentFromTop = (event.clientY / windowHeight) * 100; const clampedEyelidPercent = Math.min(Math.max(mousePercentFromTop, MIN_EYELID_PERCENT), MAX_EYELID_PERCENT); eyelids.style.clipPath = `inset(0 0 ${100 - clampedEyelidPercent}% 0)`; const eyelidsRect = eyelids.getBoundingClientRect(); const eyelidsHeightPx = eyelidsRect.height; const eyelidsTopPx = eyelidsRect.top; const eyelidsBottomPx = eyelidsRect.bottom; // console.log("eyelids top is at " + eyelidsTopPx); // The `clampedEyelidPercent` represents the visible height of the eyelid fill as a percentage // from the top of the `eyelids` element. // So, the Y position of the bottom edge of the clip-path (the "wipe line") // is `eyelidsTopPx` + (`visible_height_percentage` * `eyelidsHeightPx`). const targetWipeLineAbsoluteY = eyelidsTopPx + (eyelidsHeightPx * (clampedEyelidPercent / 100)); // --- Position the eyelid lines to match the wipe line --- // Get the <svg> containers for the eyelid lines. They are direct parents of the the <g> elements. const eyelidLineLeftSvg = eyelidlineLeft.closest('svg'); const eyelidLineRightSvg = eyelidlineRight.closest('svg'); if (!eyelidLineLeftSvg || !eyelidLineRightSvg) { console.error("SVG containers for eyelid lines not found. Cannot position lines."); return; } // those get dimensions for the eyelid lines. const eyelidLineLeftSvgRect = eyelidLineLeftSvg.getBoundingClientRect(); const eyelidLineRightSvgRect = eyelidLineRightSvg.getBoundingClientRect(); // Calculate the *default* absolute Y position of the line within its own SVG // (i.e., where it would be if no extra `translateY` was applied to `eyelid_outline` or `right_eyelid_outline`). // The `INITIAL_LINE_Y_IN_OUTLINE_SVG_UNITS` is its Y position relative to its own SVG's top-left. const defaultAbsYOfLeftLine = eyelidLineLeftSvgRect.top + INITIAL_LINE_Y_IN_OUTLINE_SVG_UNITS; const defaultAbsYOfRightLine = eyelidLineRightSvgRect.top + INITIAL_LINE_Y_IN_OUTLINE_SVG_UNITS; // console.log(defaultAbsYOfLeftLine); // Calculate the `translateY` needed for each line's <g> element // This moves the line from its `defaultAbsY` to the `targetWipeLineAbsoluteY`. const requiredTranslateYLeft = targetWipeLineAbsoluteY - defaultAbsYOfLeftLine + 1; const requiredTranslateYRight = targetWipeLineAbsoluteY - defaultAbsYOfRightLine; // Apply the transformation to the eyelid line <g> elements eyelidlineLeft.style.transform = `translateY(${requiredTranslateYLeft}px)`; eyelidlineRight.style.transform = `translateY(${requiredTranslateYRight}px)`; } // Attach the main update function to the mousemove event document.addEventListener("mousemove", updateEyelidsAndOutlines); // --- Blinking Animation (now uses the same positioning logic) --- async function blink() { // Make this function async if (isBlinking) { // Prevent multiple blinks from overlapping return; } isBlinking = true; // Set flag to true at the start of blink if (!eyelids || !eyelidlineLeft || !eyelidlineRight) { isBlinking = false; // Reset flag if elements are missing return; } // Capture the current mouse-controlled state to ensure a smooth blink animation const currentEyelidsClipPath = eyelids.style.clipPath; const currentLeftLineTransform = eyelidlineLeft.style.transform; const currentRightLineTransform = eyelidlineRight.style.transform; // Get the bounding box of the <g id="eyelid"> element (the fill). const eyelidsRect = eyelids.getBoundingClientRect(); const eyelidsHeightPx = eyelidsRect.height; const eyelidsTopPx = eyelidsRect.top; // The target for a "fully closed" blink is when the eyelid covers 100% of the eye. // This means the clip-path should go to `inset(0 0 0% 0)`. const targetClosedClipPath = `inset(0 0 0% 0)`; // 0% inset means fully visible // Calculate the target ABSOLUTE Y position for the eyelid lines when the eye is fully closed. // This should precisely match the bottom edge of the 'eyelids' fill element. const targetWipeLineAbsoluteYClosed = eyelidsTopPx + eyelidsHeightPx; const eyelidLineLeftSvg = eyelidlineLeft.closest('svg'); const eyelidLineRightSvg = eyelidlineRight.closest('svg'); // Get updated bounding client rects for the SVG containers in case they've shifted const eyelidLineLeftSvgRect = eyelidLineLeftSvg.getBoundingClientRect(); const eyelidLineRightSvgRect = eyelidLineRightSvg.getBoundingClientRect(); const defaultAbsYOfLeftLine = eyelidLineLeftSvgRect.top + INITIAL_LINE_Y_IN_OUTLINE_SVG_UNITS; const defaultAbsYOfRightLine = eyelidLineRightSvgRect.top + INITIAL_LINE_Y_IN_OUTLINE_SVG_UNITS; const lineAdjustmentOffset = 3; // Use the small offset you found helpful const requiredTranslateYLeftClosed = targetWipeLineAbsoluteYClosed - defaultAbsYOfLeftLine + lineAdjustmentOffset; const requiredTranslateYRightClosed = targetWipeLineAbsoluteYClosed - defaultAbsYOfRightLine + lineAdjustmentOffset; const blinkDuration = 450; // <--- INCREASE THIS VALUE (e.g., to 450, 500, etc.) // Store the Animation objects const eyelidAnimation = eyelids.animate([ { clipPath: currentEyelidsClipPath }, { clipPath: targetClosedClipPath }, // Fully closed state for blink { clipPath: currentEyelidsClipPath } ], { duration: blinkDuration, easing: 'ease-in-out', iterations: 1, }); const leftLineAnimation = eyelidlineLeft.animate([ { transform: currentLeftLineTransform }, { transform: `translateY(${requiredTranslateYLeftClosed}px)` }, { transform: currentLeftLineTransform } ], { duration: blinkDuration, easing: 'ease-in-out', iterations: 1, }); const rightLineAnimation = eyelidlineRight.animate([ { transform: currentRightLineTransform }, { transform: `translateY(${requiredTranslateYRightClosed}px)` }, { transform: currentRightLineTransform } ], { duration: blinkDuration, easing: 'ease-in-out', iterations: 1, }); // Wait for all animations to complete await Promise.all([ eyelidAnimation.finished, leftLineAnimation.finished, rightLineAnimation.finished ]); isBlinking = false; // Reset flag to false after all animations are done } .eyes { transform:scale(1); position: absolute; width:114.5px; height:53px;/*don't mind this, it's for measuring purposes lmao.*/ z-index: 3; left: 19px; /*background-color:red;*/ } .eyesoutline { position: absolute; z-index: 2; width: 100%; height: 100%; } .eyelidlines { position:absolute; z-index:4; } .eyelidlines2 { position:absolute; z-index:4; left:55px; top:-1px; } .eyelidlines, .eyelidlines2 { transform-origin: top left; /* matches parent’s origin */ } .whole_body { position:absolute; top:110px; left:68px; } .arm { position:absolute; top:-86px; left:-148px; rotate:90deg; } @keyframes arms { 0%, 100% { transform: rotate(3deg);; } 50% { transform: rotate(0deg); } } .whole_arm { position:absolute; top:0.1px; left:0.5px; animation:arms 2s ease-in-out infinite;/*temporarily disabled*/ } .hand { position:absolute; top:63px; left:44px; } .whole_arm_left { position:absolute; top:113px; left:25px; animation:arms 2s ease-in-out infinite; } .eyelid { fill: #ffcc66; /* Eyelid color */ /*animation: wipe-in-out-eyelid2 5s */ ease-out infinite; /* Wipe animation */ transform-origin: center; position: relative; bottom: 0; } /*all we need to do for the eyelids are to replace the clip path with .eyelidlines2 with another shape (the right eye)*/ <div class="eyes"> <div class="eyelidlines"> <svg> <clipPath id="cut-bottom"> <defs><clipPath clipPathUnits="userSpaceOnUse" id="a"><path d="M-57.3-26.6v53.2H3.839c-.02-.301-.042-.6-.06-.901-2.963-.245-5.933-.587-8.878-.643-.655-1.858-1.444-3.675-1.967-5.574-.263-.958-.582-3.19-.855-5.266-6.813-.163-3.393-1.313-.123-.97-.095-.755-.228-1.738-.274-2.133-.457-3.96-.765-7.936-1.072-11.91-.203-2.596-.417-5.192-.633-7.787-.846-10.18 3.516-15.993 8.582-17.073.037-.314.08-.628.118-.943zm52.1 40.836-.01.002.01.008v-.01z"/></clipPath></defs><path d="M26.55-26.6q12.7 0 21.7 7.8t9 18.8q0 11-9 18.8-9 7.8-21.7 7.8t-21.7-7.8q-9-7.8-9-18.8t9-18.8q9-7.8 21.7-7.8M-24.7-12.75q5.6 6.4 5.6 15.45 0 9.05-5.6 15.45-5.6 6.4-13.5 6.4t-13.5-6.4q-5.6-6.4-5.6-15.45 0-9.05 5.6-15.45 5.6-6.4 13.5-6.4t13.5 6.4" fill="#fff" fill-rule="evenodd" style="fill:blue" clip-path="url(#a)" transform="translate(57.3 26.6)"/> </clipPath> <!-- eyelids--> <clipPath id="halfClosedEyes" clipPathUnits="objectBoundingBox"> <rect id="wipeRect" x="0" y="0" width="1" height="0" /> </clipPath> <rect id="wipeRect" x="0" y="25" width="0" height="0" fill="red"/><!--here it's under the eyelids.--> <g clip-path="url(#cut-bottom)"> <g id="eyelid_outline" transform="matrix(1.0, 0.0, 0.0, 1.0, 66.55, 1.35)"> <g> <path d="M66.55 1.4 L-66.55 1.4 -66.55 -1.35 66.55 -1.35 66.55 1.4" fill="#000000" fill-opacity="0.49803922" fill-rule="evenodd" stroke="none"/> </g> </g> </g></svg> </div> <div class="eyelidlines2"> <svg> <clipPath id="cut-bottom2"> <ellipse cx="29" cy="30.5" rx="30" ry="25" fill=red fill-opacity="0.6" /> </clipPath> <!-- Eyelids (blinking animation) --> <g clip-path="url(#cut-bottom2)"> <g transform="matrix(1.0, 0.0, 0.0, 1.0, 66.55, 1.35)"> <g id="right_eyelid_outline"> <path d="M66.55 1.4 L-66.55 1.4 -66.55 -1.35 66.55 -1.35 66.55 1.4" fill="#000000" fill-opacity="0.49803922" fill-rule="evenodd" stroke="none"/> </g> </g> </g></svg> </div> <svg id="eyes_svg"> <g transform="matrix(1.0, 0.0, 0.0, 1.0, 57.3, 26.6)"> <path d="M26.55 -26.6 Q39.25 -26.6 48.25 -18.8 57.25 -11.0 57.25 0.0 57.25 11.0 48.25 18.8 39.25 26.6 26.55 26.6 13.85 26.6 4.85 18.8 -4.15 11.0 -4.15 0.0 -4.15 -11.0 4.85 -18.8 13.85 -26.6 26.55 -26.6 M-24.7 -12.75 Q-19.1 -6.35 -19.1 2.7 -19.1 11.75 -24.7 18.15 -30.3 24.55 -38.2 24.55 -46.1 24.55 -51.7 18.15 -57.3 11.75 -57.3 2.7 -57.3 -6.35 -51.7 -12.75 -46.1 -19.15 -38.2 -19.15 -30.3 -19.15 -24.7 -12.75" fill="#ffffff" fill-rule="evenodd" stroke="none"/> </g> <!--<!-- Pupils <svg id="leftEyeContainer" width="400" height="300" style="border: 1px solid black;"> <ellipse id = "left_eye_boundaries" cx="19.052" cy="29.218" rx="19.066" ry="21.884" style="fill:none;fill-rule:evenodd"/> </clipPath> <g id = "left_pupil" transform="matrix(0.5, 0.0, 0.0, 0.5, 17.0, 8.0)"> <path d="M-5.05 -4.95 Q-3.0 -2.9 -3.0 0.0 -3.0 2.9 -5.05 4.95 -7.1 7.0 -10.0 7.0 -12.9 7.0 -14.95 4.95 -17.0 2.9 -17.0 0.0 -17.0 -2.9 -14.95 -4.95 -12.9 -7.0 -10.0 -7.0 -7.1 -7.0 -5.05 -4.95" fill="#000000" fill-rule="evenodd" stroke="none"/> </g> </svg>--> <svg id="rightEyeContainer"> <ellipse id="right_eye_boundaries" cx="83" cy="26" rx="30" ry="25" fill=none /> </clipPath> <!-- <g id = "right_pupil" transform="matrix(1.0, 0.0, 0.0, 1.0, 17.0, 7.0)"> <path d="M-5.05 -4.95 Q-3.0 -2.9 -3.0 0.0 -3.0 2.9 -5.05 4.95 -7.1 7.0 -10.0 7.0 -12.9 7.0 -14.95 4.95 -17.0 2.9 -17.0 0.0 -17.0 -2.9 -14.95 -4.95 -12.9 -7.0 -10.0 -7.0 -7.1 -7.0 -5.05 -4.95" fill="#000000" fill-rule="evenodd" stroke="none"/> </g> --> </svg> <g id="eyelid" transform="matrix(1.0, 0.0, 0.0, 1.0, 57.3, 26.6)"> <!--this ends at line 128.--> <path id="eyelids_color" d="M26.55 -26.6 Q39.25 -26.6 48.25 -18.8 57.25 -11.0 57.25 0.0 57.25 11.0 48.25 18.8 39.25 26.6 26.55 26.6 13.85 26.6 4.85 18.8 -4.15 11.0 -4.15 0.0 -4.15 -11.0 4.85 -18.8 13.85 -26.6 26.55 -26.6 M-24.7 -12.75 Q-19.1 -6.35 -19.1 2.7 -19.1 11.75 -24.7 18.15 -30.3 24.55 -38.2 24.55 -46.1 24.55 -51.7 18.15 -57.3 11.75 -57.3 2.7 -57.3 -6.35 -51.7 -12.75 -46.1 -19.15 -38.2 -19.15 -30.3 -19.15 -24.7 -12.75" fill="#ffcc66"/> </g> <!--<g id="eyesoutline" transform="matrix(1.0, 0.0, 0.0, 1.0, 57.3, 26.6)"> <path d="M-50.2 -11.05 Q-55.2 -5.35 -55.2 2.7 -55.2 10.8 -50.2 16.5 -45.25 22.2 -38.15 22.2 -31.1 22.2 -26.1 16.5 -21.15 10.8 -21.1 2.7 -21.15 -5.35 -26.1 -11.05 -31.1 -16.8 -38.15 -16.8 -45.25 -16.8 -50.2 -11.05 M-57.3 2.7 Q-57.3 -6.35 -51.7 -12.75 -46.1 -19.15 -38.2 -19.15 -30.3 -19.15 -24.7 -12.75 -19.1 -6.35 -19.1 2.7 -19.1 11.75 -24.7 18.15 -30.3 24.55 -38.2 24.55 -46.1 24.55 -51.7 18.15 -57.3 11.75 -57.3 2.7" fill="#000000" fill-opacity="0.49803922" fill-rule="evenodd" stroke="none"/> <path d="M26.55 -24.3 Q14.9 -24.3 6.7 -17.15 -1.55 -10.05 -1.55 0.05 -1.55 10.1 6.7 17.25 14.9 24.35 26.55 24.4 38.15 24.35 46.4 17.25 54.65 10.1 54.65 0.05 54.65 -10.05 46.4 -17.15 38.15 -24.3 26.55 -24.3 M26.55 -26.6 Q39.25 -26.6 48.25 -18.8 57.25 -11.0 57.25 0.0 57.25 11.0 48.25 18.8 39.25 26.6 26.55 26.6 13.85 26.6 4.85 18.8 -4.15 11.0 -4.15 0.0 -4.15 -11.0 4.85 -18.8 13.85 -26.6 26.55 -26.6" fill="#000000" fill-opacity="0.49803922" fill-rule="evenodd" stroke="none"/> </g>--> </svg> </div>

Here, the transform: scale(1); line in my .eyes div class the CSS side makes the eyelid lines perfectly line up with the eyelids' flat parts.

However, if I had transform:scale(0.75); or any other value besides 1 in my transform:scale(); line in my CSS code, the eyelid lines would move away from the flat parts of the eyelid:

here only the .eyes class's transform scale was changed.

I want the eyelid lines to stay right next to the eyelid's flat parts no matter what the scale of the eyes are, just like when they were in its original scale. I'd appreciate the help!

Could anyone please help me achieve this? I would very much appreciate the help!

Read Entire Article