最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

css - How to smoothly transition to fill space of absolute positioned element - Stack Overflow

programmeradmin3浏览0评论

I'm trying to get a list of elements to smoothly transition based on if one is added or removed. The core of this is to smoothly transition to fill space of a newly changed absolute positioned element. This example applies a fade-move to subsequent item in list, which is just a transition all ease rule.

The example I'm trying to recreate is this:

Here is a basic test, I'm not trying to show the whole transition of the items, just the transition to fill the space left by the item above. I have included the fade-move class to the items by default to prevent issues with timing. But I'm not sure why when absolute is applied to item 1 that item 2 jumps up to fill the space rather than smoothly slides up like the Vue example.

Example Sandbox: Sandbox Transition Test

Test.tsx

import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react';

const Test: React.FC = () => {
    const [show, setShow] = useState(true);
    const [currentClass, setCurrentClass] = useState('');

    const toggleShow = () => {
        setShow(prevShow => !prevShow);
    };

    useEffect(() => {
        if (show) {
            setCurrentClass('fade-enter-from');
            requestAnimationFrame(() => {
                setCurrentClass('fade-enter-active fade-enter-to');
            });
        } else {
            setCurrentClass('fade-leave-from');
            requestAnimationFrame(() => {
                setCurrentClass('fade-leave-active fade-leave-to');
            });
        }
    }, [show]);

    return (
        <div className="container">
            <button onClick={toggleShow} className="mb-4 bg-blue-500 p-2 text-white">
                Toggle Element
            </button>
            <div className="relative">
                <div className={classNames('item', currentClass)}>
                    Element 1
                </div>

                <div className="item fade-move">Element 2</div>
            </div>
        </div>
    );
};

export default Test;

app.css

.container {
    position: relative;
    padding: 0;
    list-style-type: none;
}

.item {
    width: 100%;
    height: 60px;
    background-color: #f3f3f3;
    border: 1px solid #666;
    box-sizing: border-box;
    transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1); /* Apply transition to all items by default */
}

.fade-move,
.fade-enter-active,
.fade-leave-active {
    transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* 2. declare enter from and leave to state */
.fade-enter-from,
.fade-leave-to {
    opacity: 0;
    transform: scaleY(0.01) translate(30px, 0);
}

/* 3. ensure leaving items are taken out of layout flow so that moving
      animations can be calculated correctly. */
.fade-leave-active {
    position: absolute;
}

I'm trying to get a list of elements to smoothly transition based on if one is added or removed. The core of this is to smoothly transition to fill space of a newly changed absolute positioned element. This example applies a fade-move to subsequent item in list, which is just a transition all ease rule.

The example I'm trying to recreate is this: https://vuejs.org/examples/#list-transition

Here is a basic test, I'm not trying to show the whole transition of the items, just the transition to fill the space left by the item above. I have included the fade-move class to the items by default to prevent issues with timing. But I'm not sure why when absolute is applied to item 1 that item 2 jumps up to fill the space rather than smoothly slides up like the Vue example.

Example Sandbox: Sandbox Transition Test

Test.tsx

import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react';

const Test: React.FC = () => {
    const [show, setShow] = useState(true);
    const [currentClass, setCurrentClass] = useState('');

    const toggleShow = () => {
        setShow(prevShow => !prevShow);
    };

    useEffect(() => {
        if (show) {
            setCurrentClass('fade-enter-from');
            requestAnimationFrame(() => {
                setCurrentClass('fade-enter-active fade-enter-to');
            });
        } else {
            setCurrentClass('fade-leave-from');
            requestAnimationFrame(() => {
                setCurrentClass('fade-leave-active fade-leave-to');
            });
        }
    }, [show]);

    return (
        <div className="container">
            <button onClick={toggleShow} className="mb-4 bg-blue-500 p-2 text-white">
                Toggle Element
            </button>
            <div className="relative">
                <div className={classNames('item', currentClass)}>
                    Element 1
                </div>

                <div className="item fade-move">Element 2</div>
            </div>
        </div>
    );
};

export default Test;

app.css

.container {
    position: relative;
    padding: 0;
    list-style-type: none;
}

.item {
    width: 100%;
    height: 60px;
    background-color: #f3f3f3;
    border: 1px solid #666;
    box-sizing: border-box;
    transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1); /* Apply transition to all items by default */
}

.fade-move,
.fade-enter-active,
.fade-leave-active {
    transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* 2. declare enter from and leave to state */
.fade-enter-from,
.fade-leave-to {
    opacity: 0;
    transform: scaleY(0.01) translate(30px, 0);
}

/* 3. ensure leaving items are taken out of layout flow so that moving
      animations can be calculated correctly. */
.fade-leave-active {
    position: absolute;
}
Share Improve this question edited Jan 20 at 9:33 Orbitall asked Jan 19 at 15:33 OrbitallOrbitall 66113 silver badges37 bronze badges 3
  • 1 Absolute positioning removes an element from the normal layout flow. The moment you toggle an element from static or relative positioning to absolute, it is as if that element had never been in that place to begin with. – C3roe Commented Jan 20 at 8:03
  • @C3roe That what I thought, it appears like absolute and transition are the only rules used in the Vue Transition for it to slide into position, so how is that example working or am I overlooking something? – Orbitall Commented Jan 20 at 8:33
  • I'm not actually sure right now. – C3roe Commented Jan 20 at 8:48
Add a comment  | 

1 Answer 1

Reset to default 0

Position absolute might not be the way to go here. Changing position property will cause it to change the layout. In order to create smooth transitions with pure css you want the layout to remain until the transition is finished.

The main difference between the vue implementation and yours is the fact that the use of the wrapper <TransitionGroup/> which manipulates it's children while you are applying style directly on the elements. I suspect it might be calculating transitions using js and their algorithms considers the empty space in the layout, but its difficult to say without looking at the source.

In your case where you manipulate the elements and request animation frames yourself, IMHO a better approach would be to just transition the height of the target element before you remove it from the DOM.

You can achieve this by changing your css to be much leaner as follows:

.container {
  position: relative;
  padding: 0;
  list-style-type: none;
}

.item {
  width: 100%;
  background-color: #f3f3f3;
  border: 1px solid #666;
  box-sizing: border-box;
  height: 60px;
  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* declare enter from and leave to state, one is enough as your enter-from and leave-to are the same */
.item-invisible {
  height: 0;
  opacity: 0;
  transform: scaleY(0.01) translate(30px, 0);
}

And then you don't need to manage all those classes in your transitions. Just put the appropriate destination styles:

import classNames from "classnames";
import React, { useEffect, useRef, useState } from "react";

const Test: React.FC = () => {
  const [show, setShow] = useState(true);
  const [currentClass, setCurrentClass] = useState("");

  const toggleShow = () => {
    setShow((prevShow) => !prevShow);
  };

  useEffect(() => {
    if (show) {
      setCurrentClass("item-invisible");
      requestAnimationFrame(() => {
        setCurrentClass("");
      });
    } else {
      setCurrentClass("");
      requestAnimationFrame(() => {
        setCurrentClass("item-invisible");
      });
    }
  }, [show]);

  return (
    <div className="container">
      <button onClick={toggleShow} className="mb-4 bg-blue-500 p-2 text-white">
        Toggle Element
      </button>
      <div className="">
        <div className={classNames("item", currentClass)}>Element 1</div>
        <div className="item">Element 2</div>
      </div>
    </div>
  );
};

export default Test;

This creates the desired effect.

发布评论

评论列表(0)

  1. 暂无评论