Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation when removing element in Vue JS working in dev but not in prod

Using CSS-transitions I have a component that has animations for whenever the value changes.

When I run this with npm run dev, the whole thing works just fine looking like this:

working version - using <code>npm run dev</code>

However, when using npm run build and putting the code into production on an Nginx server, the animation does not work and the element is just removed, very quickly:

buggy version - using <code>npm run build</code> and hosting with Nginx

What causes this difference between dev and prod? Is there any better way of getting this animation effect?

BugRepro.vue, which I am using to reproduce this bug:

<template>
  <div class="bugrepro">
    <div class="empty-space"></div>
    <Value :value="someNumber"></Value>
    <button @click="change()">Change it!</button>
  </div>
</template>
<script>
import Value from "./Value"

export default {
  name: "BugRepro",
  data() {
    return {
      someNumber: 20
    };
  },
  components :{
      Value
  },
  methods: {
    change() {
      this.someNumber += 1;
    }
  }
};
</script>
<style>
.empty-space {
  margin-top: 100px;
}
</style>

Value.vue, which is holding the number and in charge of handling the animations when the value changes:

<template>
  <div class="card-property">
    <button class="btn btn-sm active fa" :class="`btn-info fa-tint`" style="cursor:default" title="Mana Cost">
      {{ value }}
    </button>
    <transition-group name="diff-once" class="pos-absolute" tag="div" @after-enter="removeDiff()">
      <button v-for="diff in diffs" :key="diff.id"
        class="btn btn-sm active fa diff-once" :class="`btn-info fa-tint`" style="cursor:default">
        {{diff.value}}
      </button>
    </transition-group>
  </div>
</template>
<script>
export default {
  name: "Value",
  props: ["value"],
  data() {
    return {
      diffs: [],
      diffId: 0
    };
  },
  methods: {
    removeDiff() {
      this.diffs.splice(0, 1);
    }
  },
  watch: {
    value: function(newValue, oldValue) {
      console.log(typeof newValue);
      let diff = newValue - oldValue;
      this.diffs.push({value: diff, id: this.diffId});
      this.diffId++;
    }
  }
}
</script>
<style>
.card-property {
    position: relative;
}

.diff-once-leave-active {
  transition: all 1s;
  position: absolute;
}

.pos-absolute {
  position: absolute;
  top: 0;
  left: 0;
}

.diff-once-leave {
  opacity: 1;
}

.diff-once-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>

I have considered the problem might be in my webpack.prod.conf, I have already tried disabling the OptimizeCSSPlugin which did not help:

'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = process.env.NODE_ENV === 'testing'
  ? require('../config/test.env')
  : require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      // Setting the following option to `false` will not extract CSS from codesplit chunks.
      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
      allChunks: true,
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing'
        ? 'index.html'
        : config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),
    // keep module.id stable when vendor modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // enable scope hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    // This instance extracts shared chunks from code splitted chunks and bundles them
    // in a separate chunk, similar to the vendor chunk
    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

As I do not know which files are relevant, you can also find all files on github

Easiest steps to reproduce:

  • Checkout source code, run npm install and npm run dev
  • Go to http://localhost:42637 and click the button - animation is working correctly
  • Stop the development server and run npm run build
  • Host the files on some HTTP server like Nginx
  • Go to the HTTP server hosting the files and click the button - animation is not working

Once I know which files are the most relevant, I will of course add them to this post.

like image 974
Simon Forsberg Avatar asked Mar 06 '23 00:03

Simon Forsberg


1 Answers

The problem is that once you compile into production, all the CSS files from Bootstrap and your components get combined into one, and are run through some CSS optimisations. This causes your CSS styles to define transition at multiple points. There is your .diff-once-... styles, and there are your .btn styles. In DEV mode, .diff-once-... is higher in the cascade, in production, it's .btn. So the transition rule in button overrides all the rules you define later. You can solve this by either raising specificity on your animation classes like

.btn.diff-once-leave-to { ... }
.btn.diff-once-leave { ... }

Or (recommended) by adding scoped styles. This way all the animation styles get a higher specificity by default and don't interfere with any other styles in your app:

<style scoped>
.card-property {
    position: relative;
}

.diff-once-leave-active {
  transition: all 1s;
  position: absolute;
}

.pos-absolute {
  position: absolute;
  top: 0;
  left: 0;
}

.diff-once-leave {
  opacity: 1;
}

.diff-once-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>

Hope this helps!

like image 56
ddprrt Avatar answered Mar 16 '23 21:03

ddprrt