简体   繁体   中英

How to detect if an element is outside of its container?

So I've created the the following codesandbox . I got a webapp that relies heavily on user input. For demonstration purposes I've kept it simple by displaying a bunch of authors on a a4 formatted page. The page and font-size both use vw unit to make it responsive.

As you can see in the codesandbox, the last few authors are forced off the page because it no longer fits inside the container. Ideally I'd like to detect the content that doesn't fit on the page anymore, and generate a second identical a4 page to display that particular content.

Currently in my webapp I've just added overflow: scroll; to the page div where all the content is placed in, so that it at least looks somewhat 'ok'. But it isn't a very good User Experience and I'd like to improve it.

I don't have a clue where to start so any help in the right direction would be very much appreciated.

Thanks in advance.

CSS

#app {
  -webkit-font-smoothing: antialiased;
  font: 12pt "Tahoma";
}

.book {
  margin: 0;
  padding: 0;
  background-color: #FAFAFA;
  font: 3vw "Tahoma";
}

* {
  box-sizing: border-box;
  -moz-box-sizing: border-box;
}

.page {
  /* overflow: scroll; */
  display: block;
  width: calc(100 / 23 * 21vw);
  height: calc(100 / 23 * 29.7vw);
  margin: calc(100 / 23 * 1vw) auto;
  border: 1px #D3D3D3 solid;
  border-radius: 5px;
  background: white;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}

.subpage {
  margin: calc(100 / 23 * 1vw);
  width: calc(100 / 23 * 19vw);
  height: calc(100 / 23 * 27.7vw);
  line-height: 2;
  border: 1px solid red;
  outline: 0cm #FAFAFA solid;
}

.subpage-content {
  height: 100%;
}

Javascript

export default {
  name: "App",
  data() {
    return {
      authors: [
        { id: 1, name: "Smith" },
        { id: 2, name: "Johnson" },
        { id: 3, name: "Williams" },
        { id: 4, name: "Jones" },
        { id: 5, name: "Brown" },
        { id: 6, name: "Davis" },
        { id: 7, name: "Miller" },
        { id: 8, name: "Wilson" },
        { id: 9, name: "Moore" },
        { id: 10, name: "Taylor" },
        { id: 11, name: "Anderson" },
        { id: 12, name: "Thomas" },
        { id: 13, name: "Jackson" },
        { id: 14, name: "White" },
        { id: 15, name: "Harris" },
        { id: 16, name: "Martin" },
        { id: 17, name: "Thomspson" },
        { id: 18, name: "Garcia" },
        { id: 19, name: "Martinez" },
        { id: 20, name: "Robinson" },
        { id: 21, name: "Clark" },
        { id: 22, name: "Rodeiquez" },
        { id: 23, name: "Lewis" },
        { id: 24, name: "Lee" }
      ]
    };
  }
};

HTML

<template>
  <div id="app">
    <div class="container-fluid">
      <div class="book">
        <div class="page">HEADER
          <div class="subpage" id="editor-container">Authors:
            <!-- <div class="subpage-content">The real content</div> -->
            <div v-for="item in authors" :key="item.id">{{ item.name }}</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

You can view a fork of your code sandbox here .

I changed the data structure (and template) to have a pages array in which each page has an authors array, instead of a single one. Initially, the first page holds all the authors.


data() {
  return {
    pages: [
      {
        authors: [
          { id: 1, name: "Smith" },
          ...
        ]
      }
    ]
  }
}
<div class="page" v-for="(page, pageIndex) in pages" :key="pageIndex">HEADER
  <div class="subpage" id="editor-container">
    <template v-if="pageIndex < 1">Authors:</template>
    <!-- <div class="subpage-content">The real content</div> -->
    <div v-for="item in page.authors" :key="item.id" class="author">{{ item.name }}</div>
  </div>
</div>

I then created a method recalcPages that gets called when the component is mounted:

  methods: {
    recalcPages() {
      let pageElements = this.$el.querySelectorAll(".page");
      Array.from(pageElements).some((p, pi) => {
        let authors = p.querySelectorAll(".author");
        if (authors.length) {
          return Array.from(authors).some((a, ai) => {
            let offPage = a.offsetTop + a.offsetHeight > p.offsetHeight;
            if (offPage) {
              let currentAuthors = this.pages[pi].authors;
              var p1 = currentAuthors.slice(0, ai);
              var p2 = currentAuthors.slice(ai);

              this.pages[pi].authors = p1;
              this.pages.push({ authors: p2 });
            }
            return offPage;
          });
        }
        return false;
      });
    }
  },

It iterates the actual DOM nodes and uses offsetTop + offsetHeight to calculate whether an author is off the page or not. As soon as an element leaves the page, it and all following elements are split from the current page's authors and a second page is inserted.

You'll also need to call this.recalcPages() after updating the contents deleting all pages and set a new authors array on the first one to be split up automatically again, unless you're only adding to the last page. You could also try to use the updated hook to achieve this automatically, I haven't tried that.

Of course it's quite a heavy operation, as it renders the component just to trigger re-rendering again by modifying the data. But unless you don't know the exact height of every element, there's no way around it (at least none I'm aware of).

By the way (although your final data will probably look different, but just for the sake of completeness of this demonstration) I also wrapped your Authors: headline in <template v-if="pageIndex < 1">Authors:</template> in order to display it only on the first page.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM